- 投稿日:2020-05-14T23:16:20+09:00
Ruby:インスタンス変数(attr_accessorとself)に関して
本記事はまだ更新の可能性があります。
※2020/5/14時点
・attr_accessorメソッド
・インスタンス変数(メソッド内で定義するself)
これらの関連性と整理を追求する記事です。
出発点はメソッド内で
@変数名やself.変数名などの使い方がややこしすぎて
わけがわからなくなってしまったので始めています。各種の初心者用説明サイトなどでは個別には説明されているものの
両方を混ぜ込んだコードでの説明はあまり見つけられませんでしたので
ここで自分の備忘録としても記載したく思います。本記事は、なんとなく整理ができた成果物としてのコードを載せております。
それぞれの考察に関しては
attr_accessorメソッドについて
メソッド定義の中におけるselfについて
上記の2つの記事を参照ください。
混ぜ込んだコードとコンソールでの吐き出しを順に記載します。
class Alcohl attr_accessor :name def initialize(hobby) @hobby=hobby end def my_hobby puts "私の好きなのは#{@hobby}" puts "でも大切なのは#{self.name}" end def wisky "飲みづらいのは#{@hobby}" end end alc=Alcohl.new("酒") alc.name="オリオンビール" alc.my_hobby alc2=Alcohl.new("ウイスキー") puts alc2.wiskyec2-user:~/environment/ruby_lessons $ ruby hello.rb 私の好きなのは酒 でも大切なのはオリオンビール 飲みづらいのはウイスキー以上です。
コードとしては汚いのかもしれませんが(それすらわからないのですが)、
とりあえず網羅性を意識しました。いったん、こちらで整理とします。
- 投稿日:2020-05-14T22:28:22+09:00
Kinx ライブラリ - Database (SQLite3)
Database / SQLite3
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。
今回は SQLite3 です。実際には後述するように Database オブジェクトを使用したほうが良いです。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
SQLite ライブラリ自体は開発初期の段階からあったのですが、イマイチ使いづらかったので改善を考えてました。今回、Database クラスを用意したのでようやく公開です。
SQLite3
SQLite3 はポータブルで使いやすい組み込み RDB。よくお世話になっています。ファイル 1 つで完結すること、そのファイルがプラットホーム非依存なことにより、色々な用途に使える。
SQLite
コア・ライブラリとして SQLite オブジェクトがあるが、実際には後述する Database オブジェクトを使うのが良い。Database クラスは SQLite クラスをラップしてより使いやすいインターフェースを提供するクラス。
DB 接続
SQLite オブジェクトを
new SQLite(filename[, timeout])
で作成してファイルに接続。var db = new SQLite("sample.db", 30000);Exec/Query
基本的に SQLite オブジェクトには基本メソッドとして
exec
、prepare
、transaction
の 3 つのメソッドがある。ただし、後述する Database クラスで、より使いやすいインターフェースを用意している。SELECT 以外の単発コマンドで
exec
を使い、それ以外ではprepare
で SQL オブジェクトを作成して実行する。exec
db.exec("CREATE TABLE IF NOT EXISTS mytable " "(" "id INTEGER PRIMARY KEY AUTOINCREMENT, " "name TEXT NOT NULL" ")" );prepare
prepare
で返された SQL オブジェクトはbind
、exec
、query
、next
というメソッドが存在する。全てのメソッドは処理後に自分自身のオブジェクトを返す。また、
next
メソッドはquery
メソッド呼び出し後に有効。next
の結果は抽出した行オブジェクトであり、以下の形式となっている。{ "columnName": [ "col1", "col2", "col3" ], "value": [ val1, val2, val3 ] }例えば以下のように使う。
var c, r = db.prepare("SELECT * FROM mytable WHERE (id > ? AND id < ?) OR id = ?") .bind(2) .bind(8, 10) // bind() は複数回に分けて実行できる。 .query(); // これまで設定した値を実際にバインディングして next() 実行可能にする。 while (c = r.next()) { var cols = c.columnName; var vals = c.value; System.println("Record[%d]" % vals[0]); for (var i = 0, len = cols.length(); i < len; ++i) { System.println(" %-8s = %s" % cols[i] % vals[i]); } }尚、
query
は SELECT でのみ有効。SELECT 以外でexec
を使用した場合、バインディングして実行した後、バインディングされていた値はその都度リセットされて次のexec
に備える。transaction
transacxtion
は一連の操作をロックして行う。例えば、複数の値を一気にインサートしたいときなどは以下のようにする。ロックするので 1 つずつ実施するより高速に実施できる。var ins = db.prepare("INSERT INTO mytable (name) VALUES (?)"); db.transaction(&{ for (var i = 0; i < 20; ++i) { ins.bind("name(%{i})").exec(); } });Database
クラス Database は上記 SQLite のラッパークラス。以下のメソッドがある。
- transaction ... トランザクション処理を行う。
- createTable ... テーブルを作成
- dropTable ... テーブルを削除
- insert ... データを挿入
- delete ... データを削除
- query ... データを抽出するための cursor オブジェクトを返す
- queryAll ... データを全て抽出
インスタンス化
普通に
new
する。var db = new Database(dbfile[, options]);インスタンス化する際のオプションは以下のとおり。
force
... true の場合、ファイルが存在なければファイルを新規に作成する。false の場合はエラーとして例外を送出する。デフォルトは false。timeout
... データベースの各種操作のタイムアウト値をミリ秒で指定。デフォルトは 10 秒。以下のように指定する。
var db = new Database("dbfile.db", { force: true });さらに、データベース名を複数のファイル名の配列として渡すと、最初の db をマスターとしてそれ以降の DB を自動的に
ATTACH DATABASE
する。その際、エイリアスとして自動的にファイル名の拡張子を除いた部分(いわゆる stem 部分、例えばabc.db
であればabc
)を登録する。マスター DB はmain
で指定し、それ以外はエイリアスで指定する。transaction
SQLite クラスの
transaction
より細かく指定できる。通常は同じように利用する。db.transaction(&{ ... });トランザクション処理が成功した場合は自動的にコミットする。途中で例外が発生した場合はロールバックを試みる。
また、Database クラスの
transaction
メソッドでは第一引数にモードを指定できる。モードは文字列で指定し、以下を指定可能。
'EXCLUSIVE'
...BEGIN EXCLUSIVE
でトランザクションを開始する。'IMMEDIATE'
...BEGIN IMMEDIATE
でトランザクションを開始する。未指定、または上記以外が指定された場合、
BEGIN
(BEGIN DEFERRED
)である。サンプルは以下の通り。db.transaction('EXCLUSIVE', &{ ... });createTable
createTable
にはテーブル名とスキーマを渡す。スキーマは直接文字列((id INTEGER, name TEXT)
のような形)で渡すか、配列で渡す(単に,
で結合されて()
で括られる)。db.createTable("mytable", [ "id INTEGER PRIMARY KEY AUTOINCREMENT", "name TEXT NOT NULL" ]);尚、常に
IF NOT EXISTS
で実行される。dropTable
dropTable
には単にテーブル名を渡す。db.dropTable("mytable");尚、常に
IF EXISTS
で実行される。注意
ステートメントが残っているとdatabase is locked
の RuntimeException 例外が発生するので、ステートメントへの参照が残っていない状態で実施すること。insert
insert
には以下のパラメータを指定する。
パラメータ 内容 into
必須、テーブル名 replace
true or false, 省略時 false column
必須、カラム名(文字列)、またはカラム名の配列 bind
必須、 column
に対するバインディング・データを配列で指定、column
で指定したカラムの数だけ必要複数
insert
する場合はtransaction
で囲んだほうが高速に実施される。db.transaction(&{ for (var i = 0; i < 20; ++i) { db.insert({ into: "mytable", column: ["name"], bind: ["name(%{i})"], }); } });delete
from
、where
、bind
で指定。全て必須(必ずbind
も必要)。db.delete({ from: "mytable", where: "id = ?", bind: [4] });query
条件に合うデータを DatabaseCursor で返す。初回の DatabaseCursor オブジェクトはデータをフェッチしていない状態で返される。
query()
に指定できるパラメータは以下の通り。
パラメータ 内容 distinct
true or false, 省略時 false select
省略時は *
、もしくはカラム名(文字列) or カラム名の配列from
必須、テーブル名 innerJoin
INNER JOIN 句 ( innerJoin
、outerJoin
は同時指定不可)outerJoin
OUTER JOIN 句 on
innerJoin
またはouterJoin
を指定したときだけ有効where
WHERE 句、文字列、または配列(AND で結合) bind
WHERE 句に対するバインディング・データを配列で指定、 ?
の数だけ必要groupby
GROUP BY 句 having
HAVING 句 orderby
ORDER BY 句 ( orderby
、orderbyAsc
、orderbyDesc
は同時指定不可)orderbyAsc
orderby
と同様orderbyDesc
ORDER BY 句、DESC 指定 limit
LIMIT 句 offset
OFFSET 句 以下がサンプル。
var c = db.query({ select: "*", from: "mytable", where: "(id > ? AND id < ?) OR id = ?", bind: [2, 8, 10] });上記復帰値
c
は DatabaseCursor オブジェクトでnext()
、columns()
、values()
メソッドを持つ。columns()
はカラム名の配列を返し、values()
は値の配列を返す。DatabaseCursor オブジェクトは以下のように
c.next()
が null を返すまでフェッチすることで全抽出データを取得できる。while (c.next()) { var cols = c.columns(); var vals = c.values(); System.println("Record[%d]" % vals[0]); for (var i = 0, len = cols.length(); i < len; ++i) { System.println(" %-8s = %s" % cols[i] % vals[i]); } }queryAll
queryAll は、コールバックを指定する方法と指定しない方法の 2 通りの使い方がある。コールバックを指定しなければ、全ての抽出データを一括で配列形式で取得できるが、データ量が多い場合メモリを消費してしまう可能性がある。それを回避するため、コールバック方式では 1 つずつイテレートしてコールバックするように動作する。
一括で取得する場合
一括で取得した場合は以下のようにコールバックを記載しない。
var res = db.queryAll({ select: "*", from: "mytable", where: "(id > ? AND id < ?) OR id = ?", bind: [2, 8, 10] });以下のようなデータ構造で返る。
{ "columns": ["id", "name"], "values": [ [3, "name(2)"], [5, "name(4)"], [6, "name(5)"], [7, "name(6)"], [10, "name(9)"] ] }コールバックさせる場合
コールバックさせる場合は、以下のように関数を渡す。
db.queryAll({ select: "*", from: "mytable", where: "(id > ? AND id < ?) OR id = ?", bind: [2, 8, 10] }, &(c, i) => { System.println("%2d => " % i, c.toJsonString()); });以下のようにそれぞれの行のオブジェクトが返される。この場合は DatabaseCursor オブジェクト ではない ので注意。
0 => {"columns":["id","name"],"values":[3,"name(2)"]} 1 => {"columns":["id","name"],"values":[5,"name(4)"]} 2 => {"columns":["id","name"],"values":[6,"name(5)"]} 3 => {"columns":["id","name"],"values":[7,"name(6)"]} 4 => {"columns":["id","name"],"values":[10,"name(9)"]}尚、コールバック関数で明示的に false を返すとイテレーションを中止して制御を戻す。
db.queryAll({ select: "*", from: "mytable", where: "(id > ? AND id < ?) OR id = ?", bind: [2, 8, 10] }, &(c, i) => { System.println("%2d => " % i, c.toJsonString()); return false if (i == 2); });以下のようになる。
0 => {"columns":["id","name"],"values":[3,"name(2)"]} 1 => {"columns":["id","name"],"values":[5,"name(4)"]} 2 => {"columns":["id","name"],"values":[6,"name(5)"]}サンプル
サンプル・コードは GitHub にあります。お試しください。
おわりに
SQLite3 使いやすいですね。単純にデータ・ストレージに使ってもいいですし、ロガーのバックエンドに使ったり、ポータブルな設定ファイルとして使ったりと色々使えます。何より RDB や SQL の基本的なことを学習するのにも良いかなー、と思います。
ではでは、また次回。
- 投稿日:2020-05-14T21:54:20+09:00
ActiveRecordでnew => build => save! するとどうなる
関連付けのあるモデルにおいて、親レコードをnew => 子レコードをbuild => 親レコードをsave! したときの挙動が複雑な気がしたのでメモ。
(理解力が不足しているだけかもしれない。)
検証環境: ActiveRecord 6.0.2.1
結論を言葉で
- has_oneでは親レコードと子レコードのvalidityは独立している。
- has_manyでは子レコードが1つでもinvalidなら親レコードもinvalidになる。
親レコードをsave!したとき、
- 親レコードがinvalidならraiseする
- 親レコードも子レコードもvalidな場合、insertがtransactionで囲まれて実行される
- 親レコードがvalidで子レコードがinvalidな場合(上記の性質によりhas_oneでのみありうる)、親レコードのinsert処理のみが実行される
実験
サンプルは以下。
migrations:
class CreateTables < ActiveRecord::Migration[6.0] def change create_table :customers do |t| end create_table :banks do |t| end create_table :merchants do |t| end create_table :bank_accounts do |t| t.references :customer, foreign_key: true, null: false t.references :bank, foreign_key: true, null: false t.string :account_number, null: false end create_table :orders do |t| t.references :customer, foreign_key: true, null: false t.references :merchant, foreign_key: true, null: false t.integer :price, null: false end end endmodels:
class Customer < ApplicationRecord has_one :bank_account has_many :orders end class Bank < ApplicationRecord end class Merchant < ApplicationRecord end class BankAccount < ApplicationRecord belongs_to :customer validates :account_number, presence: true end class Order < ApplicationRecord belongs_to :customer validates :price, presence: true end実験するケースは以下。
- 1 has_one
- 1-1 invalid
- 1-2 valid & insertable
- 1-3 valid & uninsertable
- 2 has_many
- 2-1 all invalid
- 2-2 all valid & insertable
- 2-3 all valid & uninsertable
- 2-4 valid&savable + valid & uninsertable
- 2-5 invalid + valid & uninsertable
- 2-6 invalid + valid & insertable
1-1 invalid
irb(main):118:0> customer = Customer.new irb(main):119:0> customer.build_bank_account => #<BankAccount id: nil, customer_id: nil, bank_id: nil, account_number: nil, created_at: nil, updated_at: nil> irb(main):120:0> customer.valid? => true irb(main):121:0> customer.bank_account.valid? => false irb(main):122:0> customer.bank_account.errors.full_messages => ["Account number can't be blank"] irb(main):123:0> customer.save! => true irb(main):125:0> customer.persisted? => true irb(main):126:0> customer.bank_account.persisted? => false1-2 valid & insertable
irb(main):156:0> customer = Customer.new irb(main):157:0> customer.build_bank_account(account_number: "1234", bank_id: 1) => #<BankAccount id: nil, customer_id: nil, bank_id: 1, account_number: "1234", created_at: nil, updated_at: nil> irb(main):158:0> customer.valid? => true irb(main):159:0> customer.bank_account.valid? => true irb(main):160:0> customer.save! => true irb(main):161:0> customer.persisted? => true irb(main):162:0> customer.bank_account.persisted? => true1-3 valid & uninsertable
irb(main):138:0> customer = Customer.new irb(main):139:0> customer.build_bank_account(account_number: "1234") => #<BankAccount id: nil, customer_id: nil, bank_id: nil, account_number: "1234", created_at: nil, updated_at: nil> irb(main):140:0> customer.valid? => true irb(main):141:0> customer.bank_account.valid? => true irb(main):142:0> customer.save! ActiveRecord::NotNullViolation (Mysql2::Error: Field 'bank_id' doesn't have a default value) irb(main):143:0> customer.persisted? => false irb(main):144:0> customer.bank_account.persisted? => false2-1 all invalid
irb(main):176:0> customer = Customer.new irb(main):177:0> customer.orders.build => #<Order id: nil, customer_id: nil, bank_id: nil, price: nil, created_at: nil, updated_at: nil> irb(main):178:0> customer.orders.build => #<Order id: nil, customer_id: nil, bank_id: nil, price: nil, created_at: nil, updated_at: nil> irb(main):187:0> customer.valid? => false irb(main):188:0> customer.errors.full_messages => ["Orders is invalid"] irb(main):191:0> customer.orders.map {|order| order.valid? } => [false, false] irb(main):193:0> customer.orders.map {|order| order.errors.full_messages } => [["Price can't be blank"], ["Price can't be blank"]] irb(main):196:0> customer.save! ActiveRecord::RecordInvalid (Validation failed: Orders is invalid)2-2 all valid & insertable
irb(main):001:0> customer = Customer.new irb(main):002:0> customer.orders.build(price: 100, merchant_id: 1) => #<Order id: nil, customer_id: nil, merchant_id: 1, price: 100, created_at: nil, updated_at: nil> irb(main):004:0> customer.orders.build(price: 200, merchant_id: 2) => #<Order id: nil, customer_id: nil, merchant_id: 2, price: 200, created_at: nil, updated_at: nil> irb(main):005:0> customer.valid? => true irb(main):006:0> customer.orders.map {|order| order.valid? } => [true, true] irb(main):007:0> customer.save! => true irb(main):008:0> customer.persisted? => true irb(main):009:0> customer.orders.map {|order| order.persisted? } => [true, true]2-3 all valid & uninsertable
irb(main):016:0> customer = Customer.new irb(main):017:0> customer.orders.build(price: 100) => #<Order id: nil, customer_id: nil, merchant_id: nil, price: 100, created_at: nil, updated_at: nil> irb(main):018:0> customer.orders.build(price: 200) => #<Order id: nil, customer_id: nil, merchant_id: nil, price: 200, created_at: nil, updated_at: nil> irb(main):019:0> customer.valid? => true irb(main):020:0> customer.orders.map {|order| order.valid? } => [true, true] irb(main):021:0> customer.save! ActiveRecord::NotNullViolation (Mysql2::Error: Field 'merchant_id' doesn't have a default value) irb(main):024:0> customer.persisted? => false irb(main):025:0> customer.orders.map {|order| order.persisted? } => [false, false]2-4 valid&savable + valid & uninsertable
irb(main):035:0> customer = Customer.new irb(main):036:0> customer.orders.build(price: 100, merchant_id: 1) => #<Order id: nil, customer_id: nil, merchant_id: 1, price: 100, created_at: nil, updated_at: nil> irb(main):037:0> customer.orders.build(price: 200) => #<Order id: nil, customer_id: nil, merchant_id: nil, price: 200, created_at: nil, updated_at: nil> irb(main):038:0> customer.valid? => true irb(main):039:0> customer.orders.map {|order| order.valid? } => [true, true] irb(main):040:0> customer.save! ActiveRecord::NotNullViolation (Mysql2::Error: Field 'merchant_id' doesn't have a default value) irb(main):041:0> customer.persisted? => false irb(main):042:0> customer.orders.map {|order| order.persisted? } => [false, false]2-5 invalid + valid & uninsertable
irb(main):044:0> customer = Customer.new irb(main):045:0> customer.orders.build => #<Order id: nil, customer_id: nil, merchant_id: nil, price: nil, created_at: nil, updated_at: nil> irb(main):046:0> customer.orders.build(price: 100) => #<Order id: nil, customer_id: nil, merchant_id: nil, price: 100, created_at: nil, updated_at: nil> irb(main):047:0> customer.valid? => false irb(main):048:0> customer.errors.full_messages => ["Orders is invalid"] irb(main):049:0> customer.orders.map {|order| order.valid? } => [false, true] irb(main):050:0> customer.orders.map {|order| order.errors.full_messages } => [["Price can't be blank"], []] irb(main):051:0> customer.save! ActiveRecord::RecordInvalid (Validation failed: Orders is invalid)2-6 invalid + valid & insertable
irb(main):059:0> customer = Customer.new irb(main):060:0> customer.orders.build => #<Order id: nil, customer_id: nil, merchant_id: nil, price: nil, created_at: nil, updated_at: nil> irb(main):061:0> customer.orders.build(price: 100, merchant_id: 1) => #<Order id: nil, customer_id: nil, merchant_id: 1, price: 100, created_at: nil, updated_at: nil> irb(main):062:0> customer.valid? => false irb(main):063:0> customer.errors.full_messages => ["Orders is invalid"] irb(main):064:0> customer.orders.map {|order| order.valid? } => [false, true] irb(main):065:0> customer.orders.map {|order| order.errors.full_messages } => [["Price can't be blank"], []] irb(main):066:0> customer.save! ActiveRecord::RecordInvalid (Validation failed: Orders is invalid)
- 投稿日:2020-05-14T21:15:10+09:00
herokuを使ったデプロイ手順
今回はherokuを使ったデプロイ方法について紹介していきます。
手順
1.herokuにログインする(herokuのアカウントがない人は下記のURLからアカウントを作成しましょう)
https://signup.heroku.com/2.Gemfileを以下のように編集
*DBでsqliteを使用している場合*
group :development do <!-- sqliteを使用している場合下記のgemを追加 --> gem 'sqlite3' # Access an interactive console on exception pages or by calling 'console' anywhere in the code. gem 'web-console', '>= 3.3.0' gem 'listen', '>= 3.0.5', '< 3.2' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' end*DBでmysqlを使用している場合*
group :development do <!-- mysqlを使用している場合下記のgemを追加 --> gem 'mysql2', '>= 0.4.4', '< 0.6.0' # Access an interactive console on exception pages or by calling 'console' anywhere in the code. gem 'web-console', '>= 3.3.0' gem 'listen', '>= 3.0.5', '< 3.2' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' endアプリ立ち上げ時にDBを指定した際は、初期でgemfileにgemの記述があるので削除かコメントアウトしておきましょう!
下記の記述もGemfileに追加し本番環境に適したパッケージに変更します。
group :production do gem 'pg' end3.productionの中以外をアップデートする
$ bundle --without production4.gitの管理下に置く
$ git init5.heroku上にアプリ作成(アプリ名は被らないような名前)
$ heroku create アプリ名6.全てのフォルダやファイルを管理対象にすることを明示
$ git add *7.現在の状態をコメント付きで保存
$ git commit -m "first commit"8.現在の状態をherokuに送信
$ git push heroku master9.Heroku上にデータベース作成
$ heroku run rails db:migrate*seeds.rb記述している場合は下記のコマンドも実行する*
$ heroku run rails db:seed10.アプリを開く
$ heroku openデプロイ後にローカルで編集した際には、6の手順から再び実行すると本番環境にも反映されます。
以上
- 投稿日:2020-05-14T20:58:08+09:00
【FactoryBot】外部参照キーのカラムデータの作成方法
【FactoryBot】外部参照キーのカラムデータの作成方法
FactoryBotで外部参照キーとなるカラムデータを作成する方法を調べたので内容をまとめます.
例えばPostモデルでファクトリデータを作成する際に一緒に投稿者となるUserのファクトリデータを作成するような状況です。目次
状況
Messageモデルのテストを行うためにConversationモデルとUserモデルのデータが必要です。
下記が今回のER図です。Messageモデルは
belongs_to :user, belongs_to :conversation
というアソシエーションを持ちます。すなわちMessageモデルのFactoryBotを実行した際にConversationモデルとUserモデルのデータを生成する必要があります。
message.rbclass Message < ApplicationRecord belongs_to :conversation belongs_to :user enduser.rbclass User < ApplicationRecord has_many :senders, class_name: 'Conversation', foreign_key: :sender_id has_many :recipients, class_name: 'Conversation', foreign_key: :recipient_id has_many :messages, dependent: :destroy endconversation.rbclass Conversation < ApplicationRecord belongs_to :sender, class_name: 'User', foreign_key: :sender_id belongs_to :recipient, class_name: 'User', foreign_key: :recipient_id has_many :messages, dependent: :destroy end
動作環境
OS : macOS Mojave 10.14.6
ruby : 2.6.3p62
rails : 5.2.4
factory_bot_rails : 5.2.0
手順
イメージとして以下の流れを実現するようにします。
STEP1. UserのFactoryBot呼び出し
STEP2. ConversationのFactoryBot呼び出し
STEP3. MessageのFactoryBot呼び出し
STEP 1. UserモデルのFactoryBotを定義
users.rbFactoryBot.define do #名前が重複しないようにシーケンスを利用 factory :user do sequence :name do |n| "user_#{n}" end end endSTEP 2. ConversationモデルのFactoryBotを定義
associationを記載することでUserのFactoryBotが呼び出されsenderに代入されます.
ポイントassociation(senderやrecipient)
とuser
は異なる名前のためfactory: :user
でどのfactoryを利用するのか明示していますconversations.rbFactoryBot.define do factory :conversation do association :sender, factory: :user association :recipient, factory: :user end end実際にConversaionモデルのFactoryBotを動かしてみました。
conversation.senderとconversation.recipientにFactoryBot :userで作成されたデータが入っています。STEP 3. MessageモデルのFactoryBotを定義
messages.rbFactoryBot.define do factory :message do sequence :body do |n| "message_#{n}" end #アソシエーション名とファクトリー名が異なる場合はconv factory: :conversationのように #記述.今回はアソシエーション名とファクトリ名が同じためconversationのみで問題ない. conversation #conversationファクトリで作成したuserデータをmessage.userに代入 user { conversation.sender } end end結果
message_spec.rbrequire 'rails_helper' RSpec.describe Message, type: :model do it 'バリデーションが通る' do message = build(:message, body:'test message' ) binding.pry expect(message).to be_valid end endmessage内を確認するとuserとconversationにデータが入っており、
テストをPassしている。当初つまづいたところ
当初はSTEP 3. FactoryBot:messageのコードでblankエラーが発生していた
原因
そこでMessagemモデルのファイルを確認すると
user_id
とconversation_id
にnullチェックがあったためBlankエラーになっていた。つまりこのようなバリデーションがある場合はcreateメソッドを利用する必要があります。
messages.rbFactoryBot.define do factory :message do sequence :body do |n| "message_#{n}" end after(:build) do |message| message.conversation = create(:conversation) message.user = message.conversation.sender end end endインスタンスが保存されるためidが表示される. その結果,nullチェックのバリデーションもPassできる.
おわりに
今回の件で以下のことを学びました。
1. associationメソッドを利用することで外部参照キーのデータを作成できる
2. associationメソッドはbuildのため、idのnullチェックがある場合はcreateを利用する
- 投稿日:2020-05-14T20:06:11+09:00
Rubyのsortとsort_byを理解してマルチソートをしよう
Rubyの配列を並び替えるときは
Array#sort
かArray#sort_by
を使うことになるかと思います。data = (1..10).to_a.shuffle p data.sort # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]こんなシンプルなsortをする場面なんてほとんど無いでしょうから、なんの参考にもなりませんね笑
よくあるものですと、DBやcsvなどのデータを取得したあとに並び替えたいときですね。
サンプルデータrequire 'date' data = [ {id: 1, name: '吉田(A)', ruby: 'yoshida', join_date: Date.new(2009, 4, 1)}, {id: 2, name: '鈴木', ruby: 'suzuki', join_date: Date.new(2015, 4, 1)}, {id: 3, name: '吉田(B)', ruby: 'yoshida', join_date: Date.new(2009, 4, 1)}, {id: 4, name: '佐藤', ruby: 'sato', join_date: Date.new(2006, 10, 1)}, {id: 5, name: '田中', ruby: 'tanaka', join_date: Date.new(2009, 4, 1)}, ]例えばこんな社員マスタみたいなものがあったとします。
数字の降順で並び替える
idを降順に並び替えたい場合はシンプルに
sort
にブロックを渡してb
を先にしてあげればできます。idの降順pp (data.sort do |a, b| b[:id] <=> a[:id] end) #[{:id=>5, # :name=>"田中", # :ruby=>"tanaka", # :join_date=>#<Date: 2009-04-01 ((2454923j,0s,0n),+0s,2299161j)>}, # {:id=>4, # :name=>"佐藤", # :ruby=>"sato", # :join_date=>#<Date: 2006-10-01 ((2454010j,0s,0n),+0s,2299161j)>}, # {:id=>3, # :name=>"吉田(B)", # :ruby=>"yoshida", # :join_date=>#<Date: 2009-04-01 ((2454923j,0s,0n),+0s,2299161j)>}, # {:id=>2, # :name=>"鈴木", # :ruby=>"suzuki", # :join_date=>#<Date: 2015-04-01 ((2457114j,0s,0n),+0s,2299161j)>}, # {:id=>1, # :name=>"吉田(A)", # :ruby=>"yoshida", # :join_date=>#<Date: 2009-04-01 ((2454923j,0s,0n),+0s,2299161j)>}]文字列の昇順
では、名前のローマ字の昇順にしたい場合はどうでしょうか?
sato, suzuki, tanaka, yoshida, yoshidaの順番にしたい場合ですね。ローマ字順pp (data.sort do |a, b| a[:ruby] <=> b[:ruby] end) #[{:id=>4, # :name=>"佐藤", # :ruby=>"sato", # :join_date=>#<Date: 2006-10-01 ((2454010j,0s,0n),+0s,2299161j)>}, # {:id=>2, # :name=>"鈴木", # :ruby=>"suzuki", # :join_date=>#<Date: 2015-04-01 ((2457114j,0s,0n),+0s,2299161j)>}, # {:id=>5, # :name=>"田中", # :ruby=>"tanaka", # :join_date=>#<Date: 2009-04-01 ((2454923j,0s,0n),+0s,2299161j)>}, # {:id=>1, # :name=>"吉田(A)", # :ruby=>"yoshida", # :join_date=>#<Date: 2009-04-01 ((2454923j,0s,0n),+0s,2299161j)>}, # {:id=>3, # :name=>"吉田(B)", # :ruby=>"yoshida", # :join_date=>#<Date: 2009-04-01 ((2454923j,0s,0n),+0s,2299161j)>}]ブロック内の比較対象を
:ruby
にするだけでできました。
これはString#<=>が実装されているために簡単に実現することができています。sortってなにしてくれてるんだっけ
っていうか、そもそもなんで<=>を指定すると並び替えてくれるんだっけ?
というのを思い返すときにArray#sortのドキュメントを読んでみます。ブロックとともに呼び出された時には、要素同士の比較をブロックを用いて行います。ブロックに2つの要素を引数として与えて評価し、その結果で比較します。ブロックは <=> 演算子と同様に整数を返すことが期待されています。つまり、ブロックは第1引数が大きいなら正の整数、両者が等しいなら0、そして第1引数の方が小さいなら負の整数を返さなければいけません。両者を比較できない時は nil を返します。
つまり最終的にブロックの最後に評価される値が整数を返してあげれば並び替えてくれるわけです。
<=>
メソッドは何らかの比較をして1, -1, 0, nil
のいずれかを返すように実装されているので、それを使えば簡単に並び替えできる、というわけです。Arrayに<=>がおるやん
それを理解した上でArrayのドキュメントを見ると気付くわけです。
Array#<=>が実装されていることに。自身と other の各要素をそれぞれ順に <=> で比較していき、結果が 0 でなかった場合にその値を返します。各要素が等しく、配列の長さも等しい場合には 0 を返します。各要素が等しいまま一方だけ配列の末尾に達した時、自身の方が短ければ -1 をそうでなければ 1 を返します。 other に配列以外のオブジェクトを指定した場合は nil を返します。
つまり配列同士の比較もできるわけです。
配列の先頭から比較し、同値の場合には次の要素を検証していき、同値じゃなかった場合には1, -1を返してくれます。
ここまで来ればもうマルチソートのやり方もなんとなく理解できましたね。マルチソートしてみる
ではサンプルのデータで、先程ローマ字順に並び替えましたが、yoshidaがかぶっています。
sort
では元の順番が保持されているためidが低い1のyoshidaが先に来ています。
3のyoshidaを先に持っていきたい(第2ソートキーとしてidの降順にしたい)場合はどうするか?こうです。
第二ソートキーとしてidを指定pp (data.sort do |a, b| [a[:ruby], -a[:id]] <=> [b[:ruby], -b[:id]] end)最初に
:ruby
で比較をし、そこで優劣がつくようなら:id
での評価はしません。
:id
での比較の際に-
をつけ、値を逆転させることで降順を実現しています。日付(Dateクラス)の降順の並び替えは?
じゃあマルチソートは一旦置いといて、今度は入社年月日の降順(新しい順)にしたい場合はどうでしょうか?
Dateクラスにも<=>が実装されているので並び替えは容易ですね。
しかし降順となるとIntegerのように-をすることはできません。(Date#-は実装されていますが、算術演算子としての-なので引数が必要です)そうなった場合、最初の
:id
での降順の並び替えで提示したようにaとbを入れ替えてあげることで実現できます。join_dateの降順で並び替えpp (data.sort do |a, b| b[:join_date] <=> a[:join_date] end) #[{:id=>2, # :name=>"鈴木", # :ruby=>"suzuki", # :join_date=>#<Date: 2015-04-01 ((2457114j,0s,0n),+0s,2299161j)>}, # {:id=>1, # :name=>"吉田(A)", # :ruby=>"yoshida", # :join_date=>#<Date: 2009-04-01 ((2454923j,0s,0n),+0s,2299161j)>}, # {:id=>3, # :name=>"吉田(B)", # :ruby=>"yoshida", # :join_date=>#<Date: 2009-04-01 ((2454923j,0s,0n),+0s,2299161j)>}, # {:id=>5, # :name=>"田中", # :ruby=>"tanaka", # :join_date=>#<Date: 2009-04-01 ((2454923j,0s,0n),+0s,2299161j)>}, # {:id=>4, # :name=>"佐藤", # :ruby=>"sato", # :join_date=>#<Date: 2006-10-01 ((2454010j,0s,0n),+0s,2299161j)>}]マルチソートで日付の降順の方法、その2
しかしこれをマルチソートのキーとして採用するとちょっとわかりづらくなる気がします。しませんか?私だけですか?
join_dateの降順で並び替えpp (data.sort do |a, b| [a[:ruby], b[:join_date]] <=> [b[:ruby], a[:join_date]] # aとbのキーを間違えてない???って思ってしまう end) # サンプルデータだとあんまり意味がないのでppの結果は省略します笑まぁ
-
で値を逆転させるのもあまり直感的ではないかも知れないので好みの問題だとは思いますが
個人的にはunixtime化して計算させるほうがわかりやすく思います。pp (data.sort do |a, b| a_time = a[:join_date].to_time.to_i b_time = b[:join_date].to_time.to_i [a[:ruby], -a_time] <=> [b[:ruby], -b_time] end)と、こんな感じで配列同士を比較させればマルチソートでの昇順/降順も自由にコントロールできます。
処理が肥大化しそうならsort_byを使えばいいじゃない
ただ上記のto_time.to_iとかもそうですし、nullrableな値がある場合の処理とかを考えると、a,bそれぞれの変数に対して同じことをやってブロックの中が肥大化していく未来が見えます。
そこでEnumerable#sort_byを使うのが良いのではないかと考えます。
ブロックの評価結果を <=> メソッドで比較することで、self を昇順にソートします
とあるので、ブロックの最後の評価されるものが
<=>
で比較できればよいので配列を渡せばArray#<=>
でマルチソートが簡単にできるやん、という感じです。つまりこうpp (data.sort_by do |v| [v[:ruby], -v[:join_date].to_time.to_i, v[:id]] end)またドキュメントにある通り、複雑な並び替えの場合には
sort
よりも処理数が少なくパフォーマンス的にも期待できます。まとめ
sort
のブロックには整数を返せばよい- 配列を渡せばPHPのarray_multisortみたいなことができる
- 複雑な条件には
sort_by
の方がおすすめブロック内でififするのは複雑度が上がって良くないので配列を渡しちゃいましょう!
- 投稿日:2020-05-14T19:38:17+09:00
Rails パンくずリスト gretel の使い方
概要
こんにちは、今回はパンくずリストを作成する際に使ったgretelというgemの使い方を記載します。
インストール
gem 'gretel'bundle installします。
ファイル作成
下記コマンドを打つと専用の設定ファイルが作成されます。
$ rails generate gretel:installconfig/breadcrumbs.rbというファイルが作成されたはずです。
パンくずを作成
前項で作成された設定ファイルにパンくずを設定していきます。
config/breadcrumbs.rbcrumb :top do link "トップページ", root_path end crumb :shops do link "カフェ一覧", shops_path parent :top end crumb :user do |user| link user.username, user_path(user) parent :shops end crumb :edit_user do |user| link "編集", edit_user_registration_path parent :user, user end crumb :following_user do |user| link "フォロー", following_user_path parent :user, user end crumb :followers_user do |user| link "フォロワー", followers_user_path parent :user, user end crumb :shop do |shop| link shop.name, shop_path parent :shops end crumb :edit_shop do |shop| link "編集", edit_shop_path parent :shop, shop end今回は最大4階層までのパンくずを設定しました。
crumbでパンくずを定義し、linkにパスを記述します。
parentには親となるパンくずを設定。parent :モデル名 と記述したあとに注目してください。
crumb :edit_shop do |shop| link "編集", edit_shop_path parent :shop, shop #ここに注目! endこのカンマ以降の記述が無ければ対象のビューで
NoMethodErrorが出てしまいます。この書き方がなかなか分かりませんでした。
viewに配置
view側はシンプルです。
html.erb<% breadcrumb :user, @user %> <%= breadcrumbs separator: " › " %>これだけです。
separatorでパンくずの階層である > を追加しています。
その他にも色々とオプションがあるのでぜひ公式で確認してみてください。以上です。
お付き合い頂きありがとうございました。
- 投稿日:2020-05-14T18:38:09+09:00
AtCoder Beginners SelectionでRuby学習【PracticeA】標準入力
はじめに
Ruby学習の一環として「競技プログラミング(競プロ)」に挑戦します。
そのための学習の中で学んだことをアウトプットしていきます。
今回は「AtCoder Beginners Selection」の一問目(PracticeA)より、標準入力について。
https://atcoder.jp/contests/abs問題
整数a,b,cと、文字列sが与えられます。
a+b+cの計算結果と、文字列sを並べて表示しなさい。制約
1≤a,b,c≤1,000
1≤|s|≤100入力は以下の形で与えられる。
a b c s # 例 1 2 3 testa+b+cとsを空白区切りで1行に出力せよ。
出力例 # 上記例の場合 6 test解答例
a = gets.to_i b,c = gets.chomp.split("").map(&:to_i) s = gets.chomp print("#{a+b+c} #{s}\n")ここで使われている標準入力に関わるメソッドを一つ一つ見ていきます。
getsメソッド
rubyにおいて標準入力を受け取るにはgetsメソッドを使います。
ただ、getsメソッドで標準入力を受け取る際には以下の2点に注意します。
①標準入力を文字列で受け取る
②文字列を受け取る場合は末尾に改行コード\nが付いて来る。#入力 3 a = gets print a => "3\n"to_iメソッド
文字列を整数にして返すメソッドです。
getsメソッドのみでは、整数ですら文字列として受け取ってしまうため、
整数を整数として扱いたい場合は、to_iメソッドを使って整数に戻してあげる必要があります。#入力 3 a = gets.to_i print a => 3この場合、改行コードも消してくれます。
chompメソッド
getsメソッドで文字列を受け取る際の改行コード問題。
これを解決してくれるのがchompメソッドです。末尾の改行コードを取り除いてくれます。
この改行コードの存在をそのままにすると、これだけで誤答判定される場合もあるようなので注意が必要です。#入力 test s = gets.chomp print s => "test"ちなみに
to_iメソッドの部分でも述べましたが、整数に変換した後は使う必要はありません。
必要ない場合にchompメソッドを付けてしまうと処理速度の低下に繋がるので注意。splitメソッド
文字列を分割して配列にしてくれるメソッドです。
区切り文字を「split("区切り文字")」の形で指定することで、区切り部分を指定できます。
解答例では、bとcの間のスペースを区切り文字として指定しています。# 段階的に見ていきます。 #入力 3 4 a = gets print a => "3 4\n" b = a.chomp print b => "3 4" c = b.split(" ") print c => ["3", "4"] # まとめて書くと #入力 3 4 d = gets.chomp.split(" ") print d => ["3", "4"]mapメソッド
配列の要素の数だけ繰り返し処理を行ってくれます。
今回の解答で用いている「map(&:to_i)」では、
繰り返し処理の引数として「to_iメソッド」を「&」を使うことで渡せる状態にしています。
このことについては、詳しく書くと長くなる内容なので…
とりあえず、下記の記事が詳しく書いて下さっているので、こちらを覗いてみられるといいかと。["1", "2", "3"].map(&:to_i) とは何か。
近いうちに自分でも記事にしてみようと思います。
# splitメソッドの続きから #入力 3 4 d = gets.chomp.split(" ") print d => ["3", "4"] e = d.map(&:to_i) print e => [3, 4]最後に
以上、AtCoder Beginners SelectionでRuby学習【PracticeA】で学ぶ標準入力でした。
mapメソッドの部分で課題は残りましたが、
ここもしっかり理解して自分の力にしていきたいと思います。
間違いなどありましたら、ご指摘いただけると嬉しいです。
- 投稿日:2020-05-14T18:37:21+09:00
hamlの導入方法
今回はhamlを導入し、erbをhamlに変換する方法を紹介していきます。
手順
1.Gemfileに以下を記述
gem 'haml-rails'2.bundle install
bundle install3.ターミナルで以下のコマンドを実行し、拡張子がerbのファイルをhamlに変換する。
$ rails haml:erb2haml※Would you like to delete the original .erb files? (This is not recommended unless you are under version control.) (y/n)
と聞かれるため、yと入力するとデフォルトで作成されたerbファイルはHamlに変換される。以上
- 投稿日:2020-05-14T18:31:52+09:00
bootstrapの導入方法
bootstrapってなに?
RubyのフレームワークはRuby on Railsで言うところの、
boootstrapとはCSSのフレームワークみたいなものです。bootstrapを学べば、効率的にビューを実装できるので知っておいて損はないです。。
導入方法
1.Gemfileに以下を記述する。
gem "sass-rails","~>5.0" gem "bootstrap-sass","~>3.3.6" gem "jquery-rails" gem "jquery-ui-rails"2.bundle updateする
$ bundle update3.appcation.cssを.scssに拡張してから下記を記述する。
@import "bootstrap-sprockets"; @import "bootstrap";4.appcation.jsの中に下記を記述。
//= require jquery //= require~と書いてる部分の一番上に記述 //= require bootstrap-sprockets //= require~と書いてる部分の一番下に記述以上
- 投稿日:2020-05-14T18:06:09+09:00
モデルの単体テストの書き方
はじめに
みなさん単体テストは好きですか??
私は苦手です。。。。多分苦手な方も多いと思うので、今回はモデルの単体テストの書き方について紹介できればいいかなと思います。
私も未熟なので、参考程度にしてください。おねがいします。。1.Gemfile
はじめにgemfileにrspecを導入していきます。
今回はfactory_botも使うので、以下のようにgemfileに記述していきましょう。group :development, :test do gem 'rspec-rails', '~> 3.8' gem 'factory_bot_rails', '~> 5.0' endbundle installも忘れずに!
$ bundle install2.RSpecの設定
ターミナルで以下のコマンドを記述。
specというディレクトリが生成されます。$ rails g rspec:install.rspecに以下を記述
--format documentationテスト用のDBを最新の構成にする
$ rails db:migrate:reset RAILS_ENV=test3.モデルを作成し単体テストを書いていこう
まずはテストモデルを生成します。
$ rails generate rspec:model モデル名それではfactory_botを使ってモデルにテストデータを予め準備します。
spec/factories/モデル名.rbファイルを作成しましょう。
ファイルの中身の模範は以下のようです。FactoryBot.define do factory :モデル名 do カラム名 {"仮の値"} end end実際に書くとこんな感じ。
FactoryBot.define do factory :product do name {Faker::Games::Zelda.item} infomation {Faker::Games::Zelda.game} category_id {"1"} brand {Faker::Games::Zelda.location} status_id {"1"} delivery_id {"1"} area_id {"1"} day_id {"1"} price {Faker::Number.number(digits: 4)} situation {"1"} user end end続いて、factory_botの情報を使ってテストコードを書いていきます。
spec/models/モデル名_spec.rbファイルを作成しましょう。
ファイルの中身の模範はこんな感じ。require 'rails_helper' describe モデル名 do describe '#メソッド名' do context 'グループ分けの説明' do it 'テストの説明' do テスト内容 end end end end実際はこんな感じ。
factory_botを使用しているので、記述量が減って簡単にかけます!require 'rails_helper' describe Product do describe '#create' do context '出品できる場合' do it "productとproduct_imageがある場合は保存できること" do expect(build(:product, product_images: [build(:product_image)])).to be_valid end end context '出品できない場合' do it "product_imageがない場合は保存できないこと" do product = build(:product) product.valid? expect(product.errors[:product_images]).to include("can't be blank") end it "nameがない場合は保存できないこと" do product = build(:product, name: nil, product_images: [build(:product_image)]) product.valid? expect(product.errors[:name]).to include("can't be blank") end it "infomationがない場合は保存できないこと" do product = build(:product, infomation: nil, product_images: [build(:product_image)]) product.valid? expect(product.errors[:infomation]).to include("can't be blank") end it "category_idがない場合は保存できないこと" do product = build(:product, category_id: nil, product_images: [build(:product_image)]) product.valid? expect(product.errors[:category_id]).to include() end it "status_idがない場合は保存できないこと" do product = build(:product, status_id: nil, product_images: [build(:product_image)]) product.valid? expect(product.errors[:status_id]).to include() end it "delivery_idがない場合は保存できないこと" do product = build(:product, delivery_id: nil, product_images: [build(:product_image)]) product.valid? expect(product.errors[:delivery_id]).to include() end it "area_idがない場合は保存できないこと" do product = build(:product, area_id: nil, product_images: [build(:product_image)]) product.valid? expect(product.errors[:area_id]).to include() end it "day_idがない場合は保存できないこと" do product = build(:product, day_id: nil, product_images: [build(:product_image)]) product.valid? expect(product.errors[:day_id]).to include() end it "priceがない場合は保存できないこと" do product = build(:product, price: nil, product_images: [build(:product_image)]) product.valid? expect(product.errors[:price]).to include() end it "priceが300以下の場合は保存できないこと" do product = build(:product, price: "299", product_images: [build(:product_image)]) product.valid? expect(product.errors[:price]).to include() end it "priceが9,999,999以上の場合は保存できないこと" do product = build(:product, price: "10000000", product_images: [build(:product_image)]) product.valid? expect(product.errors[:price]).to include() end it "user_idがない場合は保存できないこと" do product = build(:product, user_id: nil, product_images: [build(:product_image)]) product.valid? expect(product.errors[:user_id]).to include() end end end end最後に以下のコマンドをターミナルで実行し、テストがうまくいっているか確認です!
$ bundle exec rspec spec/models/モデル名_spec.rb以上です!
- 投稿日:2020-05-14T17:32:46+09:00
【簡潔】Brakeman導入方法-Rails-
Brakeman導入方法
Brakemanとは
Brakemanとはアプリケーションを開発した際にセキュリティ検査ができるgemの一つです。
Railsでは元からセキュリティについては比較的維持できるものですが、私が開発した際に補助的にこのようなツールを使ってみました。ちなみにBrakemanは静的なツールです。導入方法
作成中のプロジェクトの
Gemfile
内のgroup :development do
とend
の間にgem 'brakeman', require: false
を追記します。group :development do gem 'brakeman', require: false end次にターミナルの作成中アプリのディレクトリで
brakeman
と叩けばBrakemanでのセキュリティチェックが始まります。% brakeman出力はこんな感じです。
== Brakeman Report == Application Path: /Users/UserName/FolderName/AppName Rails Version: 5.2.4.2 Brakeman Version: 4.8.2 Scan Date: 0000-00-00 00:00:0 +0000 Duration: 0.290167 seconds Checks Run: BasicAuth, BasicAuthTimingAttack, ContentTag, CookieSerialization, CreateWith, CrossSiteScripting, DefaultRoutes, Deserialize, DetailedExceptions, DigestDoS, DynamicFinders, EscapeFunction, Evaluation, Execute, FileAccess, FileDisclosure, FilterSkipping, ForgerySetting, HeaderDoS, I18nXSS, JRubyXML, JSONEncoding, JSONEntityEscape, JSONParsing, LinkTo, LinkToHref, MailTo, MassAssignment, MimeTypeDoS, ModelAttrAccessible, ModelAttributes, ModelSerialize, NestedAttributes, NestedAttributesBypass, NumberToCurrency, PageCachingCVE, PermitAttributes, QuoteTableName, Redirect, RegexDoS, Render, RenderDoS, RenderInline, ResponseSplitting, RouteDoS, SQL, SQLCVEs, SSLVerify, SafeBufferManipulation, SanitizeMethods, SelectTag, SelectVulnerability, Send, SendFile, SessionManipulation, SessionSettings, SimpleFormat, SingleQuotes, SkipBeforeFilter, SprocketsPathTraversal, StripTags, SymbolDoSCVE, TranslateBug, UnsafeReflection, ValidationRegex, WithoutProtection, XMLDoS, YAMLParsing == Overview == Controllers: 2 Models: 3 Templates: 34 Errors: 0 Security Warnings: 0 == Warning Types == No warnings foundセキュリティチェック完了です。
この記事を読んでいただきありがとうございました。
- 投稿日:2020-05-14T17:24:24+09:00
CurrentBranchにMasterBranchの情報を反映させたい
背景
チーム開発において自分の作業ブランチにマスターブランチの情報をもってきたい!!
だけどどうすればいいかわからない、、、そんなときの解決策を今回は紹介します。
以下の4つのコマンド実行するだけです。
コマンドを実行する前の注意点として、編集途中のファイル等がある場合は、一度コミット&プッシュして何もない状態にしておきましょう。
<!-- 開発中ブランチからmasterブランチへ移動 --> $ git checkout master <!-- リモートのmasterを反映 --> $ git pull origin master <!-- masterブランチから開発中のブランチへ移動 --> $ git checkout 開発中のブランチ名 <!-- maserの内容を開発中ブランチに取り込む --> $ git merge origin master以上
- 投稿日:2020-05-14T17:07:52+09:00
devise導入したときのカラムの増やし方
悩み事
devise導入したらデフィルトでemailとpasswordしか入ってないじゃん、、、、
usersテーブルにnameやaddressカラムとかも追加したいのにどうすればいいかわからない!!ってこと一度はありますよね。
今回はそのお悩み解決いたします!!
1.usersテーブルにカラムを追加
今回はnameカラムを追加してみようと思うので、
以下のようにターミナルでコマンドを実行しましょう。$ rails g migration AddNameToUsers name:string $ rails db:migrate2.application_controller.rbを編集
application_controller.rbを以下のように編集しましょう。
これでユーザー登録時にnameカラムが保存されるようになります。app/controllers/application_controller.rbclass ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? protected def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) end end今の状態でuser情報を登録するとnameカラムも追加されてるしいい感じだ!!
と安堵するのはまだ早いのです。。。今のままでは登録時にしかnameカラムが入っていないので、編集でnameを変更しても変更されません。
不完全な状態です。3.完全感覚dreamer
じゃあ編集時に追加したカラムの編集もしっかりとできるようにしましょう!
とはいえとても簡単です。app/controllers/application_controller.rbclass ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? protected def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) <!-- 以下のように編集時の対応する記述も追加してあげましょう --> devise_parameter_sanitizer.permit(:account_update, keys: [:name]) end endこれでバッチリです!!
以上!
- 投稿日:2020-05-14T16:38:03+09:00
【rails】active_hash セレクトボックス作り方
目的
active_hashを使うと
セレクトボックスの選択肢を作る上で…
DBを使わずに、使うことができる。
準備
1.Gemfileに以下を記述する。
gem 'active_hash'下記と合わせてサーバの再起動も行う
$ bundle install準備はこれだけ
実装例
itemsテーブルにregion_idカラムを用意して、itemの登録の時にregion(発送元の地域)をアクティブハッシュから選択する。(下のgifのようなイメージ)
1.早速下記コマンドでitemsテーブルにregion_idカラムを用意
コンソールrails g model Item region_id:integer name:string rake db:migrate2.Active_Hash::Baseを継承しているモデルを作成する。(app/modelsの中にファイル作成)
app/models/region.rbclass Region < ActiveHash::Base include ActiveHash::Associations has_many :items self.data = [ {id: 1, name: '北海道'}, {id: 2, name: '青森県'}, {id: 3, name: '岩手県'}, {id: 4, name: '宮城県'}, {id: 5, name: '秋田県'}, {id: 6, name: '山形県'}, {id: 7, name: '福島県'}, {id: 8, name: '茨城県'}, {id: 9, name: '栃木県'}, {id: 10, name: '群馬県'}, {id: 11, name: '埼玉県'}, {id: 12, name: '千葉県'}, {id: 13, name: '東京都'}, {id: 14, name: '神奈川県'}, {id: 15, name: '新潟県'}, {id: 16, name: '富山県'}, {id: 17, name: '石川県'}, {id: 18, name: '福井県'}, {id: 19, name: '山梨県'}, {id: 20, name: '長野県'}, {id: 21, name: '岐阜県'}, {id: 22, name: '静岡県'}, {id: 23, name: '愛知県'}, {id: 24, name: '三重県'}, {id: 25, name: '滋賀県'}, {id: 26, name: '京都府'}, {id: 27, name: '大阪府'}, {id: 28, name: '兵庫県'}, {id: 29, name: '奈良県'}, {id: 30, name: '和歌山県'}, {id: 31, name: '鳥取県'}, {id: 32, name: '島根県'}, {id: 33, name: '岡山県'}, {id: 34, name: '広島県'}, {id: 35, name: '山口県'}, {id: 36, name: '徳島県'}, {id: 37, name: '香川県'}, {id: 38, name: '愛媛県'}, {id: 39, name: '高知県'}, {id: 40, name: '福岡県'}, {id: 41, name: '佐賀県'}, {id: 42, name: '長崎県'}, {id: 43, name: '熊本県'}, {id: 44, name: '大分県'}, {id: 45, name: '宮崎県'}, {id: 46, name: '鹿児島県'}, {id: 47, name: '沖縄県'} ] end3.Itemモデルに記述
item.rbclass Item < ApplicationRecord # アクティブハッシュ extend ActiveHash::Associations::ActiveRecordExtensions belongs_to_active_hash :region end4.フォーム例(ビュー作成)
_form.html.haml= form_for @item do |f| .new-wrapper__main__title.spacing 発送元の地域 %span.require 必須 = f.collection_select :region_id, Region.all, :id, :name,{prompt: "選択してください"}, {class: "new-wrapper__main__input-select"}参考文献
https://qiita.com/YJ2222/items/c2eaada06c99c051e151
https://qiita.com/Toman1223/items/8633142312bfa886d50b
- 投稿日:2020-05-14T16:20:01+09:00
active_hashの使い方
active_hashとはなんぞや?
Active_Recordのように読み込み専用情報をまとめたハッシュを扱うことができるもの。
こんな時に使いたい!
テーブルを持つほどでもない・ほとんど変更のないようなちょっとした静的データを保持したい!
1.Gemfile
gemfileに以下の記述する。
gem 'active_hash'bundle installも忘れずに!
$ bundle install2.モデルの作成
今回は毎度おなじみの
rails g model
での作成ではなく、
ActiveHash::Baseを継承したモデルを自作する方法で進めていきます。prefecture(都道府県)のモデルを作成していきます。
app/models/prefecture.rbという感じで作ってみましょう!!
prefecture.rbclass Prefecture < ActiveHash::Base self.data = [ {id: 1, name: '北海道'}, {id: 2, name: '青森県'}, {id: 3, name: '岩手県'}, {id: 4, name: '宮城県'}, {id: 5, name: '秋田県'}, {id: 6, name: '山形県'}, {id: 7, name: '福島県'}, {id: 8, name: '茨城県'}, {id: 9, name: '栃木県'}, {id: 10, name: '群馬県'}, {id: 11, name: '埼玉県'}, {id: 12, name: '千葉県'}, {id: 13, name: '東京都'}, {id: 14, name: '神奈川県'}, {id: 15, name: '新潟県'}, {id: 16, name: '富山県'}, {id: 17, name: '石川県'}, {id: 18, name: '福井県'}, {id: 19, name: '山梨県'}, {id: 20, name: '長野県'}, {id: 21, name: '岐阜県'}, {id: 22, name: '静岡県'}, {id: 23, name: '愛知県'}, {id: 24, name: '三重県'}, {id: 25, name: '滋賀県'}, {id: 26, name: '京都府'}, {id: 27, name: '大阪府'}, {id: 28, name: '兵庫県'}, {id: 29, name: '奈良県'}, {id: 30, name: '和歌山県'}, {id: 31, name: '鳥取県'}, {id: 32, name: '島根県'}, {id: 33, name: '岡山県'}, {id: 34, name: '広島県'}, {id: 35, name: '山口県'}, {id: 36, name: '徳島県'}, {id: 37, name: '香川県'}, {id: 38, name: '愛媛県'}, {id: 39, name: '高知県'}, {id: 40, name: '福岡県'}, {id: 41, name: '佐賀県'}, {id: 42, name: '長崎県'}, {id: 43, name: '熊本県'}, {id: 44, name: '大分県'}, {id: 45, name: '宮崎県'}, {id: 46, name: '鹿児島県'}, {id: 47, name: '沖縄県'} ] end3.アソシエーションを組んでいくぅ
今回はフリマアプリだと仮定して、商品の配送先を登録するときに都道府県をactive_hashを使って表示をしたいので、
product.rbにアソシエーションを組んでいきましょう。product.rbclass Product < ApplicationRecord extend ActiveHash::Associations::ActiveRecordExtensions belongs_to_active_hash :prefecture end4.表示していくぅ
商品を登録するフォームなどでcollection_selectを使う際は以下のように表示できます。
= f.collection_select :prefecture_id, Prefecture.all, :id, :name, {prompt:"選択してください"}, {class:""}詳細画面などで登録した情報として表示したいときは以下のように記述すれば表示がうまくいくと思います。
= @product.prefecture.name以上
- 投稿日:2020-05-14T15:48:11+09:00
【Rails】後からカラムを追加して外部キーを張る際に、add_referenceを使う場合の注意点。
始めに
忘れがちになるため、備忘録。
環境
Ruby 2.6.5
Rails 5.2.4.2アソシエーション対象
投稿用のpostsテーブルと、ユーザーのusersテーブルを関連付け。
postsテーブルに外部キー制約を張ったuser_idカラムを追加するカラム追加用のマイグレーションを作成
1.マイグレーション作成
$ rails g migration AddUserIdToPosts2.マイグレーションファイルへの記述。foreign_key: trueを忘れずに記述すること。
db/migrate/[timestamps]_add_user_id_to_posts.rbclass AddUserIdToPosts < ActiveRecord::Migration[5.2] def change add_reference :posts, :user, foreign_key: true end end※上手く外部キー制約が追加されない記述。
db/migrate/[timestamps]_add_user_id_to_posts.rbclass AddUserIdToPosts < ActiveRecord::Migration[5.2] def change add_reference :posts, :user, index: true end end3.マイグレーションファイルの保存
$ rails db:migrate
何故、foreign_keyが必要なのか
Ruby on Rails APIより、
add_reference(table_name, ref_name, **options)
:foreign_key
Add an appropriate foreign key constraint. Defaults to false.Create a supplier_id column and appropriate foreign key
add_reference(:products, :supplier, foreign_key: true)外部キー制約を張るためのforeign_keyはデフォルトではfalseになるため、ただ単にadd_referenceカラムを追加しただけでは外部キー制約は張られることがないとのこと。
つまり、「user.posts」のようにアソシエーションを利用してオブジェクトを取得しようとしても、外部キーが存在しないためエラーになるので注意が必要。
参考
Railsで外部キー制約のついたカラムを作る時のmigrationの書き方
後からreferencesカラムを追加しようとするとdb:migrateできない
- 投稿日:2020-05-14T15:48:11+09:00
【Rails】add_referenceで、カラムへ後から外部キー制約を追加する場合の注意点。
始めに
忘れがちになるため、備忘録。
アソシエーション対象
投稿用のpostsテーブルと、ユーザーのusersテーブルを関連付け。
postsテーブルに外部キー制約を張ったuser_idカラムを追加するカラム追加用のマイグレーションを作成
1.マイグレーション作成
$ rails g migration AddUserIdToPosts2.マイグレーションファイルへの記述
db/migrate/[timestamps]_add_user_id_to_posts.rbclass AddUserIdToPosts < ActiveRecord::Migration[5.2] def change add_reference :posts, :user, foreign_key: true end end※上手く外部キー制約が追加されない記述。
db/migrate/[timestamps]_add_user_id_to_posts.rbclass AddUserIdToPosts < ActiveRecord::Migration[5.2] def change add_reference :posts, :user, index: true end end3.マイグレーションファイルの保存
$ rails db:migrate
何故、foreign_keyが必要なのか
Ruby on Rails APIより、
add_reference(table_name, ref_name, **options)
:foreign_key
Add an appropriate foreign key constraint. Defaults to false.Create a supplier_id column and appropriate foreign key
add_reference(:products, :supplier, foreign_key: true)外部キー制約を張るためのforeign_keyはデフォルトではfalseになるため、ただ単にadd_referenceカラムを追加しただけでは外部キー制約は張られることがないとのこと。
つまり、「user.posts」のようにアソシエーションを利用してオブジェクトを取得しようとしても、外部キーが存在しないためエラーになるので注意が必要。
参考
Railsで外部キー制約のついたカラムを作る時のmigrationの書き方
後からreferencesカラムを追加しようとするとdb:migrateできない
- 投稿日:2020-05-14T15:45:03+09:00
Gemを利用しないでAPIを扱ってみよう
楽天商品検索APIを使ってみよう
まず企業や団体が提供しているAPIを利用するためには、大抵の場合そのサービスへの利用申請が必要になります。これはAPIを利用する時にパスワードとなるアプリケーションIDを発行してもらうためです。
アプリケーションIDを取得したら、それを含めたリクエストを送るコードを書き、実行します。
すると返り値に、各APIが処理した後の値が帰ってきます。この値のデータ型は楽天商品検索APIではJSONというものになっています。なのでこれをRubyで扱えるようにするために、ハッシュへと変換します。
その後、ハッシュの値を出力するなどのコードを書きます。
大まかな流れは以下のようになります
1. 楽天APIを使うためのアプリ登録をし,アプリケーションIDを取得する
2. RubyでWeb APIへリクエストするコードを書く
3. JSONというデータ型で返って来た値をRubyのハッシュに変換するコードを書く楽天APIを使うためのアプリ登録をし,アプリケーションIDを取得する
以下のページからアプリケーションIDを取得することができます。
Rakuten Web Serviceここでは
- アプリ名
- アプリURL
を記入する必要がありますが、今は存在しないもので大丈夫です。
これでアプリIDを取得できたと思います。
RubyでWeb APIへリクエストするコードを書く
まずAPIを使いたいアプリのあるディレクトリに、Rubyファイルを作成しましょう。
$ touch rakuten_api.rb続いて、先ほど作成したアプリIDを.bash_profileに環境変数として書き込みます
環境変数とは
環境変数は、サーバーに直接登録して参照できる変数のことです。外部に漏らしたくないパスワードなどの情報(今回でいうアプリID)を、直接環境下に置くことで、外部から不正に利用されることを防ぎます。
1. bash_profileに環境変数を書き込む
ターミナル$ sudo vi ~/.bash_profile2. 「i」を入力して、入力モードにする
3. アプリケーションIDを記述する
.bash_profileexport RAKUTEN_APP_ID='取得したアプリIDを記述'4. 記述が終わったら、「esc」キーを押してから「:wq」「エンター」で保存します。
5. 先ほどの記述を読み込ませます。
ターミナル$ source ~/.bash_profile次にAPIを利用するためのコードを書きます。
以下のコードは先ほど作成した、rakuten_api.rbに記述します。
全体のコードは以下のようです。
rakuten_api.rbrequire 'net/http' require 'uri' require 'json' puts "検索したい文言を入力してください!" search_word = gets.chomp appid = ENV["RAKUTEN_APP_ID"] url = URI.parse(URI.escape("https://app.rakuten.co.jp/services/api/IchibaItem/Search/20140222?applicationId=#{appid}&keyword=#{search_word}")) res = Net::HTTP.start(url.host, url.port, use_ssl: true){|http| http.get(url.path + "?" + url.query); } obj = JSON.parse(res.body) puts obj細かい操作についてみていきます。
まず標準ライブラリを呼び出す
rubyには組み込みライブラリとは別に、標準ライブラリというのがあります。組み込みライブラリはファイルに特別な記述をしなくても最初から利用できます。ところが、組み込みライブラリには含まれていない機能を使う際には標準ライブラリを使います。http通信を扱ったり、CSVファイルの操作をしたりするライブラリが存在します。
(今回はGemを使わないでAPIを利用するので、この操作が必要となります。もしGemを使うのであれば、必要な標準ライブラリも自動で呼び出されるので必要ないです。)
標準ライブラリは、Gemを利用するのと同様にrequire ◯◯という記述によって呼び出します。
以下のコードは net/http と uri という標準ライブラリを設定しています。rakuten_api.rbrequire 'net/http' require 'uri' require 'json'これらは、必要な標準ライブラリを呼び出すための記述です。
net/http はhttp通信をRubyで扱うための標準ライブラリです。。これによって、今回使うNet::httpクラスとstartメソッドが利用できるようになります。そして、Webサーバーからドキュメントを得たりフォームの情報を送信したりすることがRubyのプログラムでできます。
uri はURLを扱うための標準ライブラリです。URIとはURLの広義的なものです。これによって、簡単にURIのドメインなどを取り出すことができます。
3行目で、JSONファイルを読み込めるようにします。
次に具体的な操作をみていきます。
楽天市場から商品を検索する
rakuten_api.rbappid = ENV["RAKUTEN_APP_ID"] url = URI.parse(URI.escape("https://app.rakuten.co.jp/services/api/IchibaItem/Search/20140222?applicationId=#{appid}&keyword=#{search_word}")) res = Net::HTTP.start(url.host, url.port, use_ssl: true){|http| http.get(url.path + "?" + url.query); }まず環境変数として.bash_profileに書き込んだものを一度変数に代入しています。
次にuri環境ライブラリを読み込んだことによって利用できるようになったURIクラスの
parseメソッドを使って、URIのドメインを取り出します。ここで大切なのはhtttps://app.‥
の先にある、?以降の部分です。これはクエリパラメータと言います。
クエリパラメータとはさまざまな情報をWebサーバーに伝えるためにURLに付け加える情報のことです。クエリパラメータの基本構造は以下のようです。
?パラメータ名=パラメータの値パラメータは1つとは限らず、複数ある場合は&で繋いでいきます。
今回だと applicationId と keyword という2つにそれぞれアプリIDや検索ワードを代入しています。
またparseメソッドの使い方は以下のようです。
require 'uri' uri = URI.parse("https://www.google.co.jp/")例えば、上記のように実行すると以下のようにhttpの構成要素を分解してくれます。
uri.scheme => "https"
uri.host => "www.google.co.jp"
uri.port => "443"
uri.path => "/"この操作によって元のコードの次の行では
NET::HTTPクラスのstartメソッドを利用し、
引数にhostやportを指定して簡単にドメイン取得できています。ここまでで楽天市場から商品を検索することができました。
しかしまだ検索結果は、11行目の変数resにJSONというデータ形式で代入されています。
JSONをハッシュに変換する
rakuten_api.rbobj = JSON.parse(res.body) puts JSON.pretty_generate(obj)15行目では、JSONクラスのparseメソッドを利用してJSON形式のデータをRubyのハッシュクラスのインスタンスに変換しています。つまり、変数objはハッシュになっている、ということです。あとは、このハッシュから必要な情報を取り出して利用するだけです。
- 投稿日:2020-05-14T15:37:51+09:00
サジェスト機能を実装
サジェスト機能を実装
機能を追加したので学んだことをoutputしておく。
理解が深まればもう少しわかりやすく改善していく予定。実装内容
以下の写真のように文字を入力すると候補が表示されるようにしたい。
まず
gem 'jquery-ui-rails'
をGemfileに記載し、
bundle install
を実行
jQuery UIは、jQueryを拡張するライブラリ(プラグイン)の一種
これにより「autocomplete」が使えるようになり、候補がリストで表示される。早速、
search.rb
を作成し、以下のように配置する。(今回は新たに作成したが既存のものに書いても良い。)
軽く説明
1行目はjQuery記載のためのテンプレ
9行目で以下のようなhtmlファイルに記載されたid="suggest"のiuputにユーザーが入力した時に「autocomplete」が反応する。<input type="text" id="suggest">
source:data
によりデータの元を定義する
今回であればl2~l9で定義されたdataを呼び出しユーザーが入力した値に対して該当する値が検出される。
autoFocus:true
このページの最初の写真のように、「a」を入力するとリストが表示されて、「apple」の部分背景がgrayになっている。trueをfalseに変えると背景はgrayにならない。
delay:300
入力してから0.3秒後に表示される。
minLength:1
1文字以上入力された時に実行される。参考サイト
- 投稿日:2020-05-14T15:29:17+09:00
数学クイズの傑作「破れたページの少女」を、Rubyで解いてみた
問題「破れたページの少女」
1枚だけページが破れた本がある。
破れていないページ番号を合計すると15000になる。
破れたページは何ページ目だろうか?ある時SNSで見かけて知った数学の問題なのですが、なかなかスマートな問題ですよね。良い具合に引っ掛け要素があります。以下リンクでも詳しく解説されているので、こちらも御覧になってみてください。数学が好きな方は最初に是非解いてみてください^^
「これ、合計が15000以外だとどんな解が得られるのかな」…とふと思いたち、この問題をrubyで解けるようにしてみました。
(バージョン:ruby 2.5.1)仕様
破れた本の、ページ番号の合計を入力(上記の問題通り実行する場合は15000を入力)すると
↓
この本の元々のページ数と、破られたページを求め、出力する入力例破かれた本の、ページ数を全て足した合計を入力してください。 合計は? : ◯◯◯ ←ここに入力
出力例この本のページ数は、△△△ページです。 破かれたのは、□□ページ目です(同時に、◇◇ページ目も失われています)。
例外処理(解が得られなかった場合)入力した合計値の条件では、解が存在しませんでした。
という結果が得られるように、コードを書いてみたいと思います。
考え方
前提として与えられているのは、破られた本の、ページ数を全て足した合計値のみ。元々の本の総ページ数も当然分からない状態です。なのでシンプルに、元々の本のページ数が1ページだとしたら → 2ページだとしたら → … という具合に、順に検証してみようかと思います。
以下が、コード内で用意した主な変数です。
final_sum
… 最初に入力する合計値(問題文では15000)を入れる変数。book
… 本の本体。配列形式で、ページを1枚ずつ追加していきます。例)[1, 2, 3, 4, … ]
pages_sum
… 破れていない場合の、bookのページ番号の合計値を入れる変数。torn_page_list
… 破くことが可能なページ全てのパターンを配列形式で追加していきます。 例)[[1, 2],[3, 4],[5, 6], … ]
例えば3ページ目を破ると、その裏の4ページ目も同時に失うことになります。そのため
torn_page_list
には[x-1, x]
というふうに格納していきます。且つ、[奇数, 偶数]
の形でなければいけません。左右逆の[偶数, 奇数]
だと、見開きページってことになってしまう(1枚だけ破くという前提が崩れてしまいNGとなる)為です。
book
にページを1枚ずつ追加していきながら…
本のページ番号合計 - 破いて無くなるページ合計 == 最初に入力した数値(問題文では15000)
つまり、
pages_sum - torn_page_list[??].sum == final_sum
を満たすものを探し当てれば、ゴールです!作成したコード(※まだ未完成)
sample.rb(コメント付き)puts "破かれた本の、ページ数を全て足した合計を入力してください。" print "合計は? :" final_sum = gets.to_i # ↓ bookには、1ページずつ配列で格納していきます。 例) [1, 2, 3, .. ] book = [] # ↓ pages_sumには、本のページ数を足していきます。 pages_sum = 0 # ↓ torn_page_listには、破ることが可能な全てのページを、パターンとして配列で格納していきます。 例) [[1, 2], [3,4], .. ] torn_page_list = [] # ↓ インデックス値を用意 i = 1 # ↓ 以下のwhile文によって、条件を満たすケースが無いかを探り、無い場合はページを一枚ずつ追加し再検証する。 # ↓ なぜ final_sum + 1 なのかというと、「これ以上やっても、もう条件を満たすケースは無いよ!」というページ上限が、 final_sum + 1 の為。 while i <= final_sum + 1 do # ↓ 破るページを全ケース試して、解の条件を満たすケースが無いかを検証。 lost_page_is = torn_page_list.find { |lost| pages_sum - lost.sum == final_sum } # ↓ 条件を満たすケースが見つかったら、while文をbreak(強制終了)する! break if lost_page_is != nil # ↓ 満たさなかった(上記でbreakしなかった)場合は、ページを増やし再検証する。 book << i pages_sum += i if book.last.even? torn_page_list << [i - 1, i] end i += 1 end # 結果を出力 if lost_page_is != nil puts "この本のページ数は、#{book.length}ページです。" puts "破かれたのは、#{lost_page_is[0]}ページ目です(同時に、#{lost_page_is[1]}ページ目も失われています)。" else puts "入力した合計値の条件では、解が存在しませんでした。" end↓ コメントアウト無しバージョン ↓
sample.rb(コメントアウト無しバージョン)puts "破かれた本の、ページ数を全て足した合計を入力してください。" print "合計は? :" final_sum = gets.to_i book = [] pages_sum = 0 torn_page_list = [] i = 1 while i <= final_sum + 1 do lost_page_is = torn_page_list.find { |lost| pages_sum - lost.sum == final_sum } break if lost_page_is != nil book << i pages_sum += i if book.last.even? torn_page_list << [i - 1, i] end i += 1 end if lost_page_is != nil puts "この本のページ数は、#{book.length}ページです。" puts "破かれたのは、#{lost_page_is[0]}ページ目です(同時に、#{lost_page_is[1]}ページ目も失われています)。" else puts "入力した合計値の条件では、解が存在しませんでした。" end結果
以下の通り、期待する結果を得ることが出来ました。
結果1破かれた本の、ページ数を全て足した合計を入力してください。 合計は? : 15000 ↓ この本のページ数は、173ページです。 破かれたのは、25ページ目です(同時に、26ページ目も失われています)。
結果2破かれた本の、ページ数を全て足した合計を入力してください。 合計は? : 3 ↓ この本のページ数は、3ページです。 破かれたのは、1ページ目です(同時に、2ページ目も失われています)。
結果3破かれた本の、ページ数を全て足した合計を入力してください。 合計は? : 12345 ↓ 入力した合計値の条件では、解が存在しませんでした。
このコードの課題
上記で無事に解を得ることは出来たものの、解が存在しなかった場合の処理速度に課題を感じました。
アルゴリズムが持つべき性質…
有限性:アルゴリズムは終了しなければならない
明確性:アルゴリズムは実行する際の条件によって変動しないようにしなけらばならない
理解性:アルゴリズムはわかりやすいものでなければならない
高速性:アルゴリズムの処理は効率的でなければならない ←これ上記が発生している理由は、
while i <= final_sum + 1 do 〜 end
の部分。あまりにも無駄な回数の繰り返し処理をさせていました。「これ以上繰り返しても無駄だよ」の境界値は、もっと厳密に定義しないといけません。そこで考えたのが以下の表のイメージです。
本のページ数 破いた際の最小合計値(最後のページを破いた場合) 破いた際の最大合計値(1ページ目を破いた場合) 1ページ - - 2ページ 0 0 3ページ 1 3 4ページ 3 7 5ページ 6 12 nページ book.sum-(book[n-1]+book[n]) book.sum-(book[0]+book[1]) "本を破いた際に得られる合計値"には、上記の通り得られる数値の範囲が決まっています(4ページある本なら、破いた後の合計値の取りうる範囲は、表の通り3〜7の間となる)。なので、その範囲を超えてまで、繰り返し処理をする必要は無さそうだ…ということが分かります。
改善
というわけで、while文の条件を以下のように書き換えようと思います。
sample.rb# while i <= final_sum + 1 do 〜 end ←これでは無駄に繰り返してしまうので、以下に変更 while pages_sum <= final_sum + (2 * i) - 1 do上記のようにすると無駄な繰り返し処理がなくなり、例外処理も高速で処理出来るようになりました(元々がひどかっただけですがw)。
ちなみに別コードで検証してみた結果、どのような場合でも解は必ず1つのみになるようです。当たり前と言えば当たり前かもしれませんが、数学得意ではないので証明は出来ず…。解は一つという事実から、今回のように
find
メソッドを用いることが出来ます(以下13行目)。完成コード
sample.rb(完成バージョン)puts "破かれた本の、ページ数を全て足した合計を入力してください。" print "合計は? :" final_sum = gets.to_i book = [] pages_sum = 0 torn_page_list = [] i = 1 # -- 修正箇所 -- while pages_sum <= final_sum + (2 * i) - 1 do # -- -- -- -- lost_page_is = torn_page_list.find { |lost| pages_sum - lost.sum == final_sum } break if lost_page_is != nil book << i pages_sum += i if book.last.even? torn_page_list << [i - 1, i] end i += 1 end if lost_page_is != nil puts "この本のページ数は、#{book.length}ページです。" puts "破かれたのは、#{lost_page_is[0]}ページ目です(同時に、#{lost_page_is[1]}ページ目も失われています)。" else puts "入力した合計値の条件では、解が存在しませんでした。" end以上、"「破れたページの少女」を、Rubyで解いてみた"でした。ご覧いただきありがとうございました!
- 投稿日:2020-05-14T15:19:48+09:00
『TECH CAMP』2ヶ月通ってできるようになったこと
はじめに
TECH CAMPに通って2ヶ月でできるようになったことをなんとなくまとめてみようと思います!
1
言語としてはRubyとjsフレームワークとライブラリでRailsとjqueryを使えばクオリティは低いがtwitter、インスタ、メルカリのようなwebサイトは作れるようになる(本物には遠く及ばない)
2
プログラミング学習サイトpaizaのBランク問題(選りすぐりの簡単な物)が2,3問解けるようになる。
文字列を複数形にみたいな問題はとっっても簡単なのでBランクまであげたい方にはおすすめです。3
webサイトの模写だったらできるようになる(もちろんクオリティは低い)
だが、デザインのセンスが身につくわけではないので個人アプリを1からデザインしてUIに優れたサイトを作るのは厳しい。デザインの才能が高い方は例外です。まーでもほとんどの方が自分で「これはいい!!最高!!」っていうUIを備えたサイトを作るのは厳しいと思います。
4
実装方法がネット上に調べて出てくるものであればある程度マネができるようになる。(簡単なゲームなども)
まとめ
大前提としてプログラミングスクールに通ったからといってプログラマーの実務で使うような技術はつかない。
スクレイピングやクローラーの技術は全くつかないしテストコードすらまともに書けないのでこのままではプログラマーとは到底言えない。
答えに近いものがネット上にない場合は自分じゃ作れない。
0から1を作るなんて言えるほど技術はない。
僕自身プログラミングスクールに通えばもっと実践的な技術がつくと絵空事を浮かべてましたが実際短期間で得れる技術なんてこんなもんです。むしろすごく成長させてくれたと思います
僕は燃えています。自分の未知の世界ってすごい楽しい。今はペーペーだけど5年後、10年後に優秀なプログラマーになれるようにプログラムと日々遊んで行こうと思います。
OSSにプルリクエスト出してマージされる日がきたらいいな
その前に英語読めるようにならないとまずいか笑最後に
TECH CAMPがダメって言いたいわけじゃないです!チーム開発や勉強に励みやすい環境(一週間ちょっとくらいしか行ってないが笑)そして学習に一番大切な「きっかけ」を与えてくれる最高のスクールです!!!!
それを踏まえた上でそんなに素晴らしいスクールに通っても自分の思い浮かぶキラキラエンジニアになれるわけではないよというのを伝えたかったです!
キャラでもなくこんな記事書くようになるなんてプログラミングめっちゃ好きなんだなって感じて嬉しいですね
僕の友達がこんな記事見たらめちゃめちゃ笑われるんだろうな笑こんなくだらない記事を最後までご閲覧いただきありがとうございました
- 投稿日:2020-05-14T15:04:55+09:00
Railsプロジェクトの *_spec.rb の中で xmpfilter を使う方法
要点
rspec/autorun
を読み込むことで rspec コマンドで実行したのと同じ状況にするrequire "rails_helper"
などが失敗しないように-I spec
を指定- カレントディレクトリを Rails.root に固定するため
--cd
に .git のあるディレクトリを指定例
(defun rubyxmp () (interactive) (let ((s "xmpfilter --dev --fork --no-warnings --poetry") (rspec-p (string-match "_spec" (buffer-file-name))) (git-root (locate-dominating-file default-directory ".git")) xmpfilter-command-name) (when rspec-p (setq s (concat s " -r rspec/autorun -I spec")) (when git-root (setq s (concat s " --cd " git-root)))) (setq xmpfilter-command-name s) (call-interactively 'xmp)))
- 投稿日:2020-05-14T14:49:16+09:00
メソッド定義の中におけるselfについて
selfがよくわからんので学んだことのメモを。
※※ここではメソッド定義の中におけるselfの話をさします
※※クラスメソッドのselfなどは無視しますselfはメソッドの中で使えるオブジェクト。
selfはそのメソッドをうけとっているインスタンス自身を指す。例
class User attr_accessor :name # setter: name=(value) # getter: name def initialize(name) @name=name end def sayhi # self puts "hi.#{@name}" end end user=User.new("bob") user.sayhi # userがselfにあたり、レシーバーと呼ぶこともある上記でコメントにしているselfはUser.newによって生成されたオブジェクトbobを指す。
続いてselfを実際につかう場合、、
class User attr_accessor :name # setter: name=(value) # getter: name def initialize(name) @name=name end def sayhi # self puts "hi.#{@name}" # 追記箇所 puts "hi.#{self.name}" end end user=User.new("bob") user.sayhi # userがselfにあたり、レシーバーと呼ぶこともあるコンソールで吐き出すと
ec2-user:~/environment/ruby_lessons $ ruby hello.rb hi.bob hi.bobと、なる。
selfが使えるのは
「getterが設定されてるからself.nameにすると、結果としてインスタンス変数が取得できて@nameがかえってくる」ということらしい。
なのでためしにattr_accessorをコメントアウトしてみると
class User # attr_accessor :name コメントアウトに # setter: name=(value) # getter: name def initialize(name) @name=name end def sayhi # self # self.name -> @name(getterが設定されてるから) puts "hi.#{@name}" puts "hi.#{self.name}" # puts "hi.#{name}" # ややこしくならない場合は省略できる end end user=User.new("bob") user.sayhiコンソールで吐き出すと
ec2-user:~/environment/ruby_lessons $ ruby hello.rb hi.bob Traceback (most recent call last): 1: from hello.rb:118:in `<main>' hello.rb:111:in `sayhi': undefined method `name' for #<User:0x000000000256fef8 @name="bob"> (NoMethodError) ec2-user:~/environment/ruby_lessons $と、なselfのほうがエラーとなる。
要するに、selfはattr_accessor(厳密にはgetter)とセットってことですかね。
同じ事例で分かりやすい説明を発見しました。
- 投稿日:2020-05-14T14:46:02+09:00
gemのancestoryを使用した多層階構造の実装
はじめに
今回はフリマアプリでよく見かける、カテゴリの多層階構造についての実装手順を紹介していきます。
完成図
やりたいこと
カテゴリを親、子、孫の三階層に分けて登録、表示する。
1.モデルの準備
今回実装したいこととして第一に商品とカテゴリを紐付けたいので、categoryとitemのモデルを作成します。
$ rails g model category $ rails g model item2.ancestoryの導入
Gemfileにancestoryを導入します。
gem 'ancestry'ターミナルで下記コマンドを実行しよう。
$ bundle install $ rails g migration add_ancestry_to_category ancestry:string:index $ rails db:migrateancestoryを使うことで多対多の関係が1対多の関係になりDB設計がシンプルになります。
category.rbにhas_ancestoryを入れてあげましょう。category.rbclass Category < ApplicationRecord has_many :items has_ancestry enditem.rbclass Item < ApplicationRecord belongs_to :category end3.データの導入
seeds.rbにカテゴリのレコードを導入していきます。
seeds.rblady = Category.create(name: "レディース") lady_1 = lady.children.create(name: "トップス") lady_1.children.create([{name: "Tシャツ/カットソー(半袖/袖なし)"},{name: "Tシャツ/カットソー(七分/長袖)"},{name: "シャツ/ブラウス(半袖/袖なし)"},{name: "シャツ/ブラウス(七分/長袖)"},{name: "ポロシャツ"},{name: "キャミソール"},{name: "タンクトップ"},{name: "ホルターネック"},{name: "ニット/セーター"},{name: "チュニック"},{name: "カーディガン/ボレロ"},{name: "アンサンブル"},{name: "ベスト/ジレ"},{name: "パーカー"},{name: "トレーナー/スウェット"},{name: "ベアトップ/チューブトップ"},{name: "ジャージ"},{name: "その他"}])ancestryを入れるとchildrenと記述することで直前の変数の子要素として扱うことができます。
細かく説明するとまず、以下の記述で親カテゴリであるレディースが登録されます。seeds.rblady = Category.create(name: "レディース")次に、以下の記述で子カテゴリであるトップスが登録されます。
seeds.rblady_1 = lady.children.create(name: "トップス")そして、以下の記述で子カテゴリの孫カテゴリ群が登録されます。
seeds.rblady_1.children.create([{name: "Tシャツ/カットソー(半袖/袖なし)"},{name: "Tシャツ/カットソー(七分/長袖)"},{name: "シャツ/ブラウス(半袖/袖なし)"},{name: "シャツ/ブラウス(七分/長袖)"},{name: "ポロシャツ"},{name: "キャミソール"},{name: "タンクトップ"},{name: "ホルターネック"},{name: "ニット/セーター"},{name: "チュニック"},{name: "カーディガン/ボレロ"},{name: "アンサンブル"},{name: "ベスト/ジレ"},{name: "パーカー"},{name: "トレーナー/スウェット"},{name: "ベアトップ/チューブトップ"},{name: "ジャージ"},{name: "その他"}])seeds.rb内にレコードを記述し終わったら、ターミナルで以下のコマンドを実行し、DBに反映させましょう。
$ rails db:seedDBではこのように登録されます。
まとめ
ancestoryを使用すればアソシエーションの関係性もシンプルになるのでかなりオススメです!!!
参考記事
- 投稿日:2020-05-14T14:45:57+09:00
Rails6環境構築時に発生した"webpacker"と"yarn"の未導入が原因のエラーとその解決方法
はじめに
rails tutorial(第6.0版)を参考にrails6の環境を構築したところ、rails server立上げの際にエラーが発生しました。
原因は"webpacker"と"yarn"の未導入でした。
今後のために、エラー内容と"webpacker"と"yarn"の導入方法を備忘録として残します。
参考記事
Rails6 開発時につまづきそうな webpacker, yarn 関係のエラーと解決方法
Rails6 Webpackerでエラーが出た
Ubuntu18.04LTSにlinuxbrew(linux版homebrew)を入れる開発環境
Cloud9 (ubuntu server)
Ruby 2.6.3
rails 6.0.0エラー発生までの経緯
rails tutorial(第6.0版)第1章を参考にrails6の開発環境を構築開始。
1.3.1までチュートリアル通り実施し、rails severコマンドを入力すると、{略} rescue in load': Webpacker configuration file not found /home/ubuntu/environment/hello_app/config/webpacker.yml. Please run rails webpacker:install \ Error: No such file or directory @ rb_sysopen - /home/ubuntu/environment/hello_app/config/webpacker.yml (RuntimeError)上記エラーが表示されました。
エラーの内容
/hello_app/config/ 内に"webpacker.yml"が存在しないから、webpackerをインストールしなさい。・・・と理解しました。
webpackerとyarnのインストール
webpacker導入コマンド
$ rails webpacker:installYarn not installed. Please download and install Yarn from https://yarnpkg.com/lang/en/docs/install/yarnがないと返事が来た場合。
yarn導入コマンド
$ brew install yarnCommand 'brew' not found, but can be installed with:上記の返事が来た場合。
$ sudo apt install linuxbrew-wrapperyarnとwebpackerを再度導入する。
- 投稿日:2020-05-14T14:07:22+09:00
attr_accessorメソッドについて
よくわかってないattr_accessorに関して学んだことのメモ。
以下、今回のサンプルコードです。
※attr_accessorはコメントにしてますclass User # attr_accessor :name # setter: name=(value) # getter: name def initialize(name) @name=name end def sayhi puts "hi.#{@name}" end end user=User.new("bob") user.sayhiまず、自分で定義するインスタンス変数(@name)に関しては外部からアクセスすることはできない。
ここでいう外部と内部ですが、アクセスできる範囲としては
サンプルコードでいうと、class User # attr_accessor :name # setter: name=(value) # getter: name def initialize(name) @name=name end def sayhi puts "hi.#{@name}" end endということになるかと思います。
そして、newでインスタンスを作成し、sayhiメソッドを呼び出した場合、
sayhiメソッドが該当エリア内なので問題なく使えている、ということになります。上記サンプルコードをコンソールで吐き出すと
ec2-user:~/environment/ruby_lessons $ ruby hello.rb hi.bobとなる。
続いてサンプルコードに、「外部」からインスタンス変数にアクセスしようとするコードを追記。
class User # attr_accessor :name # setter: name=(value) # getter: name def initialize(name) @name=name end def sayhi puts "hi.#{@name}" end end user=User.new("bob") user.sayhi # 追記箇所(attr_accessorメソッドはコメントのママ) user.name ="bob.jr" p user.nameこれをコンソールで吐き出すと、、、
ec2-user:~/environment/ruby_lessons $ ruby hello.rb hi.bob Traceback (most recent call last): hello.rb:89:in `<main>': undefined method `name=' for #<User:0x0000000002973f40 @name="bob"> (NoMethodError)エラーがでます。
ただ、これができるようになるattr_accessorメソッドのコメントをとると、、、class User attr_accessor :name # setter: name=(value) # getter: name def initialize(name) @name=name end def sayhi puts "hi.#{@name}" end end user=User.new("bob") user.sayhi # 追記箇所(attr_accessorメソッドはコメントをはずす) user.name ="bob.jr" p user.nameこうなり、コンソールで吐き出すと
ec2-user:~/environment/ruby_lessons $ ruby hello.rb hi.bob "bob.jr"見事に書き換え&表示に成功。
念のため、書き換え前の出力も追加してみる。
class User attr_accessor :name # setter: name=(value) # getter: name def initialize(name) @name=name end def sayhi puts "hi.#{@name}" end end user=User.new("bob") user.sayhi # 追記箇所 p user.name user.name ="bob.jr" p user.nameすると
ec2-user:~/environment/ruby_lessons $ ruby hello.rb hi.bob "bob" "bob.jr"いい感じ。
上書きされた状態でメソッドを呼び出すコードを追加。
class User attr_accessor :name # setter: name=(value) # getter: name def initialize(name) @name=name end def sayhi puts "hi.#{@name}" end end user=User.new("bob") user.sayhi p user.name user.name ="bob.jr" p user.name # 追加部分 user.sayhiすると
ec2-user:~/environment/ruby_lessons $ ruby hello.rb hi.bob "bob" "bob.jr" hi.bob.jr上書きされた形でメソッドを呼び出せた!
こんな感じで「外部」からインスタンス変数の値にアクセスしたい場合に
attr_accessorメソッドを用いるとよい、らしい。
- 投稿日:2020-05-14T13:53:45+09:00
Railsをやって、最近まなんだ事をまとめてみる
はじめに
自分が最近Railsで、制作していて忘れそうなことをメモ感覚で残したものなので、
頭おかしいこと書いてあるかもしれないのでご了承ください。STI(単一テーブル継承)
1つのテーブルを継承したクラスを作成することができる。
継承することによって、子クラスは親クラスと同じカラムを使うことができるので、似たようなクラスを作って増やしたいときに、カラムの定義を1つずつ行わなくてよくなるので、手間も少なくなるので便利です。カラムを追加することもできます。※ 親クラスには、
string型
のtype
をカラムとして追加することを忘れないように、気を付けましょう!モデルの関連付けについて
1対1とか、1対多などの関係定義などを行うときに、いろんなファイル内に設定を書き込まないといけないので、結構大変です。
簡単に、こんな時にこう書く的なのをまとめます。
- 1対1のつながり(従属)
belongs_to
を従属するモデルに書く- 1対1のつながり(含む、所有など)
has_one
を所有するモデルに書く- 1対多のつながり
has_many
を所有するモデルに書く- 従属する(マイグレーションファイル)
references :従属されるモデル(単数形)
を従属する側のモデルのマイグレーションファイルに書く大体こんな感じだと思います。
Railsガイド- Active Recordの関連付けバリデーションなど
よく使うやつと個人的に思うものをまとめます。
バリデーションで、データを確認することにより、保存したいデータのみを保存することができる。
presence
- 指定した値が空かどうかを確認
format
- 正規表現などを利用して、データの内容(文字など)を検証する
length
- 長さを検証
maximum
: 最大値を指定minimum
: 最小値を指定in
: 値の範囲を指定できる(以上、以下の指定ができる)is
: 等しくないといけないように指定できる最後に
Railsで開発するときに、すごい悩まされたのがデータベース設計でした。今までは、本に書いてあることをそのまま作ればよいだけでしたが、自分で開発する際には、データベースのカラムに何が必要か、どんな制約を付けるか。などとても悩む部分が多かったです。でも、いろんなことにチャレンジすることはいつか自分のためになると思うので、皆さんも頑張ってください!!
- 投稿日:2020-05-14T12:18:21+09:00
【Ruby】引数のデフォルト値設定の書き方について
はじめに
デフォルト値ってこんな感じだったよな、、
class ApiSuggest def self.suggest(keyword, suggest_max_count = 99)#ここ Product::Suggest.where('keyword like ?', "#{keyword}%").limit(suggest_max_count).pluck(:keyword) end endってすると。
rubocopから軽く怒られた。のでその対処について。怒られ方
Do not use default positional arguments. Use keyword arguments or an options hash instead.Airbnb/OptArgParameters(RuboCop)
デフォルトの位置引数は使用しないでください。代わりにキーワード引数やオプションハッシュを使用してください。
ここで’キーワード’が出てきてなんとなく’:’を使うのかなと思った。
更に、上の英語で検索する。
と、こちらがヒットし、大変参考になった。こういった細かいところも、自然と正しくかけるように継続してrubocopの指摘に注目していきたい。
終わりに。
最後まで読んで頂きありがとうございます
転職の為、未経験の状態からRailsを学習しております。正しい知識を着実に身に着け、実力のあるエンジニアになりたいと考えています。継続して投稿していく中で、その為のインプットも必然的に増え、成長に繋がるかと考えています。
今現在、初心者だからといって言い訳はできないですが、投稿の内容に間違っているところや、付け加えるべきところが多々あるかと思いますので、ご指摘頂けると幸いです。この記事を読んで下さりありがとうございました。