LoginSignup
15
17

More than 5 years have passed since last update.

ActiveRecord::Relationのオブジェクトに対してメソッドを追加したい時

Last updated at Posted at 2017-05-10

はじめに

ActiveRecord::Relationはレコードの集まりであるが、Railsでアプリケーションを開発している時に、ActiveRecord::Relationに対してのメソッドを定義したい時が幾度もある。具体的な例とその手法を見ていきたい。
本編

ActiveRecord::BaseActiveRecord::Relationの関係

各々のモデル、ActiveRecord::BaseActiveRecord::Relationのオブジェクトに対応推している。
例えば、Userというモデルについて考えてみる。
現在、usersテーブルにはレコードが下の様に入ってたとする。

id name email power(億単位)
1 カカロット ninjin@saiya.com 130
2 ベジータ yasai@saiya.com 75
3 18号 kuririn.love@kikai.com 13
4 セル sell@kanzentai.com 300
5 ピッコロ gohan.love@namekku.com 20
6 ゴハン gohan@saiya.com 300

Userのレコードを全て所得すれば、以下のように表示される。

User.all
=> #<ActiveRecord::Relation [#<User id: 1, name: "カカロット", email: "ninjin@saiya.com", power: "130">, #<User id: 2, name: "ベジータ", email: "yasai@saiya.com", power: "75億"> ... 省略]>

つまり、 ActiveBase::Relation クラスのインスタンスを返しているのである。

all

おそらく、最も一般的なものはクラスメソッド内で、allを使う手法である。
考えてみれば、当たり前なんだけど気づかなんだ。
例:ActiveRecord::Relation内にフリーザーよりも強いやつが一人でもいたらtrueを返すメソッド

class User
  def self.can_beat_freezer?
     # 便宜上、Freezer.power が 0.0053 (53万) を返すことにします。
     # 上記のメンバーだとヘナチョコのボコボコのペチャペチャのギッタンギッタンですね
     all.pluck(:power).any? { |power| power > Freezer.power }
  end
end

他にも、relation method などありますが、allrelationを間接的に呼び出しているので、allの方がいいでしょう。
バージョンが低い場合は(3以下かな?) @relationscoped などがあったようです。(よく知らない)。

クラス化

このように少しでもロジックが入ってしまうと、メソッドにしまいたいですね。
今回のように、戦闘力を比較して解決してしまう比較的単純なメソッドならばする必要はゼロなのですが、比較的複雑なメソッドをこのようにどんどんモデルにメソッドを足していくとデブモデルが出来てしまい、収拾がつかなくなります。そこで、このようなメソッドはクラスにするのがいいでしょう。

beating_freeza.rb
class BeatingFreeza
  class << self
     def possible?(collection)
       new(collection).possible?
     end
  end

  def initialize(collection)
    @collection = collection 
  end

  def possible?
     collection.pluck(:power).any? { |power| power > Freezer.power } ||
     collection.any? { |user| user.super_saiya? } ||
     # ....... 以下戦闘力だけではなく、環境や頭の回転などを考慮するようになってくるとこのようにするのがいいでしょう。
     # ドラゴンボールは戦闘力で決まってしまうので、ハンターハンターとかのときには、クラスを別途で作りましょう。
  end
end
user.rb
class User < ActiveRecord::Base
  def self.can_beat_freeza?
    BeatingFreeza.possible?(all)   
  end
end

ちなみに

ドドリアなどの部下(果物)やサイヤ人(野菜)、リクームとかギニュウ(クリームと牛乳)を統括するフリーザ(冷凍庫)っていうことらしい。それがスーパーな人参に負けてしまうんだから、奇跡だな。

15
17
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
15
17