Rails étendant ActiveRecord :: Base


160

J'ai fait quelques lectures sur la façon d'étendre ActiveRecord: Classe de base afin que mes modèles aient des méthodes spéciales. Quelle est la manière simple de l'étendre (tutoriel étape par étape)?


Quel genre d'extensions? Nous avons vraiment besoin de plus pour continuer.
jonnii

Réponses:


336

Il existe plusieurs approches:

Utilisation d'ActiveSupport :: Concern (préféré)

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

Créez un fichier appelé active_record_extension.rbdans le librépertoire.

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 config/initializersrépertoire appelé extensions.rbet ajoutez la ligne suivante au fichier:

require "active_record_extension"

Héritage (préféré)

Reportez-vous à la réponse de Toby .

Patching de singe (doit être évité)

Créez un fichier dans le config/initializersrépertoire 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 célèbre citation sur les expressions régulières de Jamie Zawinski peut être réutilisée pour illustrer les problèmes associés à la correction des singes.

Certaines personnes, lorsqu'elles sont confrontées à un problème, pensent: «Je sais, j'utiliserai des patchs de singe.» Maintenant, ils ont deux problèmes.

La correction des singes est simple et rapide. Mais, le temps et les efforts économisés sont toujours récupérés dans le futur; avec intérêt composé. Ces jours-ci, je limite monkey patching pour prototyper rapidement une solution dans la console des rails.


3
Vous devez requireaccéder au fichier à la fin de environment.rb. J'ai ajouté cette étape supplémentaire à ma réponse.
Harish Shetty

1
@HartleyBrody c'est juste une question de préférence. Si vous utilisez l'héritage, vous devez en introduire un nouveau ImprovedActiveRecordet en hériter, lorsque vous utilisez module, vous mettez à jour la définition de la classe en question. J'utilisais l'héritage (à cause d'années d'expérience Java / C ++). Ces jours-ci, j'utilise principalement des modules.
Harish Shetty

1
Il est un peu ironique que votre lien contextualise et montre comment les gens abusent et abusent de la citation. Mais sérieusement, j'ai du mal à comprendre pourquoi le "patching de singe" ne serait pas le meilleur moyen dans ce cas. Si vous souhaitez ajouter plusieurs classes, un module est évidemment la voie à suivre. Mais si votre objectif est d'étendre une classe, n'est-ce pas pourquoi Ruby a rendu l'extension des classes de cette manière si facile?
MCB

1
@MCB, Chaque grand projet contient peu d'histoires sur un bug difficile à localiser introduit en raison du patching de singe. Voici un article d'Avdi sur les méfaits du patching: devblog.avdi.org/2008/02/23/… . Ruby 2.0 introduit une nouvelle fonctionnalité appelée Refinementsqui résout la plupart des problèmes liés au patching des singes ( yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-practice ). Parfois, une fonctionnalité est là juste pour vous contraindre à tenter le destin. Et parfois vous le faites.
Harish Shetty

1
@TrantorLiu Oui. J'ai mis à jour la réponse pour refléter la dernière documentation (on dirait que class_methods a été introduit en 2014 github.com/rails/rails/commit/… )
Harish Shetty

70

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

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

class Foo < AbstractModel
end

class Bar < AbstractModel
end

J'aime cette idée parce que c'est une manière standard de le faire mais ... j'obtiens une erreur La table 'moboolo_development.abstract_models' n'existe pas: SHOW FIELDS FROM abstract_models. Où dois-je le mettre?
xpepermint

23
Ajoutez self.abstract_class = trueà votre AbstractModel. Rails reconnaîtra désormais le modèle comme un modèle abstrait.
Harish Shetty

Hou la la! Je ne pensais pas que c'était possible. Je l'ai essayé plus tôt et a abandonné quand ActiveRecord s'est étouffé à la recherche du AbstractModeldans la base de données. Qui savait qu'un simple passeur m'aiderait à SÉCHER les choses! (Je commençais à grincer des dents ... c'était mauvais). Merci Toby et Harish!
dooleyo

Dans mon cas, c'est certainement la meilleure façon de le faire: je n'étend pas mes capacités de modèle avec des méthodes étrangères ici, mais je refactore des méthodes commmon pour des objets au comportement similaire de mon application. L'héritage a beaucoup plus de sens ici. Il n'y a pas de moyen préféré mais 2 solutions en fonction de ce que vous souhaitez réaliser!
Augustin Riedinger le

Cela ne fonctionne pas pour moi dans Rails4. J'ai créé abstract_model.rb et mis dans mon répertoire de modèles. à l'intérieur du modèle, il avait le self.abstract_class = true Ensuite, j'ai créé l'un de mes autres modèles héritant ... User <AbstractModel . Dans la console, j'obtiens: User (appelez 'User.connection' pour établir une connexion)
Joel Grannas

21

Vous pouvez également utiliser ActiveSupport::Concernet être plus idiomatique de base de Rails comme:

module MyExtension
  extend ActiveSupport::Concern

  def foo
  end

  module ClassMethods
    def bar
    end
  end
end

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

[Modifier] suite au commentaire de @daniel

Ensuite, tous vos modèles auront la méthode fooincluse comme méthode d'instance et les méthodes ClassMethodsincluses comme méthodes de classe. Par exemple sur un FooBar < ActiveRecord::Basevous aurez: FooBar.baretFooBar#foo

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


5
Notez que InstanceMethodsc'est obsolète depuis Rails 3.2, mettez simplement vos méthodes dans le corps du module.
Daniel Rikowski

J'ai placé ActiveRecord::Base.send(:include, MyExtension)un initialiseur, puis cela a fonctionné pour moi. Rails 4.1.9
6ft Dan

18

Avec Rails 4, le concept d'utilisation des préoccupations pour modulariser et sécher vos modèles a été mis en évidence.

Les préoccupations vous permettent essentiellement de regrouper le code similaire d'un modèle ou sur 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 de nombreux commentaires. Un commentaire appartient à un article ou à un événement.

Traditionnellement, les modèles peuvent ressembler à ceci:

Modèle de commentaire:

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 à la fois au modèle d'événement et d'article. En utilisant les préoccupations, nous pouvons extraire ce code commun dans un module distinct Commentable.

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

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:

Modèle de commentaire:

    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 j'aimerais souligner lors de l'utilisation des préoccupations est que les préoccupations devraient être utilisées pour un groupement «basé sur un domaine» plutôt qu'un groupement «technique». Par exemple, un groupement de domaine est comme «Commentable», «Taggable» etc. Un groupement technique sera comme «FinderMethods», «ValidationMethods».

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

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


7

É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 3

There is no step 3 :)

1
Je suppose que l'étape 2 doit être placée dans config / environment.rb. Cela ne fonctionne pas pour moi :(. Pouvez-vous s'il vous plaît écrire un peu d'aide? Thx.
xpepermint

5

Les rails 5 fournissent un mécanisme intégré d'extension 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 .


4

Juste pour ajouter à ce sujet, j'ai passé un certain temps à essayer de tester de telles extensions (j'ai suivi la ActiveSupport::Concernvoie.)

Voici comment j'ai mis en place 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

Avec Rails 5, tous les modèles sont hérités d'ApplicationRecord et cela donne un bon moyen 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 voulons l'appliquer à un ensemble particulier de modèles, 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 cela aidera les autres!


0

j'ai

ActiveRecord::Base.extend Foo::Bar

dans un initialiseur

Pour un module comme ci-dessous

module Foo
  module Bar
  end
end
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.