2012-08-23 12 views
9

Escribí un módulo simple Cacheable que hace que sea simple para almacenar en caché los campos agregados en un modelo principal. El módulo requiere que el objeto principal implemente el método cacheable y un método calc_ para cada campo que requiera el almacenamiento en caché en el nivel primario.¿Cómo uso la metaprogramación de Ruby para agregar devoluciones de llamada a un modelo de Rails?

module Cacheable 
    def cache!(fields, *objects) 
    objects.each do |object| 
     if object.cacheable? 
     calc(fields, objects) 
     save!(objects) 
     end 
    end 
    end 

    def calc(fields, objects) 
    fields.each { |field| objects.each(&:"calc_#{field}") } 
    end 

    def save!(objects) 
    objects.each(&:save!) 
    end 
end 

Me gustaría agregar devoluciones de llamada al modelo ActiveRecord en el que se incluye este módulo. Este método requeriría que el modelo implemente un hash de modelos principales y nombres de campo que requieran el almacenamiento en caché.

def cachebacks(klass, parents) 
    [:after_save, :after_destroy].each do |callback| 
    self.send(callback, proc { cache!(CACHEABLE[klass], self.send(parents)) }) 
    end 
end 

Este enfoque funciona muy bien si agrego manualmente usando dos devoluciones de llamada tales como:

after_save proc { cache!(CACHEABLE[Quote], *quotes.all) } 
after_destroy proc { cache!(CACHEABLE[Quote], *quotes.all) } 

Pero, estoy recibiendo el siguiente error al intentar utilizar el método cachebacks añadir éstos a las devoluciones de llamada .

cachebacks(Quote, "*quotes.all") 

NoMethodError: undefined method `cachebacks' for #<Class:0x007fe7be3f2ae8> 

¿Cómo agrego estas devoluciones de llamada dinámicamente a la clase?

+0

Disculpa, no pude entender la última parte. ¿'Cita' es un modelo relacionado? ¿Podría publicar cómo se ve su clase en este momento? –

+0

La respuesta que encontré basada en tu consejo debe explicar las cosas. También estoy interesado en el enfoque ActiveSupport :: Concern. – barelyknown

Respuesta

6

Esto parece un buen caso para ActiveSupport::Concern. Puede ajustar su método cachebacks ligeramente para agregarlo como un método de clase de la clase que incluye:

module Cacheable 
    extend ActiveSupport::Concern 

    module ClassMethods 
    def cachebacks(&block) 
     klass = self 
     [:after_save, :after_destroy].each do |callback| 
     self.send(callback, proc { cache!(CACHEABLE[klass], *klass.instance_eval(&block)) }) 
     end 
    end 
    end 

    def cache!(fields, *objects) 
    # ... 
    end 

    # ... 
end 

utilizarlo:

class Example < ActiveRecord::Base 
    include Cacheable 
    cachebacks { all } 
end 

El bloque se pasa a cachebacks se ejecutará en el contexto de la clase que lo está llamando. En este ejemplo, { all } es equivalente a llamar al Example.all y pasar los resultados al método cache!.


Para responder a su pregunta en los comentarios, Concern encapsula un patrón común y establece una convención en rieles. La sintaxis es ligeramente más elegante:

included do 
    # behaviors 
end 

# instead of 

def self.included(base) 
    base.class_eval do 
    # behaviors 
    end 
end 

También se aprovecha de otra convención para incluir de forma automática y correctamente métodos de la clase y de instancia. Si espacio de nombres en los módulos llamados ClassMethods y InstanceMethods (aunque como has visto, InstanceMethods es opcional), entonces ya has terminado.

Por último, maneja dependencias de módulos. La documentación ofrece un buen ejemplo de esto, pero en esencia, evita que la clase incluye tener que incluir explícitamente módulos dependientes además del módulo en el que realmente está interesado.

+1

Esa es una buena solución. No he usado ActiveSupport :: Concerns antes. ¿Cuáles son los beneficios clave sobre un enfoque como el que publiqué a continuación (el módulo Cacheable)? – barelyknown

+0

He actualizado mi respuesta con más detalles sobre 'Concern'. – Brandan

0

Como dije en el comentario, es posible que no tenga razón si no entendí su pregunta. Que este trabajo para usted?

module Cacheable 
    def self.included(base) 
    base.class_eval do 
     def self.cachebacks(klass, parents) 
     [:after_save, :after_destroy].each do |callback| 
      self.send(callback, proc { cache!(CACHEABLE[klass], self.send(parents)) }) 
     end 
     end 
    end 
    end 

    def cache!(fields, *objects) 
    objects.each do |object| 
     if object.cacheable? 
     calc(fields, objects) 
     save!(objects) 
     end 
    end 
    end 

    def calc(fields, objects) 
    fields.each { |field| objects.each(&:"calc_#{field}") } 
    end 

    def save!(objects) 
    objects.each(&:save!) 
    end 
end 
+0

Si bien no fue exactamente la solución, su respuesta me ayudó a resolverlo. Agregaré la solución completa a continuación como una respuesta separada. Si ajustas el tuyo, lo marcaré como aceptado. Muchas gracias. – barelyknown

+0

Olvidé el 'self'. Deberia de funcionar. : P –

1

Gracias a Brandon por la respuesta que me ayudó a escribir la solución.

Agregue lo siguiente a su modelo. Puede cacheback relaciones padre múltiples por modelo.También puede especificar diferentes nombres de atributos para las tablas padre e hijo al pasar un hash en lugar de una cadena para un campo en particular.

include Cacheable 
cacheback(parent: :quotes, fields: %w(weight pallet_spots value equipment_type_id)) 

Este módulo se extiende ActiveSupport :: preocupación y añade las devoluciones de llamada y realiza la cache. Sus clases principales deberán implementar los métodos calc_field para realizar el trabajo de almacenamiento en caché.

module Cacheable 
    extend ActiveSupport::Concern 

    module ClassMethods 
    def cacheback(options) 
     fields = Cacheable.normalize_fields(options[:fields]) 
     [:after_save, :after_destroy].each do |callback| 
     self.send(callback, proc { cache!(fields, self.send(options[:parent])) }) 
     end 
    end 
    end 

    def cache!(fields, objects) 
    objects = objects.respond_to?(:to_a) ? objects.to_a : [objects] 
    objects.each do |object| 
     if object.cacheable? 
     calc(fields, objects) 
     save!(objects) 
     end 
    end 
    end 

    def calc(fields, objects) 
    fields.each do |parent_field, child_field| 
     objects.each(&:"calc_#{parent_field}") if self.send("#{child_field}_changed?".to_sym) 
    end 
    end 

    def save!(objects) 
    objects.each { |object| object.save! if object.changed? } 
    end 

    def self.normalize_fields(fields) 
    Hash[fields.collect { |f| f.is_a?(Hash) ? f.to_a : [f, f] }] 
    end 

end 
+0

podría ser útil: https://github.com/Plinq/big_spoon – apneadiving

Cuestiones relacionadas