ActiveRecord :: Baseを拡張するRails


160

ActiveRecord:Baseクラスを拡張してモデルにいくつかの特別なメソッドを追加する方法についていくつか読んだことがあります。それを拡張する簡単な方法は何ですか(ステップバイステップチュートリアル)?


どのような拡張機能ですか?続行するにはさらに多くのことが必要です。
jonnii 2010

回答:


336

いくつかのアプローチがあります:

ActiveSupport :: Concernを使用する(推奨)

詳細については、ActiveSupport :: Concernのドキュメントをご覧ください。

ファイルを作成active_record_extension.rbしてlibディレクトリを。

require 'active_support/concern'

module ActiveRecordExtension

  extend ActiveSupport::Concern

  # add your instance methods here
  def foo
     "foo"
  end

  # add your static(class) methods here
  class_methods do
    #E.g: Order.top_ten        
    def top_ten
      limit(10)
    end
  end
end

# include the extension 
ActiveRecord::Base.send(:include, ActiveRecordExtension)

config/initializers呼び出されたディレクトリにファイルを作成しextensions.rb、次の行をファイルに追加します。

require "active_record_extension"

継承(推奨)

トビーの答えを参照してください。

サルのパッチ(回避する必要があります)

というconfig/initializersディレクトリにファイルを作成しますactive_record_monkey_patch.rb

class ActiveRecord::Base     
  #instance method, E.g: Order.new.foo       
  def foo
   "foo"
  end

  #class method, E.g: Order.top_ten        
  def self.top_ten
    limit(10)
  end
end

Jamie Zawinskiによる正規表現に関する有名な引用は、サルのパッチングに関連する問題を説明するために再利用できます。

一部の人々は、問題に直面したとき、「わかっている、私はサルのパッチを使用する」と思います。今、彼らは2つの問題を抱えています。

サルのパッチは簡単かつ迅速です。ただし、節約された時間と労力は、常に将来のある時点で戻されます。複利で。最近、私はRailsコンソールでソリューションを迅速にプロトタイプ化するために、サルのパッチ適用を制限しています。


3
requireの最後のファイルにする必要がありますenvironment.rb。私はこの追加のステップを私の答えに追加しました。
Harish Shetty 2010

1
@HartleyBrodyそれは好みの問題です。継承を使用する場合は、新しく導入してImprovedActiveRecordそこから継承する必要があります。を使用moduleしている場合は、問題のクラスの定義を更新します。私は継承を使用していました(長年のJava / C ++の経験が原因)。最近は主にモジュールを使用しています。
Harish Shetty 2013年

1
あなたのリンクが実際に文脈化されていて、人々が引用を誤用し、使いすぎていることを指摘しているのは少し皮肉なことです。しかし、真剣に考えれば、この場合「サルのパッチ」が最善の方法ではない理由を理解するのに苦労しています。複数のクラスに追加したい場合は、明らかにモジュールが適しています。しかし、目標が1つのクラスを拡張することである場合、Rubyがこの方法でクラスを拡張するのをとても簡単にしたのはそうではありませんか?
MCB 2013年

1
@MCB、すべての大きなプロジェクトには、サルのパッチ適用のために導入された、見つけにくいバグに関するストーリーはほとんどありません。パッチ適用の弊害に関するAvdiの記事は次のとおりです:devblog.avdi.org/2008/02/23/…。Ruby 2.0には、Refinementsサルのパッチに関する問題のほとんどに対処するという新しい機能が導入されています(yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-practice)。運命を誘惑するためだけに機能が存在する場合があります。そして時にはあなたはそうします。
Harish Shetty 2013年

1
@TrantorLiuはい。回答を更新して最新のドキュメントを反映しました(class_methodsが2014github.com/rails/rails/commit/…で導入されたように見えます)
Harish Shetty

70

クラスを拡張するだけで、継承を使用できます。

class AbstractModel < ActiveRecord::Base  
  self.abstract_class = true
end

class Foo < AbstractModel
end

class Bar < AbstractModel
end

これが標準的な方法であるため、このアイデアが好きですが...エラー「テーブル 'boboolo_development.abstract_models'が存在しません:SHOW FIELDS FROM」が発生しますabstract_models。どこに置けばいいですか?
xpepermint 2010

23
に追加self.abstract_class = trueしますAbstractModel。Railsはモデルを抽象モデルとして認識します。
Harish Shetty 2010

うわー!これが可能だとは思わなかった。以前にそれを試して、ActiveRecord AbstractModelがデータベース内を探して窒息したときはあきらめました。簡単なセッターが物事を乾かすのに役立つだろうと誰が知っていましたか?(私はうんざりし始めていた...それは悪かった)。トビーとハリッシュに感謝!
dooleyo 2013年

私の場合、これは間違いなくこれを行うための最良の方法です。ここでは、外部メソッドを使用してモデルの機能を拡張するのではなく、アプリの同様の動作オブジェクトの共通メソッドをリファクタリングします。継承は、ここでははるかに理にかなっています。優先する方法はありませんが、達成したいことに応じて2つの解決策があります!
オーガスティンリーディンガー2014

これはRails4では機能しません。私は、abstract_model.rbを作成し、modelsディレクトリーに入れました。モデルの内部にはself.abstract_class = true があり、他のモデルを継承しています... User <AbstractModel。コンソールでは、次のようになります。ユーザー(接続を確立するには 'User.connection'を呼び出します)
Joel Grannas '21年

21

次のActiveSupport::Concernように、より多くのRailsコアを慣用的に使用することもできます。

module MyExtension
  extend ActiveSupport::Concern

  def foo
  end

  module ClassMethods
    def bar
    end
  end
end

ActiveRecord::Base.send(:include, MyExtension)

[編集] @danielからのコメントに続く

次に、すべてのモデルに、メソッドfooがインスタンスメソッドとして含まれ、メソッドが ClassMethodsクラスメソッドとして含まれます。上の例FooBar < ActiveRecord::Baseあなたが持っているでしょう。FooBar.barFooBar#foo

http://api.rubyonrails.org/classes/ActiveSupport/Concern.html


5
InstanceMethodsRails 3.2から非推奨になっていることに注意してください。メソッドをモジュール本体に配置するだけです。
Daniel Rikowski

ActiveRecord::Base.send(:include, MyExtension)はイニシャライザを配置しましたが、これでうまくいきました。Rails 4.1.9
6ft Dan

18

Rails 4では、懸念事項を使用してモデルをモジュール化して乾燥させるという概念が強調されています。

懸念事項により、基本的には、モデルの類似したコードまたは複数のモデルにまたがる単一のモジュールにコードをグループ化し、このモジュールをモデルで使用できます。次に例を示します。

Articleモデル、Eventモデル、Commentモデルを考えてみましょう。記事またはイベントには多くのコメントがあります。コメントは記事またはイベントのいずれかに属しています。

従来、モデルは次のようになります。

コメントモデル:

class Comment < ActiveRecord::Base
  belongs_to :commentable, polymorphic: true
end

記事モデル:

class Article < ActiveRecord::Base
  has_many :comments, as: :commentable 

  def find_first_comment
    comments.first(created_at DESC)
  end

  def self.least_commented
   #return the article with least number of comments
  end
end

イベントモデル

class Event < ActiveRecord::Base
  has_many :comments, as: :commentable 

  def find_first_comment
    comments.first(created_at DESC)
  end

  def self.least_commented
   #returns the event with least number of comments
  end
end

お気づきのように、イベントモデルと記事モデルの両方に共通する重要なコードがあります。懸念事項を使用して、この共通コードを別のモジュールCommentableに抽出できます。

このため、app / model / concernsにcommentable.rbファイルを作成します。

module Commentable
    extend ActiveSupport::Concern

    included do 
        has_many :comments, as: :commentable 
    end

    # for the given article/event returns the first comment
    def find_first_comment
        comments.first(created_at DESC)
    end

    module ClassMethods     
        def least_commented
           #returns the article/event which has the least number of comments
        end
    end 
end

そして今あなたのモデルはこのようになります:

コメントモデル:

    class Comment < ActiveRecord::Base
      belongs_to :commentable, polymorphic: true
    end

記事モデル:

class Article < ActiveRecord::Base
    include Commentable
end

イベントモデル

class Event < ActiveRecord::Base    
    include Commentable
end

懸念を使用する際に強調したい点の1つは、懸念は「技術」グループではなく「ドメインベース」グループに使用する必要があることです。たとえば、ドメイングループは「コメント可能」、「タグ付け可能」などです。技術ベースのグループは「FinderMethods」、「ValidationMethods」のようになります。

これは、モデルの問題を理解するのに非常に役立つと思う投稿へリンクです。

記述が役立つことを願っています:)


7

ステップ1

module FooExtension
  def foo
    puts "bar :)"
  end
end
ActiveRecord::Base.send :include, FooExtension

ステップ2

# Require the above file in an initializer (in config/initializers)
require 'lib/foo_extension.rb'

ステップ3

There is no step 3 :)

1
ステップ2はconfig / environment.rbに配置する必要があると思います。それは私のために働いていない:(あなたには、いくつかのより多くの助けThxを記入してくださいできますか?。
xpepermint

5

Rails 5はを拡張するための組み込みメカニズムを提供しますActiveRecord::Base

これは、追加のレイヤーを提供することによって実現されます。

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  # put your extensions here
end

そして、すべてのモデルはそのモデルから継承します:

class Post < ApplicationRecord
end

たとえば、このブログ投稿を参照してください。


4

このトピックに追加するために、そのような拡張機能をテストする方法を検討するのにしばらく時間を費やしました(ActiveSupport::Concernルートをたどりました)。

拡張機能をテストするためのモデルを設定する方法は次のとおりです。

describe ModelExtensions do
  describe :some_method do
    it 'should return the value of foo' do
      ActiveRecord::Migration.create_table :test_models do |t|
        t.string :foo
      end

      test_model_class = Class.new(ActiveRecord::Base) do
        def self.name
          'TestModel'
        end

        attr_accessible :foo
      end

      model = test_model_class.new(:foo => 'bar')

      model.some_method.should == 'bar'
    end
  end
end

4

Rails 5では、すべてのモデルがApplicationRecordから継承され、他の拡張ライブラリを含めたり拡張したりするための優れた方法を提供します。

# app/models/concerns/special_methods.rb
module SpecialMethods
  extend ActiveSupport::Concern

  scope :this_month, -> { 
    where("date_trunc('month',created_at) = date_trunc('month',now())")
  }

  def foo
    # Code
  end
end

特別なメソッドモジュールをすべてのモデルで使用できるようにする必要があると想定し、application_record.rbファイルに含めます。これを特定のモデルセットに適用する場合は、それぞれのモデルクラスに含めます。

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  include SpecialMethods
end

# app/models/user.rb
class User < ApplicationRecord
  include SpecialMethods

  # Code
end

モジュールのメソッドをクラスメソッドとして定義する場合は、モジュールをApplicationRecordに拡張します。

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  extend SpecialMethods
end

それが他の人を助けることを願っています!


0

私が持っています

ActiveRecord::Base.extend Foo::Bar

イニシャライザ内

以下のようなモジュールの場合

module Foo
  module Bar
  end
end
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.