Activeレコードのクエリーインターフェース

このガイドでは、Active Recordを使用してデータベースからデータを取得する別の方法について説明していきます。 このガイドを読むことで、次の事が学べるはずです。

  • 様々なメソッドと条件でレコードを見つける方法
  • 見つけたレコードの並び順、属性の取得、グルーピング、その他のプロパティを指定する方法
  • データ取得のために必要なデータベースへのクエリー数を減らす"eager loading"の使用方法
  • 動的なfind系メソッドの使用方法
  • 特定のレコードの存在の確認方法
  • Active Recordモデル上での様々な計算方法について
  • 関連付け上での"EXPLAIN"の実行方法

データベースのレコードを見つけるために生のSQLを使用している場合、 同じ操作をRails上でより良い方法で実行する事が可能です。 Active Recordでは、多くのケースで直接のSQLの使用を分離しようとします。

このガイドでのコードの例では、下記のモデルを参照することとします。

これ以降の全てのモデルは、特に指定が無い限り、idを主キーとして使用します。

class Client < ActiveRecord::Base
  has_one :address
  has_many :orders
  has_and_belongs_to_many :roles
end
class Address < ActiveRecord::Base
  belongs_to :client
end
class Order < ActiveRecord::Base
  belongs_to :client, counter_cache: true
end
class Role < ActiveRecord::Base
  has_and_belongs_to_many :clients
end

Active Recordはデータベース上でクエリーの実行を行い、 また多くのデータベースとの互換性を持ちます。(MySQL、PostgreSQL、SQLite等) Active Recordメソッドは、データベースに依存することなく同じように使用することが出来ます。

1. データベースからオブジェクトを取得

データベースからオブジェクトを取得するために、Active Recordはいくつかのfind系のメソッドを提供します。 各find系のメソッドは生のSQLを書くこと無く、受け取った引数からデータベース上で一定のクエリーを実行します。 メソッドには下記のものがあります。

  • bind
  • create_with
  • eager_load
  • extending
  • from
  • group
  • having
  • includes
  • joins
  • limit
  • lock
  • none
  • offset
  • order
  • preload
  • readonly
  • references
  • reorder
  • reverse_order
  • select
  • distinct
  • uniq
  • where

上記の全メソッドは、ActiveRecord::Relationのインスタンスを返します。

Model.find(options)の主な操作は、下記のように要約することが出来ます。

  • 提供されたoptionsをSQLクエリーとして評価できるものに変換します。
  • SQLクエリーを実行し、データベースから一致する結果を取得します。
  • 各結果の行をモデルに対応したRubyオブジェクトとしてインスタンス化します。
  • もし指定されていれば、after_findコールバックを実行します。

1.1 単一オブジェクトの取得

Active Recordは、単一のオブジェクトを取得するのに、異なる5つの方法を提供します。

1.1.1 主キーを使用

Model.find(primary_key)を使用して、指定した主キーに一致するオブジェクトを取得することが出来ます。 例えば、

# 主キー(id)が10であるClientを検索
client = Client.find(10)
# => #<Client id: 10, first_name: "Ryan">

これは下記のSQLとして評価されます。

SELECT * FROM clients WHERE (clients.id = 10) LIMIT 1

Model.find(primary_key)は、マッチするレコードが見つからなかった場合、 ActiveRecord::RecordNotFound例外を発生させます。

1.1.2 take

Model.takeは、暗黙的に並び順の指定をすること無くレコードを取得します。 例えば、

client = Client.take
# => #<Client id: 1, first_name: "Lifo">

上記は次のSQLとして評価されます。

SELECT * FROM clients LIMIT 1

Model.takeは、レコードが見つからず、また例外が発生していなければ、 nilを返します。

取得したレコードは、データベースエンジンによっては変更されていることがあるかもしれません。

1.1.3 first

Model.firstは、主キーで並び替えられた最初のレコードを見つけます。 例えば、

client = Client.first
# => #<Client id: 1, first_name: "Lifo">

上記は、次のSQLとして評価されます。

SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1

Model.firstは、マッチするレコードが見つからず、また例外が発生していなければnilを返します。

1.1.4 last

Model.lastは、主キーで並び替えられた最後のレコードを見つけます。 例えば、

client = Client.last
# => #<Client id: 221, first_name: "Russel">

このSQLは次のSQLとして評価されます。

SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1

Model.lastは、一致するレコードが見つからず、また例外が発生していなければnilを返します。

1.1.5 find_by

Model.find_byは、条件に一致する最初のレコードを見つけます。 例えば、

Client.find_by first_name: 'Lifo'
# => #<Client id: 1, first_name: "Lifo">

Client.find_by first_name: 'Jon'
# => nil

これは次のように書いたものとして評価されます。

Client.where(first_name: 'Lifo').take
1.1.6 take!

Model.take!は、暗黙的な並び替えを行わずにレコードを取得します。 例えば、

client = Client.take!
# => #<Client id: 1, first_name: "Lifo">

これは次のSQLとして評価されます。

SELECT * FROM clients LIMIT 1

Model.take!は、マッチするレコードが見つからなければ、ActiveRecord::RecordNotFoundを発生させます。

1.1.7 first!

Model.first!は主キーで並び替えられた最初のレコードを見つけます。 例えば、

client = Client.first!
# => #<Client id: 1, first_name: "Lifo">

これは次のSQLとして評価されます。

SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1

Model.first!は、マッチするレコードが見つからなければ、ActiveRecord::RecordNotFoundを発生させます。

1.1.8 last!

Model.last!は、主キーで並び替えられた最後のレコードを見つけます。

client = Client.last!
# => #<Client id: 221, first_name: "Russel">

これは次のSQLとして評価されます。

SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1

Model.last!は、マッチするレコードが見つからなければ、ActiveRecord::RecordNotFoundを発生させます。

1.1.9 find_by!

Model.find_by!は条件に一致する最初のレコードを見つけます。 マッチするレコードが見つからなければ、ActiveRecord::RecordNotFoundを発生させます。 例えば、

Client.find_by! first_name: 'Lifo'
# => #<Client id: 1, first_name: "Lifo">

Client.find_by! first_name: 'Jon'
# => ActiveRecord::RecordNotFound

これは次のように書いたものとして評価されます。

Client.where(first_name: 'Lifo').take!

1.2 複数のオブジェクトの取得

1.2.1 複数の主キーの使用

Model.find(array_of_primary_key)は、主キーの配列を受け取り、 それにマッチするレコードを全て配列として返します。 例えば、

# 主キーが1と10のClientを見つけます
client = Client.find([1, 10]) # Or even Client.find(1, 10)
# => [#<Client id: 1, first_name: "Lifo">, #<Client id: 10, first_name: "Ryan">]

これはつぎのSQLとして評価されます。

SELECT * FROM clients WHERE (clients.id IN (1,10))

Model.find(array_of_primary_key)は、提供された全ての主キーで一致するものが見つからなければ、 ActiveRecord::RecordNotFound例外を発生させます。

1.2.2 take

Model.take(limit)は、明確な並び順の指定無しに、 最初からlimit件数分のレコードを取得します。

Client.take(2)
# => [#<Client id: 1, first_name: "Lifo">,
      #<Client id: 2, first_name: "Raf">]

これは、次のSQLとして評価されます。

SELECT * FROM clients LIMIT 2
1.2.3 first

Model.first(limit)は、主キーの昇順で並び替え、 最初からlimit件数分のレコードを取得します。

Client.first(2)
# => [#<Client id: 1, first_name: "Lifo">,
      #<Client id: 2, first_name: "Raf">]

これは次のSQLとして評価されます。

SELECT * FROM clients ORDER BY id ASC LIMIT 2
1.2.4 last

Model.last(limit)は主キーの降順で並び替え、 最初からlimit件数分のレコードを取得します。

Client.last(2)
# => [#<Client id: 10, first_name: "Ryan">,
      #<Client id: 9, first_name: "John">]

これは次のSQLとして評価されます。

SELECT * FROM clients ORDER BY id DESC LIMIT 2

1.3 一括処理での複数オブジェクトの取得

多くのユーザーに向けたニュースレターの送信やデータのエクスポートをするようなケースで、 大きなレコードの集まりの繰り返し処理が必要になることがあります。

これをそのまま表すと、下記のようになるかもしれません。

#これはユーザーが数千行あるようなケースで、大変非効率です。
User.all.each do |user|
  NewsLetter.weekly_deliver(user)
end

ただし、User.all.eachはActive Recordに1つのパスでテーブル全体をfetchするように指示し、 行ごとにモデルオブジェクトを作成し、メモリ内にその全体のモデルの配列を保持してしまうため、 この手法はテーブルサイズが増えるに従って現実的では無くなっていきます。 実際に、もし大量のレコードを持っていた場合、取得したデータのコレクションが利用可能なメモリ容量を超えてしまうかもしれません。

Railsは、この問題を解決するために一括処理内でレコードを分割することによってメモリに負担が掛からないようにする、2つのメソッドを提供します。 1つ目のメソッドはfind_eachで、レコードの一括処理を取得し、モデルとして個別blockで各レコードをyieldします。 2つ目のメソッドはfind_in_batchesで、レコードの一括処理を取得し、 モデルの配列としてブロック全体の一括処理をyieldします。

find_eachfind_in_batchesは、大量のレコードの一括処理で同時にメモリが使用されないようにしています。 1000レコード分のループ処理であれば、通常のfindメソッドの方が適しています。

1.3.1 find_each

find_eachメソッドはレコードのまとまりを取得し、各レコードをモデルとして個別にブロックでyield処理します。 下記の例では、find_eachは1000レコード(現在のfind_eachとfind_in_batchesでのデフォルト値)を取得し、 各レコードをモデルとして個別にブロックでyield処理します。 この処理は全てのレコードが処理されるまで、繰り返し処理されます。

User.find_each do |user|
  NewsLetter.weekly_deliver(user)
end
1.3.1.1 find_eachのオプション

find_eachメソッドは、find_eachが内部で使用するために予約している:order:limitを除く、 通常のfindメソッドのオプションのほとんどを使用することが可能です。

また、:batch_size:startの追加オプションが利用可能です。

:batch_size

:batch_sizeオプションは、個々のブロックに渡される前に、 各一括処理で取得するレコード数を指定可能にしてくれます。 例えば、下記は5000のまとまりでレコードを取得します。

User.find_each(batch_size: 5000) do |user|
  NewsLetter.weekly_deliver(user)
end
:start

デフォルトでは、主キーの昇順でレコードは取得され、主キーは数値(integer)でなければいけません。 :startオプションは、最も番号の小さいIDが開始IDとして望ましくない場合に、それを自由に設定すること可能にしてくれます。 これは、例えばバッチ処理を中断し、チェックポイントとして最後の処理IDを保存しておき、そのIDから再開したいようなケースで便利です。

例えば、主キーが2000からのユーザー5000名に対してのみ、ニュースレターを送信したい場合は下記のようにします。

User.find_each(start: 2000, batch_size: 5000) do |user|
  NewsLetter.weekly_deliver(user)
end

もし、同じ処理キューを扱う複数のワーカーが必要であれば、それはもう1つの例になるでしょう。(翻訳に自信なし) 各ワーカー上のオプションで:startを設定することで、 各ワーカーに1000レコード扱わせるようにする事が出来ます。

1.3.2 find_in_batches

find_in_batchesメソッドはfind_eachに似ており、どちらのメソッドもレコードのまとまりを取得します。 2つの違いはfind_in_batchesは、個別に処理する代わりにブロック内でモデルの配列として、まとまりをyieldします。 下記の例では、1度に1000通の送り状の配列と最後はその残りを、提供されたブロックにyieldします。

# 1度に1000通の送り状の配列をadd_invoicesに渡します
Invoice.find_in_batches(include: :invoice_lines) do |invoices|
  export.add_invoices(invoices)
end

:includeオプションは、モデル読み込み時に関連付ける名前の指定を可能にしてくれます。(翻訳に自信なし)

1.3.2.1 find_in_batchesのオプション

find_in_batchesメソッドはfind_eachと同様、 find_in_batchesの内部使用のために予約されている:order:limitを除く、 :batch_size:startオプション、通常のfindメソッドで使用されるほとんどのオプションを受け取る事が出来ます。

2. 条件

whereメソッドは条件を指定することでSQL分のWHERE句に反映され、返されるレコードを限定することを可能にします。 条件には、文字列、配列、ハッシュのいずれかを指定することが出来ます。

2.1 文字列のみによる条件

もし、条件を追加したいのであれば、Client.where("orders_count = '2'")のように指定することが出来ます。 これは、orders_countフィールドの値が2である、全てのClient(顧客)データを取得します。

条件を文字列だけで条件を構築する事は、SQLインジェクションの脆弱性の原因になり得ます。 例えば、Client.where("first_name LIKE '%#{params[:first_name]}%'")と指定するのは危険です。 次のセクションで、配列を使用したより良い条件指定の方法を参照してください。

2.2 配列による条件

数値に任意の引数として指定されるようなケースでは、どうすればよいでしょう? そのようなケースでは、次のような形式で値を指定することが可能です。

Client.where("orders_count = ?", params[:orders])

Active Recordは、1つ目の要素の条件文字列内の?マークを、 それ以降の要素の値に置き換えます。

もし複数の条件を指定したいのであれば、下記のようにします。

Client.where("orders_count = ? AND locked = ?", params[:orders], false)

この例では、1つ目の?はparams[:orders]に置き換えられ、 2つ目の?は、SQLのデータベースシステムに依存したSQLとして解釈出来るfalse値に置き換えられます。

安全上の理由から、下のコードでは無く上のコードのように書くべきです。

Client.where("orders_count = ?", params[:orders]) #安全
Client.where("orders_count = ") #危険

直接、条件文字列に値を置くことは、同様にデータベースにその値を渡すことと同義です。 これは、悪意を持っているかもしれないユーザーから直接渡されたエスケープされていない値かもしれません。 もし、この事が知られてしまうと、それを利用してデータベースに対して任意の操作が出来てしまうため、 データベース全体が危険に晒されてしまいます。 条件文字列内に直接引数を置くことは、絶対にしないでください。

SQLインジェクションの危険性についての詳細は、 セキュリティガイドを参照してください。

2.2.1 プレースホルダによる条件

パラメータを?に置き換える形式は、配列の条件内でkey/valueのハッシュでも指定することが可能です。

Client.where("created_at >= :start_date AND created_at <= :end_date",
  {start_date: params[:start_date], end_date: params[:end_date]})

多くの条件指定をする必要がある場合は、こちらの方がより分かり易くすることが出来ます。

2.3 ハッシュによる条件

またActive Recordでは、より条件文の可読性を向上してくれるハッシュでの受け渡しも可能です。 ハッシュでの条件指定であれば、指定したいフィールドをキーに指定したい条件を値にしたハッシュを渡します。

ハッシュ条件に指定可能なものは、等価、範囲、部分集合、検査のみです。

2.3.1 等価の条件
Client.where(locked: true)

フィールド名は、文字列にすることも可能です。

Client.where('locked' => true)

belongs_toの関連付けが行われているケースでは、Active Recordオブジェクトが値として使用されていれば、 関連付けのキーはモデルを指定するために使用する事が出来ます。 このメソッドは、同様にポリモーフィズムでの関連付けで動作します。

Post.where(author: author)
Author.joins(:posts).where(posts: {author: author})

この値(author)はシンボルではありません。 例えば、Client.where(status: :active)とすることは出来ません。

2.3.2 範囲の条件
Client.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)

これは、昨日作成されたClient(顧客)を、BETWEENのSQL文を使用して取得します。

SELECT * FROM clients WHERE (clients.created_at BETWEEN '2008-12-21 00:00:00' AND '2008-12-22 00:00:00')

これは、配列による条件指定を短い構文で表したものになります。

2.3.3 部分集合の条件

もし、IN式を使用してレコードを探したい場合、 条件ハッシュに配列を渡すことで、これを行うことが出来ます。

Client.where(orders_count: [1,3,5])

このコードは下記のようなSQLを生成します。

SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))

2.4 NOT条件

NOTのSQLクエリーは、where.notで構築することが出来ます。

Post.where.not(author: author)

言い換えると、クエリーは引数無しのwhereに呼び出され、 その後のチェインでwhereの条件が渡されたnotによって生成することが出来ます。

3. 並び順

指定した並び順でデータベースからレコードを取得するために、orderメソッドを使用することが出来ます。

例えば、テーブル内のcreated_atフィールドを昇順で並び替えてレコードの集まりを取得したい場合は、下記のようにします。

Client.order(:created_at)
# OR
Client.order("created_at")

同様にASCまたはDESCを次のように指定します。

Client.order(created_at: :desc)
# OR
Client.order(created_at: :asc)
# OR
Client.order("created_at DESC")
# OR
Client.order("created_at ASC")

また、複数のフィールドを指定した並び替えも可能です。

Client.order(orders_count: :asc, created_at: :desc)
# OR
Client.order(:orders_count, created_at: :desc)
# OR
Client.order("orders_count ASC, created_at DESC")
# OR
Client.order("orders_count ASC", "created_at DESC")

もし、例えば異なるコンテキストで複数のorderを使用したのであれば、 新しいorderを追加することが出来ます。

Client.order("orders_count ASC").order("created_at DESC")
# SELECT * FROM clients ORDER BY orders_count ASC, created_at DESC

4. 特定のフィールドを選択

デフォルトでModel.findは、select *を使用し、結果として全てのフィールドを取得します。

このフィールドを任意に取得するには、selectメソッドを使用します。

例えば、下記はviewable_byとlockedのカラムの結果のみを取得します。

Client.select("viewable_by, locked")

このSQLクエリーは下記のようになります。

SELECT viewable_by, locked FROM clients

インスタンス化されるモデルオブジェクトも、選択したフィールドしか持たない事に注意してください。 もし、選択していないフィールドにアクセスしようとすると、下記のエラーが発生します。

ActiveModel::MissingAttributeError: missing attribute: <attribute>

<attribute>には、問い合わせようとしたフィールドが入ります。 idは関連付けが行われていると、ActiveRecord::MissingAttributeErrorが発生しない事に注意してください。 これは関連付けの関数プロパティでidが必要となるためです。

もし、特定レコードの一意の値ごとに単一のレコードを取得したいのであれば、distinctを使用することが出来ます。

Client.select(:name).distinct

これは次のようなSQLを生成します。

SELECT DISTINCT name FROM clients

また、その一意制約を取り除く事も可能です。

query = Client.select(:name).distinct
# => 一意の名前を返します

query.distinct(false)
# => 重複していたとしても、全ての名前も返します

5. limit文とoffset文

Model.findでSQLにLIMITを適用するには、 relation上でlimitoffsetを使用します。

limitを使用して取得するレコード数を指定し、 offsetを使用してスキップするレコード数を指定してレコード取得開始位置を調整します。 例えば、

Client.limit(5)

上記は最大で5件のClient(顧客)レコードを返します。 offsetの指定が無いため、テーブルの先頭から5件が返されます。 発行されるSQLは下記のようになります。

SELECT * FROM clients LIMIT 5

下記は、offsetを追加することで、

Client.limit(5).offset(30)

31番目から始まるClient(顧客)レコードを最大5件返します。 SQLは下記のようになります。

SELECT * FROM clients LIMIT 5 OFFSET 30

6. group文

GROUP BYをSQLに適用するには、groupメソッドを使用します。

例えば、注文(Order)の作成日(created_at)を日付けのまとまりとして取得したい場合は、下記のように指定します。

Order.select("date(created_at) as ordered_date, sum(price) as total_price").group("date(created_at)")

そしてこれは、データベース上の注文(Order)の各日付け毎に、単一のOrderオブジェクトを与えてくれます。

SQLは下記のようになります。

SELECT date(created_at) as ordered_date, sum(price) as total_price
FROM orders
GROUP BY date(created_at)

7. having文

SQLはHAVINGを指定して、Group BYのフィールドに条件を指定することが出来ます。 havingメソッドを追加することで、SQL文にHAVINGを適用することが可能です。

例えば下記のようにすると、

Order.select("date(created_at) as ordered_date, sum(price) as total_price").
  group("date(created_at)").having("sum(price) > ?", 100)

SQLは次のようにして実行されます。

SELECT date(created_at) as ordered_date, sum(price) as total_price
FROM orders
GROUP BY date(created_at)
HAVING sum(price) > 100

これは1日の注文が$100ドル以上である、各日付け毎の単一のOrderオブジェクトを返します。

8. 条件の上書き

8.1 except

exceptメソッドを使用して、確定している条件を除外することが出来ます。 例えば、

Post.where('id > 10').limit(20).order('id asc').except(:order)

このSQLは下記のように実行されます。

SELECT * FROM posts WHERE id > 10 LIMIT 20

8.2 unscope

exceptメソッドは、関連付けが結合されている場合には動作しません。 例えば、

Post.comments.except(:order)

上記は、もしCommentでデフォルトのscopeが設定されていると、order(並び替え)の条件を保持したままになります。 関連付けが連結していたとしても、全ての並び順の条件を削除したい場合は、 下記のようにunscopeを使用してください。

下記の=は代入ではなく、"同じように評価される"という意味で使用されていると思われます。

Post.order('id DESC').limit(20).unscope(:order) = Post.limit(20)
Post.order('id DESC').limit(20).unscope(:order, :limit) = Post.all

下記のように、where句にunscopeの指定を追加することも可能です。

Post.where(:id => 10).limit(1).unscope(where: :id, :limit).order('id DESC') = Post.order('id DESC')

8.3 only

onlyメソッドを使用して、条件を上書きすることも可能です。 例えば、

Post.where('id > 10').limit(20).order('id desc').only(:order, :where)

このSQLは次のように実行されます。

SELECT * FROM posts WHERE id > 10 ORDER BY id DESC

8.4 reorder

reorderメソッドは、デフォルトのscopeのorderを上書きします。 例えば、

class Post < ActiveRecord::Base
  ..
  ..
  has_many :comments, order: 'posted_at DESC'
end

Post.find(10).comments.reorder('name')

このSQLは次のように実行されます。

SELECT * FROM posts WHERE id = 10 ORDER BY name

reorderの指定が無ければ、SQLは次のようになります。

SELECT * FROM posts WHERE id = 10 ORDER BY posted_at DESC

8.5 reverse_order

reverse_orderメソッドは、指定されると並び順を反転します。

Client.where("orders_count > 10").order(:name).reverse_order

このSQLは次のように実行されます。

SELECT * FROM clients WHERE orders_count > 10 ORDER BY clients.id DESC

このメソッドは、引数を受け取りません。

9. Null関連付け

noneメソッドは、レコード無しでチェーン可能なRelationを返します。 返されるRelationにチェーンされて続く条件は、空のRelationの生成を続行します。(翻訳に自信なし) これは、メソッドにチェイン可能な応答をメソッドにする必要がある場合や、結果が0件のscopeを返すかもしれないような場合に便利です。

Post.none # 空のRelationを返し、クエリを実行しません。
# 下記のvisible_postsメソッドは、Relationを返す事を期待されます。
@posts = current_user.visible_posts.where(name: params[:name])

def visible_posts
  case role
  when 'Country Manager'
    Post.where(country: country)
  when 'Reviewer'
    Post.published
  when 'Bad User'
    Post.none # => この場合[]またはnilを返すと、呼び出し元を破壊してしまいます。
  end
end

10. 読み込み専用オブジェクト

Active Recordは、Relation上でreadonlyメソッドを提供し、返されるオブジェクトの変更を明示的に禁じます。 readonlyとされたレコードの変更を試みると、ActiveRecord::ReadOnlyRecord 例外が発生します。

client = Client.readonly.first
client.visits += 1
client.save

上記のコードではclientはreadonlyオブジェクトとして設定されるため、 visitsの値を更新するためにclient.saveが呼び出された時に、ActiveRecord::ReadOnlyRecord例外が発生します。

11. Updateでのレコードのロック

ロック(Lock)処理は、データベース内のレコードの更新で、膨大な更新を確実にする際に競合状態を防止するのに役立ちます。

Active Recordは2つのロック処理のメカニズムを提供します。

  • 楽観的ロック
  • 悲観的ロック

11.1 楽観的ロック

楽観的ロックは複数のユーザーが編集のために同じレコードへアクセスすることを許可し、 データの競合が極めて少ない事を前提にしています。 そのレコードが開かれたかどうかで、別のプロセスがレコードに変更を加えたかどうかをチェックすることによってこれを行います。 もしそれが起こると、ActiveRecord::StaleObjectError例外がスローされ、更新は破棄されます。

楽観的ロックのカラム

楽観的ロックを使用するには、テーブルにinteger型のlock_versionというカラムが必要になります。 レコードが更新される度に、Active Recordはlock_versionカラムの値を増やします。 もし更新するリクエストのがlock_versionが、現在のデータベースのlock_versionの値より低い場合、 更新リクエストはActiveRecord::StaleObjectErrorエラーで失敗します。

c1 = Client.find(1)
c2 = Client.find(1)

c1.first_name = "Michael"
c1.save

c2.name = "should fail"
c2.save # ActiveRecord::StaleObjectError発生

このため衝突が起こった場合には、例外のrescueし、ロールバック、マージ、そうでなければビジネスロジックでその衝突を解決する必要あります。

この振る舞いは、ActiveRecord::Base.lock_optimistically = falseを設定することによって、 オフにすることが出来ます。

lock_versionのカラム名を上書きするには、 ActiveRecord::Baseが提供するlocking_columnのクラス属性を次のように変更します。

class Client < ActiveRecord::Base
  self.locking_column = :lock_client_column
end

11.2 悲観的ロック

悲観的ロックは、データベースに実装されている機能が提供するメカニズムを使用します。 Relationを構築するときに、ロックを使用すると、選択した行の排他ロックを取得します。 ロックを使用したRelationは、通常はデッドロック状態を防ぐために内部のトランザクションをラップします。

例えば、

Item.transaction do
  i = Item.lock.first
  i.name = 'Jones'
  i.save
end

上記のセッションは、MySQLのバックエンドのために下記のSQLを生成します。

SQL (0.2ms)   BEGIN
Item Load (0.3ms)   SELECT * FROM `items` LIMIT 1 FOR UPDATE
Item Update (0.4ms)   UPDATE `items` SET `updated_at` = '2009-02-07 18:05:56', `name` = 'Jones' WHERE `id` = 1
SQL (0.8ms)   COMMIT

また、異なるタイプのロックを使用できるように、lockメソッドに生のSQLを渡すことも出来ます。 例えば、MySQLはレコードをロックするけれど、他のクエリーの読み込みを未だ許可するLOCK IN SHARE MODEという式を持ちます。 この式が指定するには、下記のようにlockオプション(lockメソッド?)を渡します。

Item.transaction do
  i = Item.lock("LOCK IN SHARE MODE").find(1)
  i.increment!(:views)
end

もし既にモデルのインスタンスを持っている場合、 下記のコードのようにすることで、トランザクションを開始し、ロックを取得することが出来ます。

item = Item.first
item.with_lock do
  # このブロックはトランザクション内で呼ばれ、
  # itemはロックされます。
  item.increment!(:views)
end

12. テーブルの連結

Active RecordはSQLにJOIN句を指定するjoinsと呼ばれるfind系のメソッドを提供します。 joinsメソッドを使用する複数の方法が存在します。

12.1 SQL文字列を部分的に使用

単純にjoinsに、JOIN句の生のSQLを適用する事が出来ます。

Client.joins('LEFT OUTER JOIN addresses ON addresses.client_id = clients.id')

これは、結果として下記のSQLになります。

SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id = clients.id

12.2 名前付き関連付けの配列/ハッシュの使用

このメソッドは、INNER JOINのみで動作します。

Active Recordはjoinsを使用する際に、 モデル上で定義された関連付けの名前をショートカットとしてJOIN句の指定に使用出来るようにしてくれます。

下記のCategory、Post、Comments、Guestモデルが定義されている前提で考察してみましょう。

class Category < ActiveRecord::Base
  has_many :posts
end

class Post < ActiveRecord::Base
  belongs_to :category
  has_many :comments
  has_many :tags
end

class Comment < ActiveRecord::Base
  belongs_to :post
  has_one :guest
end

class Guest < ActiveRecord::Base
  belongs_to :comment
end

class Tag < ActiveRecord::Base
  belongs_to :post
end

この後に続くコードでは、意図したINNER JOINが使用されたjoinクエリーが生成されます。

12.2.1 単一の関連付けでのJOIN
Category.joins(:posts)

これは下記のSQLを生成します。

SELECT categories.* FROM categories
  INNER JOIN posts ON posts.category_id = categories.id

または英語で、"return a Category object for all categories with posts" (postsに紐付く全てのカテゴリーのCategoryオブジェクトを返します)となります。 複数のpostが同じカテゴリーを持つ場合、重複したカテゴリーが含まれることに注意してください。 もし、カテゴリーを重複させたくないのであれば、Category.joins(:posts).uniqを使用してください。

12.2.2 複数の関連付けのJOIN
Post.joins(:category, :comments)

これは下記のSQLを生成します。

SELECT posts.* FROM posts
  INNER JOIN categories ON posts.category_id = categories.id
  INNER JOIN comments ON comments.post_id = posts.id

または英語で、"return all posts that have a category and at least one comment" (カテゴリーと少なくとも1つのコメントも持つ全てのpostを返します。)となります。 前述の繰り返しになりますが、複数のコメントを持つpostが複数回表示されることに注意してください。

12.2.3 入れ子の関連付けによるJOIN(1階層)
Post.joins(comments: :guest)

これは下記のSQLを生成します。

SELECT posts.* FROM posts
  INNER JOIN comments ON comments.post_id = posts.id
  INNER JOIN guests ON guests.comment_id = comments.id

または英語で、"return all posts that have a comment made by a guest." (ゲストによるコメントを持つ全てのpostを返します)となります。

12.2.4 入れ子の関連付けによるJOIN(複数階層)
Category.joins(posts: [{comments: :guest}, :tags])

これは下記のSQLを生成します。

SELECT categories.* FROM categories
  INNER JOIN posts ON posts.category_id = categories.id
  INNER JOIN comments ON comments.post_id = posts.id
  INNER JOIN guests ON guests.comment_id = comments.id
  INNER JOIN tags ON tags.post_id = posts.id

12.3 条件指定によるテーブル連結

通常の配列と文字列条件を使用して、テーブル連結した上で条件を指定することが可能です。 ハッシュによる条件指定は、特別な文法により連結するテーブルを指定します。

time_range = (Time.now.midnight - 1.day)..Time.now.midnight
Client.joins(:orders).where('orders.created_at' => time_range)

入れ子でハッシュ条件を指定する、より整った別の文法で書くことも出来ます。

time_range = (Time.now.midnight - 1.day)..Time.now.midnight
Client.joins(:orders).where(orders: {created_at: time_range})

これは、再びBETWEENのSQL式を使用して、 昨日作成された注文(order)を持つ顧客(client)を全て見つけ出します。

13. Eager Loading連携

Eager loading(意欲的な読み込み)は、Model.findによって返されるオブジェクトの関連レコードを、 出来るだけ少ないクエリーで読み込むためのメカニズムです。

N + 1クエリー問題

下記のコードで考えてみましょう。 これは顧客を10人見つけ、彼らの郵便番号を出力します。

clients = Client.limit(10)

clients.each do |client|
  puts client.address.postcode
end

このコードは、一見問題無いように見えますが、件数分のクエリーが実行されてしまうという問題を抱えています。 上記のコードは合計で、1(顧客10人を見つけるため) + 10(それぞれの顧客毎に住所を読み込むため) = 11クエリーが実行されます。

N + 1クエリー問題の解決

Active Recordは、開発者に予め全ての関連付けを指定してもらうことで、それを読み込むようにします。 これはModel.find呼び出しのincludesメソッドの指定によって可能になります。 includesを使用することで、Active Recordは指定された関連付けの全てを、 可能な限り少ないクエリーで読み込みます。

上記のコードのClient.limit(10)を、Eager loadingを使用して書き換えてみましょう。

clients = Client.includes(:address).limit(10)

clients.each do |client|
  puts client.address.postcode
end

上記のコードであれば、以前の11のクエリーに対し、2つのクエリーしか実行しません。

SELECT * FROM clients LIMIT 10
SELECT addresses.* FROM addresses
  WHERE (addresses.client_id IN (1,2,3,4,5,6,7,8,9,10))

13.1 複数の関連付けによるEager Loading

Active Recordは、includesメソッドを使用した単一のModel.findの呼び出しで、 配列・ハッシュ、または配列/ハッシュの入れ子のハッシュによる複数の関連付けのEager loadingを行ってくれます。

13.1.1 複数の関連付けの配列
Post.includes(:category, :comments)

これは全ての投稿(post)と、各投稿に関連付けられたカテゴリー(category)とコメント(comment)を読み込みます。

13.1.2 関連付けハッシュの入れ子
Category.includes(posts: [{comments: :guest}, :tags]).find(1)

これはidが1であるカテゴリーと、Eager Loadingで全ての関連付けられた投稿(post)、 投稿に関連付けられたタグ(tag)とコメント(comment)、そして各コメントのゲストを見つけ出します。

13.2 Eager Loadingで関連付けられた上での条件指定

Active RecordはEager Loading上でjoinsのように条件を指定することが可能ですが、 joinsの代わりに使用する事をお勧めします。(翻訳に自信なし)

これを行う必要がある場合は、通常通りwhereを使用することが可能です。

Post.includes(:comments).where("comments.visible" => true)

これがLEFT OUTER JOINを含むクエリーを生成するのに対し、 joinsメソッドは代わりにINNER JOINを使用した1つのものを生成します。

SELECT "posts"."id" AS t0_r0, ... "comments"."updated_at" AS t1_r5 FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE (comments.visible = 1)

もし、where条件が無ければ、通常の2つのクエリーのセットを生成します。

このincludesのクエリーのケースであれば、投稿(post)へのコメントが何も無くても、 全ての投稿(post)情報を読み込みます。 joins(INNER JOIN)を使用すると、連結条件は一致しなければならず、 一致しない場合はレコードは返されません。

14. スコープ

スコープ(scope)は、共通で使用する(よく使われる)クエリーを指定することで、 関連付けられたオブジェクトまたはモデル上でメソッド呼び出しとして参照する事が出来るようにしてくれます。 wherejoinsincludesのような各メソッドを予め定義しておく事を可能にしてくれます。 全てのscopeメソッドは、更にメソッド(他のscopeのような)が呼び出せるように、 ActiveRecord::Relationオブジェクトを返してくれます。

シンプルなスコープを定義するには、クラス内にscopeメソッドを使用し、 このスコープが呼び出された際に実行したいクエリーを渡します。

class Post < ActiveRecord::Base
  scope :published, -> { where(published: true) }
end

これはクラスメソッド定義と全く同義で、 どちらを使うかは個人の好みになります。

class Post < ActiveRecord::Base
  def self.published
    where(published: true)
  end
end

スコープはまた、スコープの内部でもチェーン可能です。

class Post < ActiveRecord::Base
  scope :published,               -> { where(published: true) }
  scope :published_and_commented, -> { published.where("comments_count > 0") }
end

このpublishedスコープをクラス、

Post.published # => [published posts]

または関連付けされたPostオブジェクトのいずれかから呼び出すことが出来ます。

category = Category.first
category.posts.published # => [published posts belonging to this category]

14.1 引数について

スコープに引数を渡すことも出来ます。

class Post < ActiveRecord::Base
  scope :created_before, ->(time) { where("created_at < ?", time) }
end

これは、下記のようにして呼び出すことが出来ます。

Post.created_before(Time.zone.now)

ただし、これは単にクラスメソッドによって提供される機能の複製になります。

class Post < ActiveRecord::Base
  def self.created_before(time)
    where("created_at < ?", time)
  end
end

スコープが引数を受け取る場合には、クラスメソッドを使用するほうが好ましいです。 これらのメソッドは、関連付けオブジェクト上からでもアクセスすることが出来ます。

category.posts.created_before(time)

14.2 スコープのマージ

whereと同様、スコープはAND条件を使用してマージされます。

class User < ActiveRecord::Base
  scope :active, -> { where state: 'active' }
  scope :inactive, -> { where state: 'inactive' }
end

User.active.inactive
# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'inactive'

スコープとwhere条件を織り交ぜて、 最終的なSQLが全ての条件をANDで繋げるようにすることが出来ます。

User.active.where(state: 'finished')
# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'finished'

もし、最後のWHERE句だけが必要な場合は、Relation#mergeを使用することが出来ます。

User.active.merge(User.inactive)
# => SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'

default_scopeは、スコープとwhere条件で上書きされることに注意してください。

class User < ActiveRecord::Base
  default_scope  { where state: 'pending' }
  scope :active, -> { where state: 'active' }
  scope :inactive, -> { where state: 'inactive' }
end

User.all
# => SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'

User.active
# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active'

User.where(state: 'inactive')
# => SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'

上記の通り、default_scopescopewhereの両方で上書きされます。

14.3 デフォルトスコープの適用

もし、モデルを通して全てのクエリーにスコープが適用されるようにしたいのであれば、 モデル内でdefault_scopeメソッドを使用することで、それを行うことが可能です。

class Client < ActiveRecord::Base
  default_scope { where("removed_at IS NULL") }
end

このモデルでクエリーが実行されると、SQLクエリーは下記のようになります。

SELECT * FROM clients WHERE removed_at IS NULL

もし、デフォルトスコープ(default_scope)でより複雑な事をする必要がある場合は、 代わりにクラスメソッドとして定義することが出来ます。

class Client < ActiveRecord::Base
  def self.default_scope
    # ActiveRecord::Relationを返す必要があります。
  end
end

14.4 全てのスコープを削除

もし、何らかの理由でスコープの適用を削除したい場合には、unscopedメソッドを使用することが出来ます。 これは、モデル内にdefault_scopeが指定されていて、 特定のクエリーに対してそれを適用したくない場合に便利です。

Client.unscoped.all

このメソッドは全てのスコープを削除し、テーブル上で通常のクエリーを実行します。

unscopedscopeのチェーンは動作しないことに注意してください。 こういったケースでは、ブロック形式のunscopedの使用をお勧めします。

Client.unscoped {
  Client.created_before(Time.zone.now)
}

15. 動的ファインダー

動的なfindはRails4.0で非推奨になり、Rails4.1で削除される予定なので注意してください。 代わりにActive Recordのレコードを使用することが推奨されています。 非推奨になったGemは、 https://github.com/rails/activerecord-deprecated_findersにあります。

テーブル内で定義した各フィールド(属性とも言う)に対し、Active Recordはfind系のメソッドを提供します。 例えばもし、Clientモデルにfirst_nameと呼ばれるフィールドがある場合、 Active Recordからfind_by_first_nameというメソッドが提供されます。 また、Clientモデルにlockedフィールドがあれば、find_by_lockedが提供されます。

感嘆符(!)をClient.find_by_name!("Ryan")のようにして動的findの最後に付けることで、 もし該当するレコードが1件も無ければ、 ActiveRecord::RecordNotFoundエラーを発生させることが出来るようになります。

もし、namelockedの両方でfindを使用したい場合は、 その2つのフィールドの間に単に"and"を入れるだけで、それらがチェーンされます。 例えば、Client.find_by_first_name_and_locked("Ryan", true)となります。

16. findまたはbuildによる新しいオブジェクト

レコードを探し、もし存在しなければレコードを新規作成したいといったケースがよくあります。 これは、find_or_create_byfind_or_create_by!メソッドを使うことで行うことが出来ます。

16.1 find_or_create_by

find_or_create_byメソッドは、その属性を持つレコードが存在するか否かを調べます。 もし存在しなければ、createが呼び出されます。下記の例を見てみましょう。

'Andy'という名前の顧客(client)を見つけたいと仮定すると、 その顧客が存在しなかった場合にその名前の顧客がcreateされます。

Client.find_or_create_by(first_name: 'Andy')
# => #<Client id: 1, first_name: "Andy", orders_count: 0, locked: true, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27">

このメソッドによって、次のようなSQLが生成されます。

SELECT * FROM clients WHERE (clients.first_name = 'Andy') LIMIT 1
BEGIN
INSERT INTO clients (created_at, first_name, locked, orders_count, updated_at) VALUES ('2011-08-30 05:22:57', 'Andy', 1, NULL, '2011-08-30 05:22:57')
COMMIT

find_or_create_byは、既に存在するレコードか新しいレコードのどちらかを返します。 このケースでは、Andyという名前の顧客が存在しなかったため、新しいレコードが作成されて返されました。

新しいレコードは、バリデーション(validation)をパスしたか否かに依って、 データベースに保存されていない(createのように)かもしれません。

新しいレコードを作成する際に'locked'属性はtrueに設定したいが、 このクエリーにはそれを含めたくないと仮定します。 そのため、"Andy"という名前の顧客を見つけ、 存在しなければ"Andy"という名前のロック(locked)されていない顧客を作成します。

これを行うための2つの方法が用意されています。 1つ目は、create_withの使用です。

Client.create_with(locked: false).find_or_create_by(first_name: 'Andy')

2つ目は、ブロックを使用する方法です。

Client.find_or_create_by(first_name: 'Andy') do |c|
  c.locked = false
end

ブロックは顧客が作成される際にのみ、実行されます。 2回目のこのコードの実行では、ブロックは無視されます。

16.2 find_or_create_by!

find_or_create_by!を使用して、新しいレコードが無効(invalid)だった場合に、例外を発生させることも可能です。 検証(Validation)についてはこのガイドでは説明しませんが、ひとまず下記のコードをClientモデルに追加してみましょう。

validates :orders_count, presence: true

もし、新しい顧客(Client)をorders_countを渡さずに作成しようとすると、 そのレコードは無効(invalid)となり、例外が発生します。

Client.find_or_create_by!(first_name: 'Andy')
# => ActiveRecord::RecordInvalid: Validation failed: Orders count can't be blank

16.3 find_or_initialize_by

find_or_initialize_byメソッドは、find_or_create_byのように動作しますが、 createの代わりにnewを呼び出します。 これは、メモリー内に新しいモデルのインスタンスを作成しますが、データベースには保存しない事を意味します。 find_or_create_byと同じく、ここでは'Nick'という名前を指定します。

nick = Client.find_or_initialize_by(first_name: 'Nick')
# => <Client id: nil, first_name: "Nick", orders_count: 0, locked: true, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27">

nick.persisted?
# => false

nick.new_record?
# => true

オブジェクトはこの時点ではデータベース内に保存しないので、SQLは下記のように生成されます。

SELECT * FROM clients WHERE (clients.first_name = 'Nick') LIMIT 1

これをデータベースに保存したい場合は、saveを呼び出します。

nick.save
# => true

17. SQL文によるfind

もし直接SQLを書いてテーブルからレコードを見つけたい場合は、find_by_sqlを使用することが出来ます。 find_by_sqlメソッドは、クエリーが返すレコードが単一のものであっても、オブジェクトの配列を返します。 例えば、このようにしてクエリーを実行することが出来ます。

Client.find_by_sql("SELECT * FROM clients
  INNER JOIN orders ON clients.id = orders.client_id
  ORDER clients.created_at desc")

find_by_sqlは、データベースへの任意のクエリーを実行して、インスタンスオブジェクトを取得するシンプルな方法を提供します。

17.1 select_all

find_by_sqlは、connection#select_allと近い関係にあります。 select_allはデータベースからfind_by_sql任意のSQLを使用してオブジェクトを取得しますが、 それらはインスタンス化を行いません。 代わりに、各レコードを指し示すハッシュの配列を取得します。

Client.connection.select_all("SELECT * FROM clients WHERE id = '1'")

17.2 pluck

pluckは、モデルのテーブルから1つまたは複数のカラムを参照するのに使用されます。 これはカラム名のリストを引数として受け取り、データ型に沿ったカラム固有の値の配列を返します。

Client.where(active: true).pluck(:id)
# SELECT id FROM clients WHERE active = 1
# => [1, 2, 3]

Client.distinct.pluck(:role)
# SELECT DISTINCT role FROM clients
# => ['admin', 'member', 'guest']

Client.pluck(:id, :name)
# SELECT clients.id, clients.name FROM clients
# => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]

pluckは、下記のようなコードにも置き換える事が出来ます。

Client.select(:id).map { |c| c.id }
# or
Client.select(:id).map(&:id)
# or
Client.select(:id, :name).map { |c| [c.id, c.name] }
Client.pluck(:id)
# or
Client.pluck(:id, :name)

selectと異なり、pluckはActiveRecordオブジェクトにコンストラクタすること無く、 直接データベースの結果をRubyの配列に変換します。 これは、規模の大きいまたは回数の多いクエリーのパフォーマンスが向上することに繋がります。 ただし、モデルメソッドの上書きでは利用出来ません。 例えば、

class Client < ActiveRecord::Base
  def name
    "I am #{super}"
  end
end

Client.select(:name).map &:name
# => ["I am David", "I am Jeremy", "I am Jose"]

Client.pluck(:name)
# => ["David", "Jeremy", "Jose"]

更にselectや、その他のRelationスコープと異なり、 pluckは即座にクエリーを実行するため、それ以降スコープをチェーンすることが出来ませんが、 予めコンストラクトしたスコープと動作させる事は可能です。

Client.pluck(:name).limit(1)
# => NoMethodError: undefined method `limit' for #<Array:0x007ff34d3ad6d8>

Client.limit(1).pluck(:name)
# => ["David"]

17.3 ids

idsは、テーブルの主キーを使用して、関連付けのために全てのIDを取り出すのに使用することが出来ます。

Person.ids
# SELECT id FROM people
#主キーが"id"では無い場合
class Person < ActiveRecord::Base
  self.primary_key = "person_id"
end

Person.ids
# SELECT person_id FROM people

18. オブジェクトの存在

もし、オブジェクト(レコード)が単に存在するか否かを確認したいのであれば、exists?メソッドでそれを行うことが出来ます。 このメソッドは、findとして同じクエリーをデータベースに対して行いますが、 オブジェクトまたはオブジェクトのコレクションを返す代わりに、trueかfalseのいずれかを返します。

Client.exists?(1)

また、exists?メソッドは複数のidを受け取りますが、 いずれかのレコードが存在すればtrueを返します。

Client.exists?(1,2,3)
# or
Client.exists?([1,2,3])

モデルまたはRelation上では、引数無しでもexists?を使用することが可能です。

Client.where(first_name: 'Ryan').exists?

上記はfirst_nameが'Ryan'の顧客が少なくとも1人存在すれば、 trueを返し、そうでなければfalseを返します。

Client.exists?

上記は、Clientテーブルが空であればfalseを返し、そうでなければtrueを返します。

any?many?を使用して、モデルまたはRelation上で存在の確認をすることも出来ます。

# モデルを通して
Post.any?
Post.many?

# 名付けされたスコープを通して
Post.recent.any?
Post.recent.many?

# Relationを通して
Post.where(published: true).any?
Post.where(published: true).many?

# 関連付けを通して
Post.first.categories.any?
Post.first.categories.many?

19. 計算

このセクションでは、最初にcountを例としてオプションの説明をしますが、 以降のメソッド全てにおいて、ここで説明するオプションが適用されます。

全ての計算メソッドはモデル上で直接動作します。

Client.count
# SELECT count(*) AS count_all FROM clients

または、Relation上で、

Client.where(first_name: 'Ryan').count
# SELECT count(*) AS count_all FROM clients WHERE (first_name = 'Ryan')

Relation上で、複雑な計算を実行するために様々なfind系メソッドを使用することも可能です。

Client.includes("orders").where(first_name: 'Ryan', orders: {status: 'received'}).count

これは、下記のように実行されます。

SELECT count(DISTINCT clients.id) AS count_all FROM clients
  LEFT OUTER JOIN orders ON orders.client_id = client.id WHERE
  (clients.first_name = 'Ryan' AND orders.status = 'received')

19.1 Count

もし、モデルのテーブル内にいくつのレコードがあるのか確認したい場合は、Client.countを呼び出すことで、 その件数を取得することが可能です。 もし、更に条件を絞り込んで、データベース内の年齢(age)が入力されている全ての顧客を見つけたい場合は、 Client.count(:age).を使用することが出来ます。

オプションについては、このセクションの最初のオプションの説明を参照してください。

19.2 Average

テーブルに関連付けられたクラスからaverageメソッドを呼び出す事で、テーブル内のある数の平均値を確認することが出来ます。 このメソッドは、下記のようにして呼び出します。

Client.average("orders_count")

これは、フィールドの平均を表す数値(3.14159265のような浮動小数点数の可能性があります)を返します。

オプションについては、このセクションの最初のオプションの説明を参照してください。

19.3 Minimum

テーブルに関連付けられたクラスからminimumメソッドを呼び出す事で、テーブル内のある数の最小値を確認することが出来ます。 このメソッドは、下記のようにして呼び出します。

Client.minimum("age")

オプションについては、このセクションの最初のオプションの説明を参照してください。

19.4 Maximum

テーブルに関連付けられたクラスからmaximumメソッドを呼び出す事で、テーブル内のある数の最大値を確認することが出来ます。 このメソッドは、下記のようにして呼び出します。

Client.maximum("age")

オプションについては、このセクションの最初のオプションの説明を参照してください。

19.5 Sum

テーブルに関連付けられたクラスからsumメソッドを呼び出す事で、テーブル内の全てのレコードの合計値を確認することが出来ます。 このメソッドは、下記のようにして呼び出します。

Client.sum("orders_count")

オプションについては、このセクションの最初のオプションの説明を参照してください。

20. EXPLAINの実行

Relationにより、クエリー上でEXPLAINを実行させる事が可能です。 例えば、MySQLであれば、

User.where(id: 1).joins(:posts).explain

次のように実行されます。

EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `posts` ON `posts`.`user_id` = `users`.`id` WHERE `users`.`id` = 1
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | Extra       |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|  1 | SIMPLE      | users | const | PRIMARY       | PRIMARY | 4       | const |    1 |             |
|  1 | SIMPLE      | posts | ALL   | NULL          | NULL    | NULL    | NULL  |    1 | Using where |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
2 rows in set (0.00 sec)

Active Recordは、データベースシェルをエミュレートした内容が、そのまま出力されるように実行します。 そのため、同じクエリーであってもPostgreSQLのアダプターが使用されていれば、 代わりに次のように出力されます。

EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" WHERE "users"."id" = 1
                                  QUERY PLAN
------------------------------------------------------------------------------
 Nested Loop Left Join  (cost=0.00..37.24 rows=8 width=0)
   Join Filter: (posts.user_id = users.id)
   ->  Index Scan using users_pkey on users  (cost=0.00..8.27 rows=1 width=4)
         Index Cond: (id = 1)
   ->  Seq Scan on posts  (cost=0.00..28.88 rows=8 width=4)
         Filter: (posts.user_id = 1)
(6 rows)

Eager Loadingは裏で複数のクエリーをトリガする可能性があり、 クエリーによっては事前にその結果が必要になる事があります。 そのためexplainは実際にそのクエリーを実行して、 クエリープランの問い合わせを行います。 例えば、MySQLであれば

User.where(id: 1).includes(:posts).explain

下記のように出力されます。

EXPLAIN for: SELECT `users`.* FROM `users`  WHERE `users`.`id` = 1
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
|  1 | SIMPLE      | users | const | PRIMARY       | PRIMARY | 4       | const |    1 |       |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
1 row in set (0.00 sec)

EXPLAIN for: SELECT `posts`.* FROM `posts`  WHERE `posts`.`user_id` IN (1)
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | posts | ALL  | NULL          | NULL | NULL    | NULL |    1 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

20.1 EXPLAINについての解説

EXPLAINの出力の解説については、このガイドの範疇を超えているため、 下記のリンク先を参照してください。

 Back to top

© 2010 - 2017 STUDIO KINGDOM