web-dev-qa-db-fra.com

Rails étendant ActiveRecord :: Base

J'ai lu quelques articles sur la manière d'étendre ActiveRecord: classe de base afin que mes modèles disposent de méthodes spéciales. Quel est le moyen facile de l'étendre (tutoriel pas à pas)?

159
xpepermint

Il y a plusieurs approches:

Utilisation de ActiveSupport :: Concern (Preferred)

Lisez la documentation ActiveSupport :: Concern pour plus de détails.

Créez un fichier appelé active_record_extension.rb Dans le répertoire 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)

Créez un fichier dans le répertoire config/initializers Appelé extensions.rb Et ajoutez la ligne suivante au fichier:

require "active_record_extension"

Héritage (préféré)

Voir Toby réponse .

Le singe patcher (devrait être évité)

Créez un fichier dans le répertoire config/initializers Appelé 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

La fameuse citation sur les expressions rationnelles de Jamie Zawinski peut être réaffectée pour illustrer les problèmes associés à la correction de singe.

Certaines personnes, confrontées à un problème, se disent: "Je sais, je vais utiliser la correction de singe". Maintenant, elles ont deux problèmes.

La réparation de singe est facile et rapide. Mais, le temps et les efforts économisés sont toujours extraits dans le futur; avec intérêt composé. Ces jours-ci, je limite l'application des correctifs à un singe à la création rapide d'un prototype de solution dans la console Rails.

334
Harish Shetty

Vous pouvez simplement étendre la classe et simplement utiliser l'héritage.

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

class Foo < AbstractModel
end

class Bar < AbstractModel
end
70
Toby Hede

Vous pouvez aussi utiliser ActiveSupport::Concern et être plus Rails idiomatique de base comme:

module MyExtension
  extend ActiveSupport::Concern

  def foo
  end

  module ClassMethods
    def bar
    end
  end
end

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

[Edit] suite au commentaire de @daniel

Tous vos modèles auront alors la méthode foo incluse en tant que méthode d’instance et les méthodes de ClassMethods incluses en tant que méthodes de classe. Par exemple. sur un FooBar < ActiveRecord::Base Tu vas avoir: FooBar.bar et FooBar#foo

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

21
nikola

Avec Rails 4, le concept d’utilisation des préoccupations pour modulariser et DRY vos modèles ont été mis en évidence.

Les problèmes vous permettent en principe de regrouper le code similaire d'un modèle ou de plusieurs modèles dans un seul module, puis d'utiliser ce module dans les modèles. Voici un exemple:

Considérez un modèle d'article, un modèle d'événement et un modèle de commentaire. Un article ou un événement a beaucoup de commentaires. Un commentaire appartient à l'article ou à l'événement.

Traditionnellement, les modèles peuvent ressembler à ceci:

Commentaire modèle:

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

Modèle d'article:

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

Modèle d'événement

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

Comme nous pouvons le constater, il existe un élément de code important commun aux modèles d’événement et d’article. En utilisant des préoccupations, nous pouvons extraire ce code commun dans un module séparé Commentable.

Pour cela, créez un fichier commentable.rb dans app/model/concerne.

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

Et maintenant, vos modèles ressemblent à ceci:

Commentaire modèle:

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

Modèle d'article:

class Article < ActiveRecord::Base
    include Commentable
end

Modèle d'événement

class Event < ActiveRecord::Base    
    include Commentable
end

Un point que je voudrais souligner lors de l’utilisation de Concerns est que Les préoccupations doivent être utilisées pour un groupement "basé sur un domaine" plutôt que pour un groupement "technique". Par exemple, un groupe de domaines s'apparente à 'Commentable', 'Taggable', etc. Un groupe technique s'apparente à 'FinderMethods', 'ValidationMethods'.

Voici un lien vers un message que j'ai trouvé très utile pour comprendre les préoccupations de modèles.

J'espère que la rédaction aide :)

17
Aaditi Jain

étape 1

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

étape 2

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

étape

There is no step 3 :)
7
Vitaly Kushner

Rails 5 fournit un mécanisme intégré pour prolonger ActiveRecord::Base.

Ceci est réalisé en fournissant une couche supplémentaire:

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

et tous les modèles héritent de celui-là:

class Post < ApplicationRecord
end

Voir par exemple cet article de blog .

5
Adobe

Avec Rails 5, tous les modèles sont hérités de ApplicationRecord, ce qui permet à Nice d’inclure ou d’étendre d’autres bibliothèques d’extensions.

# 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

Supposons que le module de méthodes spéciales doit être disponible sur tous les modèles, incluez-le dans le fichier application_record.rb. Si nous souhaitons l'appliquer à un ensemble de modèles particulier, incluez-le dans les classes de modèles respectives.

# 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

Si vous souhaitez que les méthodes définies dans le module soient des méthodes de classe, étendez le module à ApplicationRecord.

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

J'espère que ça aidera les autres!

4
Ashik Salman

Juste pour ajouter à ce sujet, j'ai passé un certain temps à essayer de tester de telles extensions (j'ai descendu le ActiveSupport::Concern route.)

Voici comment j'ai configuré un modèle pour tester mes extensions.

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
Will Tomlins

J'ai

ActiveRecord::Base.extend Foo::Bar

dans un initialiseur

Pour un module comme ci-dessous

module Foo
  module Bar
  end
end
0
Ed Richards