ActiveRecord::Baseを継承しているモデルのコレクションに対してメソッドを追加したいことがたまにあると思います。例えば次のような画面で
今表示しているsalesの平均価格を表示したいというような場合です。viewには次ような感じで書きたいんじゃないでしょうか。
📄app/views/sales/index.html.erb
... Average of prices: <%= @sales.average_price %> ...
Railsではこんなことも簡単に実現できます。モデルにメソッドを追加すれば良いだけ。
📄app/models/sale.rb
class Sale < ActiveRecord::Base def self.average_price all.average(:price) end end
(rails4で動作確認しています。rails3のallは配列を返すので、scopedにすれば動くと思います)
一体どういう仕組でこのメソッドが呼び出されてるんでしょうか。ちょっとpry-stack_explorerで確認してみると・・・
📄call stack
Showing all accessible frames in stack (116 in total): -- => #0 average_price <Sale.average_price()> #1 [block] block in run <PryByebug::Processor#run(initial=?, &block)> #2 [method] run <PryByebug::Processor#run(initial=?, &block)> #3 [method] start_with_pry_byebug <Pry.start_with_pry_byebug(target=?, options=?)> #4 [method] pry <Object#pry(object=?, hash=?)> #5 [method] average_price <Sale.average_price()> #6 [block] block in method_missing <ActiveRecord::Delegation::ClassSpecificRelation#method_missing(method, *args, &block)> #7 [method] scoping <ActiveRecord::Relation#scoping()> #8 [method] method_missing <ActiveRecord::Delegation::ClassSpecificRelation#method_missing(method, *args, &block)> #9 [method] _app_views_sales_index_html_erb__3326902891793499470_70306254138640 <ActionView::CompiledTemplates#_app_views_sales_index_html_erb__3326902891793499470_70306254138640(local_assigns, output_buffer)> #10 [block] block in render <ActionView::Template#render(view, locals, buffer=?, &block)> ...
method_missingを使ってデリゲートしているようです。黒魔術の定石ですね。このActiveRecord::Delegation::ClassSpecificRelationをのぞいてみます。
📄activerecord/lib/active_record/relation/delegation.rb
# File activerecord/lib/active_record/relation/delegation.rb, line 57 def method_missing(method, *args, &block) if @klass.respond_to?(method) self.class.delegate_to_scoped_klass(method) scoping { @klass.send(method, *args, &block) } elsif Array.method_defined?(method) self.class.delegate method, :to => :to_a to_a.send(method, *args, &block) elsif arel.respond_to?(method) self.class.delegate method, :to => :arel arel.send(method, *args, &block) else super end end
なるほどこうなっているのか。。Arrayにもデリゲートできるようですね(to_aされてしまうのでscopeチェインができなくなりますが)。う〜む・・こうしてみると、やはりrubyは偉大だと言わざるをえない。
関連する記事
- 【実践】RailsでExcelレポート出力(その1)
- ActiveRecord依存のModuleをrspecでテストする
- chosen-railsによる検索機能付きセレクトボックスで、検索画面作成の手間を省く
- 久しぶりにRailsで開発して感じたこと
- Railsアプリを『浅く』パフォーマンス・チューニングしてみる(その1)