rails foreign create belong before association after_save after ruby callback

foreign - ¿Cómo implementar una "devolución de llamada" en Ruby?



rails after_save (6)

A menudo implemento devoluciones de llamada en Ruby como en el siguiente ejemplo. Es muy cómodo de usar.

class Foo # Declare a callback. def initialize callback( :on_die_cast ) end # Do some stuff. # The callback event :on_die_cast is triggered. # The variable "die" is passed to the callback block. def run while( true ) die = 1 + rand( 6 ) on_die_cast( die ) sleep( die ) end end # A method to define callback methods. # When the latter is called with a block, it''s saved into a instance variable. # Else a saved code block is executed. def callback( *names ) names.each do |name| eval <<-EOF @#{name} = false def #{name}( *args, &block ) if( block ) @#{name} = block elsif( @#{name} ) @#{name}.call( *args ) end end EOF end end end foo = Foo.new # What should be done when the callback event is triggered? foo.on_die_cast do |number| puts( number ) end foo.run

No estoy seguro de la mejor expresión idiomática para las llamadas de estilo C en Ruby, o si hay algo aún mejor (y menos como C). En C, haría algo como:

void DoStuff( int parameter, CallbackPtr callback ) { // Do stuff ... // Notify we''re done callback( status_code ) }

¿Cuál es un buen equivalente de Ruby? Básicamente, quiero llamar a un método aprobado en clase, cuando se cumple una determinada condición dentro de "DoStuff"


El equivalente de rubí, que no es idiomático, sería:

def my_callback(a, b, c, status_code) puts "did stuff with #{a}, #{b}, #{c} and got #{status_code}" end def do_stuff(a, b, c, callback) sum = a + b + c callback.call(a, b, c, sum) end def main a = 1 b = 2 c = 3 do_stuff(a, b, c, method(:my_callback)) end

El enfoque idiomático sería pasar un bloque en lugar de una referencia a un método. Una ventaja que tiene un bloque sobre un método independiente es el contexto: un bloque es un closure , por lo que puede hacer referencia a variables del ámbito en el que se declaró. Esto reduce el número de parámetros que do_stuff necesita pasar a la devolución de llamada. Por ejemplo:

def do_stuff(a, b, c, &block) sum = a + b + c yield sum end def main a = 1 b = 2 c = 3 do_stuff(a, b, c) { |status_code| puts "did stuff with #{a}, #{b}, #{c} and got #{status_code}" } end


Exploré el tema un poco más y actualicé el código.

La siguiente versión es un intento de generalizar la técnica, aunque sigue siendo extremadamente simplificada e incompleta.

Robé en gran medida, hem, encontré inspiración en la implementación de callbacks de DataMapper, que me parece bastante completo y hermoso.

Recomiendo echar un vistazo al código @ http://github.com/datamapper/dm-core/blob/master/lib/dm-core/support/hook.rb

De todos modos, tratar de reproducir la funcionalidad utilizando el módulo Observable fue bastante interesante e instructivo. Algunas notas:

  • método agregado parece ser necesario porque los métodos de instancia originales no están disponibles en el momento de registrar las devoluciones de llamada
  • la clase incluida se hace tanto observada como autoobservadora
  • el ejemplo está limitado a los métodos de instancia, no admite bloques, argumentos, etc.

código:

require ''observer'' module SuperSimpleCallbacks include Observable def self.included(klass) klass.extend ClassMethods klass.initialize_included_features end # the observed is made also observer def initialize add_observer(self) end # TODO: dry def update(method_name, callback_type) # hook for the observer case callback_type when :before then self.class.callbacks[:before][method_name.to_sym].each{|callback| send callback} when :after then self.class.callbacks[:after][method_name.to_sym].each{|callback| send callback} end end module ClassMethods def initialize_included_features @callbacks = Hash.new @callbacks[:before] = Hash.new{|h,k| h[k] = []} @callbacks[:after] = @callbacks[:before].clone class << self attr_accessor :callbacks end end def method_added(method) redefine_method(method) if is_a_callback?(method) end def is_a_callback?(method) registered_methods.include?(method) end def registered_methods callbacks.values.map(&:keys).flatten.uniq end def store_callbacks(type, method_name, *callback_methods) callbacks[type.to_sym][method_name.to_sym] += callback_methods.flatten.map(&:to_sym) end def before(original_method, *callbacks) store_callbacks(:before, original_method, *callbacks) end def after(original_method, *callbacks) store_callbacks(:after, original_method, *callbacks) end def objectify_and_remove_method(method) if method_defined?(method.to_sym) original = instance_method(method.to_sym) remove_method(method.to_sym) original else nil end end def redefine_method(original_method) original = objectify_and_remove_method(original_method) mod = Module.new mod.class_eval do define_method(original_method.to_sym) do changed; notify_observers(original_method, :before) original.bind(self).call if original changed; notify_observers(original_method, :after) end end include mod end end end class MyObservedHouse include SuperSimpleCallbacks before :party, [:walk_dinosaure, :prepare, :just_idle] after :party, [:just_idle, :keep_house, :walk_dinosaure] before :home_office, [:just_idle, :prepare, :just_idle] after :home_office, [:just_idle, :walk_dinosaure, :just_idle] before :second_level, [:party] def home_office puts "learning and working with ruby...".upcase end def party puts "having party...".upcase end def just_idle puts "...." end def prepare puts "preparing snacks..." end def keep_house puts "house keeping..." end def walk_dinosaure puts "walking the dinosaure..." end def second_level puts "second level..." end end MyObservedHouse.new.tap do |house| puts "-------------------------" puts "-- about calling party --" puts "-------------------------" house.party puts "-------------------------------" puts "-- about calling home_office --" puts "-------------------------------" house.home_office puts "--------------------------------" puts "-- about calling second_level --" puts "--------------------------------" house.second_level end # => ... # ------------------------- # -- about calling party -- # ------------------------- # walking the dinosaure... # preparing snacks... # .... # HAVING PARTY... # .... # house keeping... # walking the dinosaure... # ------------------------------- # -- about calling home_office -- # ------------------------------- # .... # preparing snacks... # .... # LEARNING AND WORKING WITH RUBY... # .... # walking the dinosaure... # .... # -------------------------------- # -- about calling second_level -- # -------------------------------- # walking the dinosaure... # preparing snacks... # .... # HAVING PARTY... # .... # house keeping... # walking the dinosaure... # second level...

Esta simple presentación del uso de Observable podría ser útil: http://www.oreillynet.com/ruby/blog/2006/01/ruby_design_patterns_observer.html


Por lo tanto, esto puede ser muy "no ruby", y no soy un desarrollador "profesional" de Ruby, así que si ustedes van a serlo, sean amables por favor :)

Ruby tiene un módulo integrado llamado Observer. No me ha resultado fácil de usar, pero para ser justos, no le di muchas posibilidades. En mis proyectos, he recurrido a la creación de mi propio tipo EventHandler (sí, uso mucho C #). Aquí está la estructura básica:

class EventHandler def initialize @client_map = {} end def add_listener(id, func) (@client_map[id.hash] ||= []) << func end def remove_listener(id) return @client_map.delete(id.hash) end def alert_listeners(*args) @client_map.each_value { |v| v.each { |func| func.call(*args) } } end end

Entonces, para usar esto lo expongo como un miembro de solo lectura de una clase:

class Foo attr_reader :some_value_changed def initialize @some_value_changed = EventHandler.new end end

Los clientes de la clase "Foo" pueden suscribirse a un evento como este:

foo.some_value_changed.add_listener(self, lambda { some_func })

Estoy seguro de que esto no es idiomático, Ruby y yo solo estoy calzando mi experiencia C # en un nuevo idioma, pero me ha funcionado.



Este "bloque idiomático" es una parte muy importante de Ruby todos los días y se cubre con frecuencia en libros y tutoriales. La sección de información de Ruby proporciona enlaces a recursos útiles de aprendizaje [en línea].

La forma idiomática es usar un bloque:

def x(z) yield z # perhaps used in conjunction with #block_given? end x(3) {|y| y*y} # => 9

O tal vez convertido a un Proc ; Aquí muestro que el "bloque", convertido a un Proc implícitamente con &block , es solo otro valor "invocable":

def x(z, &block) callback = block callback.call(z) end # look familiar? x(4) {|y| y * y} # => 16

(Solo use el formulario anterior para guardar el block-now-Proc para su uso posterior o en otros casos especiales, ya que agrega sobrecarga y ruido de sintaxis).

Sin embargo, una lambda puede usarse con la misma facilidad (pero esto no es idiomático):

def x(z,fn) fn.call(z) end # just use a lambda (closure) x(5, lambda {|y| y * y}) # => 25

Si bien los enfoques anteriores pueden incluir "llamar a un método", ya que crean cierres, los Methods vinculados también se pueden tratar como objetos invocables de primera clase:

class A def b(z) z*z end end callable = A.new.method(:b) callable.call(6) # => 36 # and since it''s just a value... def x(z,fn) fn.call(z) end x(7, callable) # => 49

Además, a veces es útil usar el método #send (en particular, si un método se conoce por su nombre). Aquí guarda un objeto Método intermedio que se creó en el último ejemplo; Ruby es un sistema de transmisión de mensajes:

# Using A from previous def x(z, a): a.__send__(:b, z) end x(8, A.new) # => 64

Feliz codificación!