参照元はMitch Crowe氏のこちらのエントリー。時期にして3年以上前の更新になりますが、未だに参照することが多いものをこの際敬意を込めてまとめることにしました。
1. first_or_create(ブロック付き)
任意のレコードを新規保存する際に、既に同じ情報を持ったレコードがある場合被らせたくない!というニーズにクリティカルに答えてくれるのがfirst_or_create
メソッド。where
メソッドと併用することで、検索条件に合致するレコードが存在する場合にはそのレコードを参照し、無ければ検索条件の内容で新しいレコードを新規保存してくれます。返り値は参照したレコードもしくは新規作成したレコードです。
Book.where(title: 'Frozen').first_or_create
# Book Load (1.7ms) SELECT `books`.* FROM `books` WHERE `books`.`title` = 'Frozen' ORDER BY `books`.`id` ASC LIMIT 1
## 該当するデータが存在しない場合のみ検索条件の内容で新規レコードを作成
# (0.1ms) BEGIN
# SQL(29.9ms) INSERT INTO `books` (`created_at`, `title`, `updated_at`) VALUES ('2015-06-26 15:58:59', 'Frozen', '2015-06-26 15:58:59')
# (1.9ms) COMMIT
しかしレコードの検索をかけたタイミングで別のプロパティ値を設定したくなる時が頻繁に出てくる。例えば既存のレコードを参照するにしろ新規レコードを作成するにしろ、新たに著者(author)の情報も追加して保存したい、といったケースです。first_or_initialize
メソッドとsave
メソッドを併用すれば解決しますが、なんとふたつもメソッド使わなくていいんです。 first_or_createメソッドはブロックを引数に持つことができます。
Book.where(title: 'Frozen').first_or_create do |book|
book.author = 'Charles Dickens'
end
# Book Load (1.7ms) SELECT `books`.* FROM `books` WHERE `books`.`title` = 'Frozen' ORDER BY `books`.`id` ASC LIMIT 1
## 検索条件ではないauthorのデータも同時に保存できる
# (0.1ms) BEGIN
# SQL(29.9ms) INSERT INTO `books` (`created_at`, `title`, `author`, `updated_at`) VALUES ('2015-06-26 15:58:59', 'Frozen', 'Charles Dickens', '2015-06-26 15:58:59')
# (1.9ms) COMMIT
2. none
Rails4からサポートされた機能。名前から想像できるとおり空のモデルオブジェクトを生成して返り値とします。例えばユーザーのコンディションによっては、何をやっても「該当するデータは存在しませんでした」というメッセージだけを返すなど違和感なく機能を制限したいときに使えるメソッドです。
@posts = current_user.available_posts
## 有効期限切れのユーザーに対してはレコードが何も無いように見せたい時
def available_posts
case role
when 'Admin'
Post.all
when 'Reviewer'
Post.where(condition: published)
when 'Expired'
Post.none
end
end
3. find_each
ぼくらのイテレータeach
。しかし数千〜数万のレコードに対して同一の処理を繰り返したいときに使用すると、単一のクエリがレコードの数分発行されてしまうのでアプリが素敵にフリーズします。そんな時はfind_each
メソッドがよさげな様子。1000件のデータをまとめてバッチ処理してくれるために処理が高速化します。
Book.where(author: nil).find_each do |book|
book.author = "not to be filled yet"
book.save
end
4. find_by
テーブルから条件に該当するただひとつのレコードを取得したいときBook.where(title: 'Frozen', author: 'Charles Dickens').first
とするのはなんだかスマートじゃないですよね。その時に使えるのがfind_by
メソッドです。条件に合致するただひとつのレコードを取得し配列に入れずに返してくれます。
Book.find_by(title: 'Frozen', author: 'Charles Dickens')
## 返り値は配列に入っていない
# Book Load (0.3ms) SELECT `books`.* FROM `books` WHERE `books`.`title` = 'frozen' AND `books`.`author` = 'Charles Dickens' LIMIT 1
# => #<Book id: 1, title: "Frozen", author: "Charles Dickens", created_at: "2015-06-27 03:11:23", updated_at: "2015-06-27 03:11:23">
5. pluck
任意のカラムの値で配列化したい時、map
メソッドを使って以下のようにやっていませんか?
@book_titles = Book.all.map(&:title)
代わりにpluck
メソッドを使ってあげましょう。
all × map
メソッドの組み合わせと比較してみると、実行速度の面からも著しい改善が見られるようです。
@book_titles = Book.pluck(:title)
# => ["Frozen", "Tower", "the Lion King", ...]
6. merge
2つのハッシュを結合し、キーとバリューを維持したまま1つのハッシュにして返り値としてくれます。なお同じキーの値があった際には、merge
メソッドの引数として渡した値の方が優先される模様。merge!
という破壊的メソッドも用意されていて、こちらはレシーバ自身の値を書き換えてしまいます。レコードを保存する際に、複数のハッシュを単一のハッシュにできるmerge
は大変重宝します。
book_data = {title: "Frozen", author: "Charles Dickens"}
book_review = {rate: 10, review: "Awesome!"}
book_data.merge(book_review)
# => {title: "Frozen", author: "Charles Dickens", rate: 10, review: "Awesome!"}
以上となります。
オリジナルの英語記事では、使用頻度の関係から紹介できなかったscoping
などについても解説されていますので、ぜひ一度ご覧になってみてください。