2009-11-08 14 views
21

Tengo dos modelos que contienen el mismo método:¿Dónde poner el código común encontrado en varios modelos?

def foo 
    # do something 
end 

¿Dónde debo poner esto?

Sé que el código común va en el directorio lib en la aplicación Rails.

Pero si lo pongo en una nueva clase en lib llamada 'Foo', y tengo que añadir su funcionalidad tanto de mi ActiveRecord models, lo hago de esta manera:

class A < ActiveRecord::Base 
includes Foo 

class B < ActiveRecord::Base 
includes Foo 

y luego ambos A y B contendrá el método foo como si lo hubiera definido en cada uno?

Respuesta

34

Crear un módulo, que se puede poner en el directorio lib:

module Foo 
    def foo 
    # do something 
    end 
end 

A continuación, puede include el módulo en cada una de las clases del modelo:

class A < ActiveRecord::Base 
    include Foo 
end 

class B < ActiveRecord::Base 
    include Foo 
end 

Los A y B modelos ahora tiene un método foo definido.

Si sigue los convenios de nombres de Rails con el nombre del módulo y el nombre del archivo (por ejemplo, Foo en foo.rb y FooBar en foo_bar.rb), entonces Rails cargará automáticamente el archivo por usted. De lo contrario, deberá usar require_dependency 'file_name' para cargar su archivo lib.

5

Una opción es ponerlos en un nuevo directorio, por ejemplo app/models/modules/. A continuación, puede añadir esto a config/environment.rb:

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

Esto hará require todos los archivos en ese directorio, por lo que si se pone un archivo como el siguiente en el directorio de módulos:

module SharedMethods 
    def foo 
    #... 
    end 
end 

A continuación, puede sólo lo utilizan en sus modelos, ya que se cargará automáticamente:

class User < ActiveRecord::Base 
    include SharedMethods 
end 

Este enfoque es más organizado que poner estos mixins en el lib directorio porque se quedan cerca de las clases que los usan.

+0

Si ambos modelos llaman "before_save: before_method" y también me pusieron esto en SharedMethods, hará que el trabajo también? ¿O solo funciona para las definiciones de métodos? –

+0

Además, ¿importa dónde aparece su código 'require' en environment.rb? –

+1

Probablemente querrá que en el "Rails :: Initializer.run hacer | config | ... fin" – nicholaides

14

que realmente tienen dos opciones:

  1. utilizar un módulo de lógica común e incluirla en un & B
  2. utilizar una clase común C que se extiende ActiveRecord y tienen un & B extender C.

Use # 1 si la funcionalidad compartida no es central para cada clase, sino que se aplica a cada clase.Por ejemplo:

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

Uso # 2 si la funcionalidad compartida es común a cada clase y una cuota de & B una relación natural:

(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

en este caso, hay una necesidad de una relación de 'letras', ¿verdad? ¿Qué hay de hacer lo mismo y extender el activerecord a A y B? – Ron

+2

La opción n. ° 2 hace que Rails asuma que A y B están almacenados en una tabla llamada "letras". Si sólo desea lógica compartida, manteniendo A y B en tablas separadas, una clase padre abstracta es el camino a seguir, como @Ron señaló [abajo] (http://stackoverflow.com/a/20749863/2657571). – EK0

4

Como otros han mencionado incluyen Foo es la manera de hacer las cosas ... Sin embargo, no parece obtener la funcionalidad que desea con un módulo básico. La siguiente es la forma que toman muchos de los complementos de Rails para agregar métodos de clase y nuevas devoluciones de llamada además de los nuevos métodos de instancia.

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

Probé ese método, pero todavía me dice que no hay ningún método definido (en mi caso, 'scope'). – Jonah

5

Así es como lo hice ... En primer lugar crear el mixin:

module Slugged 
    extend ActiveSupport::Concern 

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

luego se mezcla en todos los modelos que lo necesita:

class Sector < ActiveRecord::Base 
    include Slugged 

    validates_uniqueness_of :name 
    etc 
end 

Es casi bonita!

Para completar el ejemplo, aunque es irrelevante para la cuestión, aquí está mi modelo Slug:

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

Si necesita ActiveRecord :: Base código como una parte de sus funcionalidades comunes, el uso de una clase abstracta podría ser útil también Algo como:

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 

Tan simple como eso. Además, si el código no está relacionado con ActiveRecord, encuentre ActiveSupport::Concerns como un mejor enfoque.

Cuestiones relacionadas