1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ActiveRecordモデルの拡張検討

Last updated at Posted at 2022-09-17

ActiveRecordモデルの拡張方法にはいくつか選択肢があるので、どういった方法を選ぶのが適切かよくよく考えなおしてみたいと思います

今回のストーリー

新規ユーザーは必ず無料のサンプルを受け取ることができるので、ユーザーが作成されたときに、無料サンプル用の注文を作成したい
ただし、受け取れる無料サンプルは一人一つまでで、無料サンプルを提供する販売元と無料サンプルはコードで指定したい

販売元コード: DEFAULT
商品コード: JOIN

スクリーンショット 2022-09-17 11.01.30.png

2種類の拡張方法を試す

  1. scopeを使った拡張を使った拡張
  2. AssociationExtentionを使ったhas_manyリレーションの拡張

1.Scopeを使った拡張

Scopeを使った拡張ではクラス本体が拡張される
そのクラスを参照する全ての場合に利用できる
buildやcreateにも利用することが可能
ActiveRecord::Scoping::Named::ClassMethods

まずは特定条件でレコードがフィルタリングできることを確認します

:pencil: 実装

class Order < ApplicationRecord
  belongs_to :customer
  belongs_to :item
  # 発送前の注文をフィルタするスコープを定義
  scope :not_yet_shipped, -> { where("shipment_date < ?", Time.zone.now)}
end

:computer: 検証
下記の2つの呼び出し方法で同じSQLが発行されていることを確認します

> Order.not_yet_shipped
Order Load (0.5ms)  SELECT `orders`.* FROM `orders` WHERE (shipment_date < '2022-09-17 01:23:32.786304')

> Customer.last.orders.not_yet_shipped
Order Load (0.5ms)  SELECT `orders`.* FROM `orders` WHERE (shipment_date < '2022-09-17 01:23:32.786304')

次に、スコープからレコードが作成できることも確認します
ただし、この場合には、「無料のサンプルは一人ひとつまで」という制約を入れたい場合に、scopeの中だけでは拡張できないので、create_join_bonusをメソッドとして定義することになります

:pencil: 実装

class Order < ApplicationRecord
  belongs_to :customer
  belongs_to :item
  # キャンペーンの注文をフィルタするスコープの定義
  scope :campaign_order, -> {
    where(item: Item.where({seller: Seller.where(code: 'DEFAULT').first, campaign_code: 'JOIN'}).last)
  }

 # 無料サンプル用のレコードを作成
  def create_join_bonus
    find_or_create_by!(item: find_join_bonus) do |order|
      order.shipment_date = Time.zone.now + 3.days
    end
  end

 # 無料サンプル用のレコードを検索
  def find_join_bonus
    seller = Seller.where(code: 'DEFAULT').first
    Item.where({seller: seller, campaign_code: 'JOIN'}).last
  end
end

:computer: 検証
campaign_orderのスコープからだと何件も同じレコードを作成できてしまう抜け道があることを確認する

> Customer.last.orders.campaign_order.create
=> #<Order:0x00007fc84e190c30
 id: 3,
 customer_id: 3,
 item_id: 1,
 shipment_date: nil,
 created_at: Sat, 17 Sep 2022 01:07:54 UTC +00:00,
 updated_at: Sat, 17 Sep 2022 01:07:54 UTC +00:00>

> Customer.last.orders.campaign_order
=> [#<Order:0x00007fc84e23bf68
  id: 2,
  customer_id: 3,
  item_id: 1,
  shipment_date: nil,
  created_at: Sat, 17 Sep 2022 01:07:52 UTC +00:00,
  updated_at: Sat, 17 Sep 2022 01:07:52 UTC +00:00>,
 #<Order:0x00007fc84e23be00
  id: 3,
  customer_id: 3,
  item_id: 1,
  shipment_date: nil,
  created_at: Sat, 17 Sep 2022 01:07:54 UTC +00:00,
  updated_at: Sat, 17 Sep 2022 01:07:54 UTC +00:00>]

2.AssociationExtentionを使ったhas_manyリレーションの拡張

Active Record の関連付け - Railsガイド

Orderモデルは一般的なECサイトなどでは、必然的に非常にたくさんの機能を抱えることになります

「無料のサンプルは一人ひとつまで」という業務要件は、Customerモデルの1行(=一人)に対して、紐づく独自要件なのでキャンペーンの要件としてひとくくりにしてて、モジュールに切り出したいと思います

そこで、has_manyリレーション内で、新たに作成されたCampaignExtension を使って拡張したいと思います

この場合には、クラス本体は拡張されず、orders経由でレコードが作成される場合にのみ拡張されます

has_oneやbelongs_toのときには利用できないので注意しましょう

:pencil: 実装

class Customer < ApplicationRecord
  has_many :orders, -> { extending CampaignExtension },
           class_name: 'Order', inverse_of: :customer
end

app/models/concerns/campaign_extension.rb を新たに作成します
create_join_bonusというメソッドに「無料のサンプルは一人ひとつまで」という制限をまとめて実装してしまいましょう

module CampaignExtension
  def create_join_bonus
    find_or_create_by!(item: find_join_bonus) do |order|
      order.shipment_date = Time.zone.now + 3.days
    end
  end

  def find_join_bonus
    seller = Seller.where(code: 'DEFAULT').first
    Item.where({seller: seller, campaign_code: 'JOIN'}).last
  end
end

orderモデルからは、ロジックを削除して、このようにシンプルな形に戻しましょう

class Order < ApplicationRecord
  belongs_to :customer
  belongs_to :item
  # 発送前の注文をフィルタするスコープを定義
  scope :not_yet_shipped, -> { where("shipment_date < ?", Time.zone.now)}

:computer: 検証検証
何度呼び出しても重複レコードが作成されないことを確認しましょう

> Customer.last.orders.create_join_bonus
=> #<Order:0x00007fd528524568
 id: 1,
 customer_id: 2,
 item_id: 1,
 shipment_date: Sun, 18 Sep 2022 15:09:10 UTC +00:00,
 created_at: Thu, 15 Sep 2022 15:09:10 UTC +00:00,
 updated_at: Thu, 15 Sep 2022 15:09:10 UTC +00:00>

>  Customer.last.orders
=> [#<Order:0x00007fc84e23bf68
  id: 2,
  customer_id: 3,
  item_id: 1,
  shipment_date: nil,
  created_at: Sat, 17 Sep 2022 01:07:52 UTC +00:00,
  updated_at: Sat, 17 Sep 2022 01:07:52 UTC +00:00>,
 #<Order:0x00007fc84e23be00
  id: 3,
  customer_id: 3,
  item_id: 1,
  shipment_date: nil,
  created_at: Sat, 17 Sep 2022 01:07:54 UTC +00:00,
  updated_at: Sat, 17 Sep 2022 01:07:54 UTC +00:00>]

「無料のサンプルは一人ひとつまで」という制限がActiveRecordの機能を通して実現できたというよりは、制限を置く場所を変えてあげると、あとからメソッドを利用する人が間違えにくくなるという、認識を持っていただけれと思います
また、ドメインとモデルの分離をしてFat Modelをダイエットさせるというときにも役に立つと思います

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?