2009-11-08 5 views
21

Ich habe zwei Modelle, die die gleiche Methode enthalten:Wohin mit dem gemeinsamen Code in mehreren Modellen?

def foo 
    # do something 
end 

Wo soll ich das sagen?

Ich weiß, gemeinsamen Code geht in lib Verzeichnis in einer Rails App.

Aber wenn ich es in einer neuen Klasse in libFoo‘ genannt, und ich brauche, um seine Funktionalität zu meinen beiden ActiveRecord models hinzuzufügen, mache ich das so:

class A < ActiveRecord::Base 
includes Foo 

class B < ActiveRecord::Base 
includes Foo 

und dann beide A und B werden die foo-Methode genauso enthalten, als ob ich sie in jedem definiert hätte?

Antwort

34

Erstellen Sie ein Modul, mit dem Sie im lib Verzeichnis setzen können:

module Foo 
    def foo 
    # do something 
    end 
end 

Sie können dann in jedem Ihrer Modellklassen include das Modul:

class A < ActiveRecord::Base 
    include Foo 
end 

class B < ActiveRecord::Base 
    include Foo 
end 

Die A und B Modelle werden Jetzt haben Sie eine foo Methode definiert.

Wenn Sie die Namenskonventionen von Rails mit dem Namen des Moduls und dem Namen der Datei befolgen (z. B. Foo in foo.rb und FooBar in foo_bar.rb), lädt Rails die Datei automatisch für Sie. Andernfalls müssen Sie require_dependency 'file_name' verwenden, um Ihre lib-Datei zu laden.

5

Eine Option besteht darin, sie in ein neues Verzeichnis zu stellen, z. B. app/models/modules/. Dann können Sie diese auf config/environment.rb hinzufügen:

Dir["#{RAILS_ROOT}/app/models/modules/*.rb"].each do |filename| 
    require filename 
end 

Dies wird require jede Datei in in diesem Verzeichnis, also wenn Sie setzen eine Datei wie folgt in Ihrem Module Verzeichnis:

module SharedMethods 
    def foo 
    #... 
    end 
end 

Dann können Sie verwenden sie es nur in Ihren Modellen, weil es automatisch geladen wird:

class User < ActiveRecord::Base 
    include SharedMethods 
end 

Dieser Ansatz besser organisiert ist, diese Mixins in der 01 als PuttenVerzeichnis, weil sie in der Nähe der Klassen bleiben, die sie verwenden.

+0

Wenn beide Modelle "before_save: before_method" aufrufen und ich dies auch in SharedMethods verwende, funktioniert das auch? Oder funktioniert es nur für die Methodendefinitionen? –

+0

Ist es auch wichtig, wo Ihr 'require'-Code in environment.rb angezeigt wird? –

+1

Sie wollen es wahrscheinlich in der "Rails :: Initializer.run tun | config | ... Ende" Abschnitt – nicholaides

14

Sie haben wirklich zwei Möglichkeiten:

  1. ein Modul für die gemeinsame Logik verwenden und es in A & B
  2. verwenden eine gemeinsame Klasse C, die Active erstreckt und haben eine & B erstrecken C.

Verwenden Sie # 1, wenn die freigegebene Funktionalität nicht Kern jeder Klasse ist, aber für jede Klasse gilt.Zum Beispiel:

(app/lib/serializable.rb) 
module Serializable 
    def serialize 
    # do something to serialize this object 
    end 
end 

verwendbares # 2, wenn die gemeinsam genutzte Funktionalität für jede Klasse und A & B-Aktie eine natürliche Beziehung gemeinsam ist:

(app/models/letter.rb) 
class Letter < ActiveRecord::Base 
    def cyrilic_equivilent 
    # return somethign similar 
    end 
end 

class A < Letter 
end 

class B < Letter 
end 
+0

In diesem Fall besteht die Notwendigkeit einer Beziehung "Briefe", richtig? Wie wäre es, das Gleiche zu tun und den Active Record tatsächlich auf A und B zu erweitern? – Ron

+2

Option # 2 lässt Rails annehmen, dass A und B beide in einer Tabelle namens "Buchstaben" gespeichert sind. Wenn Sie nur geteilte Logik verwenden möchten, während Sie A und B in separaten Tabellen aufbewahren, ist eine abstrakte Elternklasse der richtige Weg, wie @Ron auf [unten] hingewiesen hat (http://stackoverflow.com/a/20749863/2657571). – EK0

4

Wie andere erwähnt haben Foo enthalten ist die Art und Weise, Dinge zu tun ... Es scheint jedoch nicht die gewünschte Funktionalität mit einem Basismodul zu bekommen. Das folgende ist die Form, die viele Rails-Plugins benötigen, um Klassenmethoden und neue Callbacks zusätzlich zu neuen Instanzmethoden hinzuzufügen.

module Foo #:nodoc: 

    def self.included(base) # :nodoc: 
    base.extend ClassMethods 
    end 

    module ClassMethods 
    include Foo::InstanceMethods 

    before_create :before_method 
    end 

    module InstanceMethods 
    def foo 
     ... 
    end 

    def before_method 
     ... 
    end 
    end 

end 
+0

Ich habe diese Methode versucht, aber es sagt mir immer noch, dass keine Methode definiert ist (in meinem Fall "Scope"). – Jonah

5

Hier ist, wie ich es getan habe ... zuerst die mixin erstellen:

module Slugged 
    extend ActiveSupport::Concern 

    included do 
    has_many :slugs, :as => :target 
    has_one :slug, :as => :target, :order => :created_at 
    end 
end 

es dann in jedem Modell mischen, die es braucht:

class Sector < ActiveRecord::Base 
    include Slugged 

    validates_uniqueness_of :name 
    etc 
end 

Es ist fast hübsch!

das Beispiel zu vervollständigen, wenn es um die Frage irrelevant ist, hier ist mein Slug Modell:

class Slug < ActiveRecord::Base 
    belongs_to :target, :polymorphic => true 
end 
4

Wenn Sie Activerecord :: Base Code als Teil Ihrer gemeinsamen Funktionalitäten benötigen, eine abstrakte Klasse mit könnte auch nützlich sein. Etwas wie:

class Foo < ActiveRecord::Base 
    self.abstract_class = true 
    #Here ActiveRecord specific code, for example establish_connection to a different DB. 
end 

class A < Foo; end 
class B < Foo; end 

So einfach ist das. Wenn der Code ActiveRecord nicht verwandt ist, finden Sie ActiveSupport::Concerns als einen besseren Ansatz außerdem.