delegateをしっかり理解しないまま使っていたので、調べようと思います。
solidus内のdelegate
solidusのmodelからdelegateが使われている場所を抜粋してみます。
has_one :master,
-> { where(is_master: true).with_deleted },
inverse_of: :product,
class_name: 'Spree::Variant',
autosave: true
...
def find_or_build_master
master || build_master
end
...
MASTER_ATTRIBUTES = [
:cost_currency,
:cost_price,
:depth,
:height,
:price,
:sku,
:weight,
:width,
]
MASTER_ATTRIBUTES.each do |attr|
->delegate :"#{attr}", :"#{attr}=", to: :find_or_build_master
end
->delegate :amount_in,
:display_amount,
:display_price,
:has_default_price?,
:images,
:price_for,
:price_in,
:rebuild_vat_prices=,
to: :find_or_build_master
alias_method :master_images, :images
例えば、spree/product.rbから抜粋した上記の部分ですが、いくつかのメソッドまたはテーブルカラムがto: :find_or_build_masterに delegateされています。
delegateとは
[英語の意味]
delegate: 【自動】
権限を委任[委譲・委託・委嘱]する
【他動】
〔人を〕代表[代理]に立てる[として派遣する]
〔権限・任務などを人に〕委任する、委譲する、委託する、委嘱する
APIドックによれば
delegate(*methods, to: nil, prefix: nil, allow_nil: nil)
Provides a delegate class method to easily expose contained objects' public methods as your own.
Options
:to - Specifies the target object
:prefix - Prefixes the new method with the target name or a custom prefix
:allow_nil - if set to true, prevents a Module::DelegationError from being raised
The macro receives one or more method names (specified as symbols or strings) and the name of the target object via the :to option (also a symbol or string).
Delegation is particularly useful with Active Record associations:
[https://api.rubyonrails.org]
使用例
class Aisatsu < ActiveRecord::Base
def hello
'hello'
end
def goodbye
'goodbye'
end
end
class Taro < ActiveRecord::Base
has_one :aisatsu
delegate :hello, to: :Aisatsu
end
taro = Taro.new
taro.hello # => "hello"
taro.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
というかんじで、delegate はメソッドを異なるクラス間で簡単に使えるようにするマクロ的な使い方ができることが分かります。
上記の例ではAisatsuクラスとTaroクラスが関連付けがされています。
本来なら、taro.aisatsu.helloとしなければならないところを、delegateで直接helloメソッドを使えるようにしています。
イメージとしては
delegate :hello, to: :Aisatsuとしてあげると
hello = (aisatsu.hello)相当として使用できるようになるので、taro.(aisatsu.)helloが使えるようになるという感じでしょうか。
他にもto: 〇〇には、定数to: :CONSTANTや、@インスタンス変数to: :@instanceを当てはめることもできます。
本題に戻る
本題のsolidusのproduct.rbに戻ります。
has_one :master,
-> { where(is_master: true).with_deleted },
inverse_of: :product,
class_name: 'Spree::Variant',
autosave: true
...
def find_or_build_master
master || build_master
end
...
MASTER_ATTRIBUTES = [
:cost_currency,
:cost_price,
:depth,
:height,
:price,
:sku,
:weight,
:width,
]
MASTER_ATTRIBUTES.each do |attr|
->delegate :"#{attr}", :"#{attr}=", to: :find_or_build_master
end
->delegate :amount_in,
:display_amount,
:display_price,
:has_default_price?,
:images,
:price_for,
:price_in,
:rebuild_vat_prices=,
to: :find_or_build_master
alias_method :master_images, :images
抜粋部分では 二箇所でdelegateが使用されています。
"#{attr}", :"#{attr}="というような複雑な書き方をしているところもありますが、今回はそちらは置いておいてto: :find_or_build_masterという部分に注目します。
:find_or_build_masterはproduct.rbで定義されているインスタンスメソッドです。
def find_or_build_master
master || build_master
end
使用例に当てはめてみると、本来なら関係付けを利用してproduct.master.price としなければいけないところを、product.priceと直接productモデルのインスタンスproductから使えるようにしているわけですね。
下のdelegateも同様にproduct.master.imagesとしなければいけないところを、product.imagesと'master'を省略できるようにしているというわけです。
今回は以上になります!