arait-code’s RC

もうすぐエンジニア転職して2年になります。

railsのActiveRecordでandとorの複合条件の絞り込みをする時

railsActiveRecord
AかつBorC(A and (B or C))の条件を満たす書き方のメモです。

結論: .mergeを使用する

.where(条件A)
.merge(モデル名.where(条件B).or(モデル名.where(条件c)))

SQLを書いてしまった方が楽なのですがActiveRecordで書きたい場面があったので
何らかの理由でSQLを書きたくない、そういった時に使います。

今回想定するテーブル構造は下記です。

テーブル構造

class MasterProduct < ApplicationRecord
    has_many :order_product
end

# ### Columns
#
# Name                                       | Type               | Attributes
# ------------------------------------------ | ------------------ | ---------------------------
# **`created_at`**                           | `datetime`         | `not null`
# **`distribution_packaging_code`**          | `string(14)`       |
# **`id`**                                   | `integer`          | `not null, primary key`
# **`jan_code`**                             | `string(13)`       | `not null`    |
# **`updated_at`**                           | `datetime`         | `not null`
class OrderProduct < ApplicationRecord
 belongs_to :master_product
end

# ### Columns
#
# Name                         | Type               | Attributes
# ---------------------------- | ------------------ | ---------------------------
# **`created_at`**             | `datetime`         | `not null`
# **`id`**                     | `integer`          | `not null, primary key`
# **`pharmacy_id`**            | `integer`          | `not null`
# **`wholesaler_id`**          | `integer`          |
# **`master_product_id`**      | `integer`          | `not null`

MasterProductに紐づくOrderProductテーブルがある
OrderProductのpharmacy_idがxかつMasterProductのdistribution_packaging_codeかjan_codeが指定の値のレコードを取り出す

SQLにするとこう

(SELECT 
 order_products.wholesaler_id,
    order_products.master_product_id
  FROM
 order_products
    INNER JOIN master_products
    ON master_products.id = order_products.master_product_id
  WHERE order_products.pharmacy_id = :pharmacy_id
  AND (master_products.distribution_packaging_code = :distribution_packaging_code
                         OR master_products.jan_code = :jan_code)
  ORDER BY order_appointed_on DESC, order_products.created_at DESC
  LIMIT 1)

ActiveRecordにするとこう

OrderProduct.select(:id)
    .where(pharmacy_id: pharmacy.id)
    .joins(:master_product)
    .merge(MasterProduct.where(distribution_packaging_code: params[:id]).or(MasterProduct.where(jan_code: params[:id])))
    .order(order_appointed_on: :desc, created_at: :desc)
    .limit(1)