ActiveRecord クエリインタフェースのまとめ


1年くらい前にRubyの勉強のためにActiveRecordクエリインタフェースでよく利用するメソッドをまとめたもの

参考
・https://railsguides.jp/active_record_querying.html


# オブジェクトの取り出し
## 単一のオブジェクトを取り出す
find
主キーに対するオブジェクトを返す

find_by
キーを指定して対するオブジェクトを返す

first
主キー順の最初のレコードを取り出す

last
主キー順の最後のレコードを取り出す


## 複数のオブジェクトをバッチで取り出す
find_each
分割してレコードを取得して処理をする。
デフォルトでは1000件づつ取得する。
各レコードを1つのモデルとして個別にブロックにyieldする。

主なオプション
・batch_size：1回のバッチで取り出すレコード数を指定できる。
・begin_at：ある主キーのIDより小さいIDを除外したい場合に指定できる。バッチ処理の開始位置を指定。
・end_at：ある主キーのIDより大きいIDを除外したい場合に指定できる。バッチ処理の終了位置を指定。

find_each_batches
find_eachとほぼ同じ。違いは各レコードを個別にではなくモデルの配列としてブロックにyieldする。

# 条件
where
条件を指定して条件に当てはまるレコードをすべて取得する。
文字列、配列、Hash、nilで指定できる。ハッシュで指定することが多い。

``` ruby
# Hashで指定
Book.where(category_id: 1)

# SQL
# SELECT books.* FROM books WHERE books.category_id = 1
# to_sqlメソッドを使うとSQLを表示できて便利
# Book.where(category_id: 1).to_sql

# 文字列指定
Book.where("category_id = '1'")
# 配列指定
Book.where("category_id = ?", params[: category_id])
```

## 範囲指定
rangeを渡すとBetween文で範囲指定してくれるので便利

``` ruby
Book.where(created_at: Time.now.in_time_zone.all_month)
# SQL
SELECT books.* FROM books WHERE books.created_at BETWEEN '2016-06-01 00:00:00' AND '2016-06-30 23:59:59' )
```

## NOT条件
SQLのNOTクエリはwhere.notで表せる

# 並び替え
orderメソッドを使用する

``` ruby
Book.order(created_at: :desc)
Book.order(created_at: :asc)
```

# 特定のフィールドだけ取り出す
select
特定のフィールドだけを取り出す

``` ruby
Book.select(:name).distinct
# distinct：重複排除
```

# グループ
group
GROUP BY句を追加する

``` ruby
Book.select(:category_id).group(:category_id)
```

# Having
having
HAVING句を追加する

``` ruby
Book.select(:category_id).group(:category_id).having("count(category_id) > ?", 100).count()
```


# 条件を上書きする
unscope
特定の条件を取り除く

``` ruby
# unscopeなし
Book.select(:id).where('id > 400').order('id asc').to_sql
# => "SELECT `books`.`id` FROM `concerts` WHERE (id > 400) ORDER BY id asc"

# unscopeあり
Book.select(:id).where('id > 400').order('id asc').unscope(:where, :order, :select).to_sql
# => "SELECT `books`.* FROM `concerts`"
```

only
条件を上書きする（指定したクエリのみ使用する）

``` ruby
# onlyなし
Book.select(:id).where('id > 400').order('id asc').to_sql
# => "SELECT `books`.`id` FROM `concerts` WHERE (id > 400) ORDER BY id asc"

# onlyあり
Book.select(:id).where('id > 400').order('id asc').only(:where, :select).to_sql
# => "SELECT `books`.`id` FROM `concerts` WHERE (id > 400) 
```

reorder
デフォルトのスコープの並び順を上書きする

``` ruby
# デフォルトで並び順を設定していたとする
class Book < ApplicationRecord
   default_scope-> {order(:created_at)}
end


# 新しい順で欲しくなったんだけど… (他に名前順で…）
Book.order('created_at DESC')
# => ORDER BY created_at ASC, created_at DESC

Book.order(:name)
# => ORDER BY created_at ASC, name ASC

# reorderを使えば
Book.reorder('created_at DESC')
# => ORDER BY created_at DESC
```

reverse_order
並び順が指定されている場合に並び順を逆にする

rewhere
既存のwhere条件を上書きする

# Nullリレーション
none
空のリレーションを返す。レコードは返さない。
メソッドまたはスコープへの連鎖可能な応答が必要だけど結果を返したくない場合に便利

# 読み取り専用オブジェクト
readonly
返されたどのオブジェクトに対しても変更を明示的に禁止する

# テーブルのロック
レコードを更新できないようロックする
楽観的ロック(optimatic)
複数のユーザーが同じレコードを編集することを許す。
テーブルにlock_versionという名前のinteger型カラムが必要。

悲観的ロック(pessimistic)
データベースが提供するロック機構を使用する。

# テーブル結合
join
テーブルを関連付ける。(INNER JOIN)

``` ruby
# ネストした関連付け(単一)
Book.joins(comments: :guest)

# ネストした関連付け(複数)
Book.joins({ comments: :guest }, :tags)
```

関連付けを一括読み込みする
N+1問題を解決するためeager loadingを行う。
includes,eager_load, preloadメソッドがある。
基本的にはincludesで良いが必ずJOINしたい場合はeager_load、JOINしたくない場合はpreloadを使う。

こちらのqiita記事がオススメ
http://qiita.com/k0kubun/items/80c5a5494f53bb88dc58


# スコープ
関連オブジェクトやモデルへのメソッド呼び出しとして参照される、よく使用されるクエリを指定することができる。

``` ruby
class Book < ApplicationRecord
   scope :publishe, -> { where(:published: true) }
end

# 引数あり
class Book < ApplicationRecord
   scope :created_before, ->(time) { where("created_at < ?", time) }
end
```
# メソッドチェイン
メソッドを連続的に呼び出すこと

# 新しいオブジェクトを検索またはビルドする
find_or_create_by
指定された属性を持つレコードが存在するかどうかをチェックし存在しなければcreateする。

新しいレコードの作成時'locked'属性をfalseに設定したいが、それをクエリに含めたくない場合

``` ruby
# create_withを使うパターン
Book.create_with(locked: false).find_or_create_by(name: 'Effective Ruby')


# ブロックを使うパターン
Book.find_or_create_by(name: 'Effective Ruby') do |c|
  c.locked = false
end
```
find_or_create_by!
新しいレコードが無効な場合に例外を発生させることが可能

find_or_initialize_by
find_or_create_byと同様に動作するがcreateではなくnewを呼ぶ

# SQLで検索
find_by_sql
独自のSQLを使用してレコードを検索できる。配列が返ってくる。

select_all
独自のSQLを使用してレコードを検索できる。Hashの配列が返ってくる。

pluck
テーブルから指定したカラムを取得する

ids
主キーを利用するリレーションのIDをすべて取得する

# オブジェクトの存在チェック
exists?
オブジェクトが存在するかを確認する。
発行するクエリはfindと同様

# 計算
count, average, max, min, sumのメソッドがある。

# Explain
explain を用いる

EXPLAIN
https://dev.mysql.com/doc/refman/5.6/ja/explain-output.html
http://nippondanji.blogspot.jp/2009/03/mysqlexplain.html
http://qiita.com/mucho0623/items/fa575002bf0ade699e2f