ドキュメントを読み込むのは大事、ということでRailsガイドを頭から読んでいく取り組みをしています。 各章ごとに、(Railsガイドにちゃんと書いてあるのに)知らなかった機能を雑にまとめていきます。
今回は、Active Record クエリインターフェイスの章です。
データベースからオブジェクトを取り出すメソッド
リンクはこちら
create_with
APIドキュメントを見ましたが、そもそもwhereで絞り込んだ後にnewするとwhereで絞り込んだ値を持つのを知りませんでした、、
memos = Memo.where(content: 'sample01') memo = memos.new memo.content # => 'sample01' memos = Memo.create_with(content: 'sample02') memo = memos.new memo.content # => 'sample02'
extract_associated
コレクションに対して、それに関連づくレコードを配列で取得できます。
注意点としては、Memo::ActiveRecord_Relation
ではなく Array
で返ってくるところでしょうか。
Memo.where(id: [3, 4]).extract_associated(:user) # => [#<User id: 2, email: "sample04@example.com", ...>, # => #<User id: 1, email: "sample02@example.com", ...>]
from
FROM句のサブクエリを指定できます。
subquery = Memo.where(content: nil).to_sql Memo.where(user_id: 1).select('id').from("(#{subquery}) AS memos") #=> Memo Load (0.2ms) SELECT "memos"."id" FROM (SELECT "memos".* FROM "memos" WHERE "memos"."content" IS NULL) AS memos WHERE "memos"."user_id" = ? [["user_id", 1]]
readonly
readonly
を使い取得したコレクションは更新等ができなくなります。
バリデーションを無視するupdate_attribute
でも無理でした。
memo = Memo.readonly.first memo.update(content: 'update') # => ActiveRecord::ReadOnlyRecord memo.update_attribute(:content, 'update') # => ActiveRecord::ReadOnlyRecord
references
includes
と一緒に使い、where
等を使った際にテーブル名を関連付ける。
User.includes(:memos).where('memos.user_id = 1').references(:memos) # でもこれは以下のように書く方がよさそう User.includes(:memos).where(memos: { user_id: 1})
単一のオブジェクトを返すクエリメソッド
リンクはこちら
findはidを複数受け取ることもできる
find
ってidを一つだけ受け取るものと思っていましたが、複数受け取ることもできるんですね。
where(id: ...)
より短く書けるので、普通に使う場面多そうだなと思いました。
レコードがないときはエラーを返す点はwhere
とは違いますね。
Memo.find(1, 2) # => SELECT "memos".* FROM "memos" WHERE "memos"."id" IN (?, ?) [["id", 1], ["id", 2]] # => [#<Memo:0x0000000112f37880 id: 1, content: "sample01", ...> #<Memo:0x0000000112f377b8 id: 2, content: "sample02", ...>] Memo.find(1, 100) # id: 100のレコードはない # => ActiveRecord::RecordNotFound
take
「どのレコードが取り出されるかは指定されません。」ということで使うケースがあまりなさそうではあります。
Memo.take # => SELECT "memos".* FROM "memos" LIMIT ? [["LIMIT", 1]]
条件の上書き(rewhereなど)
リンクはこちら
いくつかは知っていましたが、一覧できちんと見れたのでよかったです。
rewhere
などは、使うとしたらdefault_scope
を使っていて、上書きしたいようなときでしょうか。
(その場合、rewhere
を使わないといけない時点で実装がやや怪しい気がしますが)
class Memo < ApplicationRecord default_scope { where.not(content: nil) } ... end Memo.all #=> [#<Memo:0x00000001083ea0b8 id: 1, content: "sample01", user_id: 1,...>, #<Memo:0x00000001083e9ff0 id: 2, content: "sample02", user_id: 1, ...>, #<Memo:0x00000001083e9f28 id: 3, content: "sample03", user_id: 2, ...>, #<Memo:0x00000001083e9e60 id: 4, content: "sample04", user_id: 2, ...>] Memo.rewhere(content: nil) # => [#<Memo:0x00000001079a38e8 id: 5, content: nil, user_id: 1, ...>, #<Memo:0x00000001079a3578 id: 6, content: nil, user_id: 1, ...>]
ifを使った際のscopeとクラスメソッドの違い
リンクはこちら
scopeはifの条件に当てはまらないときにはActiveRecord::Relation
オブジェクトを返します。
class Memo < ApplicationRecord scope :content_presents, -> { where.not(content: nil) if false } end Memo.content_presents # => [#<Memo:0x0000000109ee8ec8 id: 1, content: "sample01", ...>, #<Memo:0x000000010a1bda90 id: 2, content: "sample02", ...>, ...]
class Memo < ApplicationRecord def self.content_presents where.not(content: nil) if false end end Memo.content_presents # => nil
動的検索(find_by_attribute)
リンクはこちら
find_by_カラム名
で検索できるんですね。
レコードが見つからなかった場合にnilを返すのもfind_by
と同じです。
Memo.find_by_content('sample01') # => #<Memo:0x000000010afc7028 id: 1, content: "sample01", ...> Memo.find_by_content('No Record Content') # => nil
SQLで検索
リンクはこちら
積極的に使う機会は少ないかもですが、カスタムSQLを記述する方法として、コードリーディング等で見かけるかもなので、知れてよかったです。
find_by_sql
以下の例だと、Memoクラスのインスタンスが配列で帰ってきます。
インスタンスといえ、content
のみを取得しているので、created_at
などの情報は持っていません。
memos = Memo.find_by_sql("SELECT content FROM memos") #=> [#<Memo:0x000000010acaf9a8 id: nil, content: "sample01">, #<Memo:0x000000010acaf8e0 id: nil, content: "sample02">, #<Memo:0x000000010acaf818 id: nil, content: "sample03">, #<Memo:0x000000010acaf750 id: nil, content: "sample04">, #<Memo:0x000000010acaf688 id: nil, content: nil>, #<Memo:0x000000010acaf5c0 id: nil, content: nil>] memo.created_at #=> ActiveModel::MissingAttributeError
select_all
ActiveRecord::Result
オブジェクトが返ってきます。
to_a
を使うとハッシュの配列になります。
Memo.connection.select_all("SELECT content FROM memos") #=> #<ActiveRecord::Result:0x000000010a80f470 @column_types={}, @columns=["content"], @hash_rows=nil, @rows=[["sample01"], ["sample02"], ["sample03"], ["sample04"], [nil], [nil]]> Memo.connection.select_all("SELECT content FROM memos").to_a #=> [{"content"=>"sample01"}, {"content"=>"sample02"}, {"content"=>"sample03"}, {"content"=>"sample04"}, {"content"=>nil}, {"content"=>nil}]
includesとpluck
includes
が使われているときにpluck
を使うと、eager_loading
が引き起こされるというのは知らなかったです。
User.includes(:memos) #=> User Load (0.2ms) SELECT "users".* FROM "users" # => Memo Load (0.3ms) SELECT "memos".* FROM "memos" WHERE "memos"."user_id" IN (?, ?, ?, ?, ?, ?, ?) [["user_id", 1], ["user_id", 2], ... User.includes(:memos).pluck(:id) # idsでも同じ # => SELECT "users"."id" FROM "users" LEFT OUTER JOIN "memos" ON "memos"."user_id" = "users"."id" # => [1, 1, 1, 1, 2, 2, 3, 4, 5, 6, 7] # LEFT JOINされているので重複がある