Rotaie che estendono ActiveRecord :: Base


160

Ho fatto alcune letture su come estendere ActiveRecord: classe Base in modo che i miei modelli avessero alcuni metodi speciali. Qual è il modo semplice per estenderlo (tutorial passo per passo)?


Che tipo di estensioni? Abbiamo davvero bisogno di più per andare avanti.
jonnii,

Risposte:


336

Esistono diversi approcci:

Utilizzo di ActiveSupport :: Preoccupazione (preferito)

Leggi la documentazione di ActiveSupport :: Preoccupazione per maggiori dettagli.

Crea un file chiamato active_record_extension.rbnella libdirectory.

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)

Creare un file nella config/initializersdirectory chiamata extensions.rbe aggiungere la seguente riga al file:

require "active_record_extension"

Eredità (preferita)

Fai riferimento alla risposta di Toby .

Patch di scimmia (dovrebbe essere evitato)

Crea un file nella config/initializersdirectory chiamata 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 famosa citazione sulle espressioni regolari di Jamie Zawinski può essere riproposta per illustrare i problemi associati al patching delle scimmie.

Alcune persone, di fronte a un problema, pensano "Lo so, userò il patching delle scimmie". Ora hanno due problemi.

Il patching delle scimmie è semplice e veloce. Ma il tempo e lo sforzo risparmiati vengono sempre estratti in futuro; con interesse composto. Oggi limito il patching delle scimmie per prototipare rapidamente una soluzione nella console delle rotaie.


3
Devi requireil file alla fine di environment.rb. Ho aggiunto questo ulteriore passaggio alla mia risposta.
Harish Shetty,

1
@HartleyBrody è solo una questione di preferenza. Se usi l'ereditarietà, devi introdurre un nuovo ImprovedActiveRecorded ereditare da quello, quando stai usando module, stai aggiornando la definizione della classe in questione. Usavo l'ereditarietà (causa di anni di esperienza Java / C ++). In questi giorni uso principalmente moduli.
Harish Shetty,

1
È un po 'ironico che il tuo link fosse in realtà contestualizzante e sottolineando come le persone sbagliano e abusano della citazione. Ma con tutta serietà ho difficoltà a capire perché il "patching delle scimmie" non sarebbe il modo migliore in questo caso. Se vuoi aggiungere più classi allora un modulo è ovviamente la strada da percorrere. Ma se il tuo obiettivo è quello di estendere una classe, allora non è per questo che Ruby ha reso così semplice estendere le classi in questo modo?
MCB

1
@MCB, Ogni grande progetto ha poche storie su un bug difficile da individuare introdotto a causa del patching delle scimmie. Ecco un articolo di Avdi sui mali del patching: devblog.avdi.org/2008/02/23/… . Ruby 2.0 introduce una nuova funzionalità chiamata Refinementsche affronta la maggior parte dei problemi con il patching delle scimmie ( yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-practice ). A volte una funzione è lì solo per costringerti a tentare il destino. E a volte lo fai.
Harish Shetty,

1
@TrantorLiu Sì. Ho aggiornato la risposta per riflettere la documentazione più recente (sembra che class_methods sia stata introdotta nel 2014 github.com/rails/rails/commit/… )
Harish Shetty l'

70

Puoi semplicemente estendere la classe e usare semplicemente l'ereditarietà.

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

class Foo < AbstractModel
end

class Bar < AbstractModel
end

Mi piace questa idea perché è un modo standard di farlo ma ... Ottengo un errore La tabella 'moboolo_development.abstract_models' non esiste: MOSTRA CAMPI DA abstract_models. Dove dovrei metterlo?
xpepermint,

23
Aggiungi self.abstract_class = trueal tuo AbstractModel. Rails ora riconoscerà il modello come modello astratto.
Harish Shetty,

Wow! Non pensavo fosse possibile. L'ho provato prima e si è arreso quando ActiveRecord ha soffocato cercando AbstractModelil database. Chi sapeva che un semplice setter mi avrebbe aiutato a ASCIUGARE le cose! (Stavo iniziando a rabbrividire ... era male). Grazie Toby e Harish!
dooleyo,

Nel mio caso è sicuramente il modo migliore per farlo: non sto estendendo le mie abilità di modello con metodi estranei qui, ma rifattorizzando metodi comuni per oggetti comportamentali simili della mia app. L'ereditarietà ha molto più senso qui. Non esiste un modo preferito ma 2 soluzioni a seconda di ciò che vuoi ottenere!
Augustin Riedinger,

Questo non funziona per me in Rails4. Ho creato abstract_model.rb e inserito nella directory dei miei modelli. all'interno del modello aveva il self.abstract_class = true Poi ho fatto ereditare su altri miei modelli ... User <AbstractModel . Nella console ottengo: Utente (chiama "User.connection" per stabilire una connessione)
Joel Grannas

21

Puoi anche usare ActiveSupport::Concerned essere più idiomatico del core di Rails come:

module MyExtension
  extend ActiveSupport::Concern

  def foo
  end

  module ClassMethods
    def bar
    end
  end
end

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

[Modifica] seguendo il commento di @daniel

Quindi tutti i tuoi modelli includeranno il metodo foocome metodo di istanza e i metodi ClassMethodsinclusi come metodi di classe. Ad esempio su un FooBar < ActiveRecord::Baseavrai: FooBar.bareFooBar#foo

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


5
Nota che InstanceMethodsè deprecato da Rails 3.2, inserisci i tuoi metodi nel corpo del modulo.
Daniel Rikowski,

Ho inserito ActiveRecord::Base.send(:include, MyExtension)un inizializzatore e poi ha funzionato per me. Rotaie 4.1.9
6ft Dan

18

Con Rails 4, il concetto di utilizzo delle preoccupazioni per modulare e ASCIUGARE i tuoi modelli è stato messo in evidenza.

Le preoccupazioni fondamentalmente consentono di raggruppare un codice simile di un modello o tra più modelli in un singolo modulo e quindi utilizzare questo modulo nei modelli. Ecco un esempio:

Considera un modello di articolo, un modello di evento e un modello di commento. Un articolo o un evento ha molti commenti. Un commento appartiene all'articolo o all'evento.

Tradizionalmente, i modelli possono apparire così:

Modello di commento:

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

Modello dell'articolo:

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

Modello di evento

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

Come possiamo notare, esiste una parte significativa del codice comune sia al modello di evento che a quello di articolo. Usando le preoccupazioni possiamo estrarre questo codice comune in un modulo separato Commentable.

Per questo creare un file commentable.rb in app / modello / preoccupazioni.

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

E ora i tuoi modelli si presentano così:

Modello di commento:

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

Modello dell'articolo:

class Article < ActiveRecord::Base
    include Commentable
end

Modello di evento

class Event < ActiveRecord::Base    
    include Commentable
end

Un punto che vorrei evidenziare mentre uso le preoccupazioni è che le preoccupazioni dovrebbero essere usate per il raggruppamento "basato sul dominio" piuttosto che per il raggruppamento "tecnico". Ad esempio, un raggruppamento di domini è come "Commentable", "Taggable" ecc. Un raggruppamento basato su tecniche sarà come "FinderMethods", "ValidationMethods".

Ecco un link a un post che ho trovato molto utile per comprendere le preoccupazioni in Modelli.

Spero che il writeup aiuti :)


7

Passo 1

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

Passo 2

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

Passaggio 3

There is no step 3 :)

1
Immagino che il passaggio 2 debba essere inserito in config / environment.rb. Non funziona per me :(. Puoi per favore scrivere qualche altro aiuto?
Grazie

5

Rails 5 fornisce un meccanismo integrato per l'estensione ActiveRecord::Base.

Ciò si ottiene fornendo un livello aggiuntivo:

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

e tutti i modelli ereditano da quello:

class Post < ApplicationRecord
end

Vedi ad esempio questo post sul blog .


4

Solo per aggiungere a questo argomento, ho trascorso un po 'di tempo a capire come testare tali estensioni (sono andato giù per il ActiveSupport::Concernpercorso.)

Ecco come ho impostato un modello per testare le mie estensioni.

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

Con Rails 5, tutti i modelli sono ereditati da ApplicationRecord e consentono di includere o estendere altre librerie di estensioni.

# 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

Supponiamo che il modulo dei metodi speciali debba essere disponibile su tutti i modelli, includerlo nel file application_record.rb. Se vogliamo applicarlo per un particolare set di modelli, includerlo nelle rispettive classi di modelli.

# 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

Se si desidera avere i metodi definiti nel modulo come metodi di classe, estendere il modulo ad ApplicationRecord.

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

Spero che aiuti gli altri!


0

io ho

ActiveRecord::Base.extend Foo::Bar

in un inizializzatore

Per un modulo come di seguito

module Foo
  module Bar
  end
end
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.