2009-11-08 4 views
21

mam dwa modele, które zawierają tę samą metodę:Gdzie umieścić wspólny kod znaleziony w wielu modelach?

def foo 
    # do something 
end 

Gdzie należy umieścić to?

Znam wspólny kod w katalogu lib katalogu w aplikacji Rails.

Ale jeśli mogę umieścić go w nowej klasie w lib nazwie „Foo”, a muszę dodać swoją funkcjonalność obu moich ActiveRecord models, mogę to zrobić tak:

class A < ActiveRecord::Base 
includes Foo 

class B < ActiveRecord::Base 
includes Foo 

a następnie obie A i B będzie zawierać metodę foo, tak jakbym ją zdefiniował w każdym z nich?

Odpowiedz

34

Utwórz moduł, który można umieścić w katalogu lib:

module Foo 
    def foo 
    # do something 
    end 
end 

Następnie można include moduł w każdej z klas modelu

class A < ActiveRecord::Base 
    include Foo 
end 

class B < ActiveRecord::Base 
    include Foo 
end 

W A i B modele będą teraz zdefiniowano metodę foo.

Jeśli zastosujesz konwencje nazewnictwa Railsów z nazwą modułu i nazwą pliku (np. Foo w foo.rb i FooBar w foo_bar.rb), Railsy automatycznie załadują plik za Ciebie. W przeciwnym razie będziesz musiał użyć require_dependency 'file_name', aby załadować plik lib.

5

Jedną z opcji jest umieszczenie ich w nowym katalogu, na przykład app/models/modules/. Następnie można dodać do config/environment.rb:

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

Będzie to require każdy plik w w tym katalogu, więc jeśli umieścić plik jak poniżej w katalogu modułów:

module SharedMethods 
    def foo 
    #... 
    end 
end 

następnie można po prostu użyj go w swoich modelach, ponieważ zostanie on automatycznie załadowany:

class User < ActiveRecord::Base 
    include SharedMethods 
end 

To podejście jest bardziej zorganizowane niż umieszczanie tych miksów w lib katalogu, ponieważ pozostają w pobliżu klas, które z nich korzystają.

+0

Jeśli oba modele nazywają "before_save: before_method", a ja również umieściłem to w SharedMethods, to też będzie działać? Czy to działa tylko w przypadku definicji metod? –

+0

Czy ma znaczenie, gdzie twój kod "wymaga" pojawia się w environment.rb? –

+1

Prawdopodobnie będziesz go potrzebował w sekcji "Rails :: Initializer.run do | config | ..." – nicholaides

14

Ty naprawdę masz dwie możliwości:

  1. użyć modułu do wspólnej logiki i dołączyć go do & B
  2. używać wspólnego klasy C, który rozciąga ActiveRecord i mają & B przedłużyć C.

Użyj nr 1, jeśli udostępniona funkcja nie jest podstawowa dla każdej klasy, ale dotyczy każdej klasy.Na przykład:

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

Korzystanie # 2, jeżeli wspólne funkcje są wspólne dla każdej klasy oraz udziału & B naturalnym związku:

(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

w tym przypadku istnieje potrzeba relacji "liter", prawda? A co z robieniem tego samego i faktycznie przedłużeniem aktywnego rekordu do A i B? – Ron

+2

Opcja nr 2 sprawia, że ​​Railsy przyjmują, że A i B są przechowywane w tabeli o nazwie "litery". Jeśli potrzebujesz tylko wspólnej logiki, zachowując jednocześnie A i B w osobnych tabelach, abstrakcyjna klasa rodzica jest drogą do zrobienia, jak @Ron wskazał [poniżej] (http://stackoverflow.com/a/20749863/2657571). – EK0

4

Jak wspominają inni to Foo jest droga do rzeczy ... Wydaje się jednak, że nie zapewnia on pożądanej funkcjonalności za pomocą podstawowego modułu. Poniżej przedstawiono wiele wtyczek Railsowych, które dodają metody klasy i nowe wywołania zwrotne oprócz nowych metod instancji.

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

Próbowałem tej metody, ale nadal mówi mi, że nie ma zdefiniowanej metody (w moim przypadku "zakres"). – Jonah

5

Oto jak to zrobiłem ... Po pierwsze stworzyć mixin:

module Slugged 
    extend ActiveSupport::Concern 

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

następnie wymieszać ją do każdego modelu, który potrzebuje go:

class Sector < ActiveRecord::Base 
    include Slugged 

    validates_uniqueness_of :name 
    etc 
end 

To prawie całkiem!

Aby dokończyć przykład, jeśli jest to bez znaczenia dla kwestii, oto mój model ślimak:

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

Jeśli potrzebujesz ActiveRecord :: Base kod jako część swoich wspólnych funkcji, przy użyciu klasy abstrakcyjnej może być również użyteczny. Coś takiego:

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 

To takie proste. Ponadto, jeśli kod nie jest związany z ActiveRecord, znajdź ActiveSupport::Concerns jako lepsze podejście.