tutorial rubyonrails rails que guide ejemplos curso ruby-on-rails ruby-on-rails-4

ruby-on-rails - rubyonrails - ruby on rails tutorial



Rails Observer Alternatives for 4.0 (12)

¡Tengo el mismo probjem! ¡Encontré una solución ActiveModel :: Dirty para que pueda seguir los cambios de su modelo!

include ActiveModel::Dirty before_save :notify_categories if :data_changed? def notify_categories self.categories.map!{|c| c.update_results(self.data)} end

http://api.rubyonrails.org/classes/ActiveModel/Dirty.html

Con los observadores oficialmente eliminados de Rails 4.0 , tengo curiosidad por saber qué están usando otros desarrolladores en su lugar. (Aparte de usar la gema extraída). Aunque los observadores eran sin duda objeto de abuso y podían volverse fácilmente difíciles de manejar a veces, había muchos casos de uso fuera de la limpieza del caché donde eran beneficiosos.

Tome, por ejemplo, una aplicación que necesita rastrear cambios a un modelo. Un observador podría observar fácilmente los cambios en el Modelo A y registrar esos cambios con el Modelo B en la base de datos. Si desea observar los cambios en varios modelos, un solo observador podría manejar eso.

En Rails 4, tengo curiosidad por las estrategias que usan otros desarrolladores en lugar de los observadores para recrear esa funcionalidad.

Personalmente, me inclino por una especie de implementación de "controlador de grasa", donde estos cambios se rastrean en el método de creación / actualización / eliminación de cada controlador de modelos. Si bien aumenta el comportamiento de cada controlador ligeramente, ayuda a la lectura y la comprensión ya que todo el código está en un solo lugar. La desventaja es que ahora hay un código que es muy similar disperso en varios controladores. La extracción de ese código en métodos auxiliares es una opción, pero aún le quedan llamadas a esos métodos en todas partes. No es el fin del mundo, pero tampoco está en el espíritu de "controladores delgados".

Las devoluciones de llamada de ActiveRecord son otra opción posible, aunque una que no me gusta personalmente ya que tiende a unir dos modelos diferentes muy estrechamente en mi opinión.

Entonces, en el mundo de Rails 4, no-observadores, si tuvieras que crear un nuevo registro después de crear / actualizar / destruir otro, ¿qué patrón de diseño utilizarías? ¿Controladores de grasa, devoluciones de llamada de ActiveRecord, o algo completamente distinto?

Gracias.


¿Qué hay de usar un PORO en su lugar?

La lógica detrás de esto es que sus ''acciones adicionales para ahorrar'' probablemente sean de lógica comercial. Esto me gusta mantenerme separado tanto de los modelos AR (que deberían ser lo más simples como sea posible) y de los controladores (que son molestos para probarlos correctamente)

class LoggedUpdater def self.save!(record) record.save! #log the change here end end

Y simplemente llámalo como tal:

LoggedUpdater.save!(user)

Incluso podría expandirlo, inyectando objetos de acción adicionales después de guardar

LoggedUpdater.save(user, [EmailLogger.new, MongoLogger.new])

Y para dar un ejemplo de los "extras". Sin embargo, es posible que desee spiffy un poco:

class EmailLogger def call(msg) #send email with msg end end

Si te gusta este enfoque, te recomiendo una lectura de la publicación del blog Bryan Helmkamps 7 Patterns .

EDITAR: También debo mencionar que la solución anterior permite agregar lógica de transacciones también cuando sea necesario. Por ejemplo, con ActiveRecord y una base de datos compatible:

class LoggedUpdater def self.save!([records]) ActiveRecord::Base.transaction do records.each(&:save!) #log the changes here end end end


Creo que el problema con la desaprobación de los observadores no es que los observadores fueran malos en sí mismos, sino que estaban siendo maltratados.

Me gustaría advertir que no agregue demasiada lógica en sus devoluciones de llamada o simplemente mover el código para simular el comportamiento de un observador cuando ya existe una solución de sonido para este problema, el patrón Observador.

Si tiene sentido usar observadores, entonces, por supuesto, use observadores. Simplemente entienda que necesitará asegurarse de que su lógica de observador siga las prácticas de codificación de sonido, por ejemplo, SOLID.

La gema observadora está disponible en rubygems si desea agregarla a su proyecto https://github.com/rails/rails-observers

vea este breve hilo, aunque no incluye una discusión exhaustiva, creo que el argumento básico es válido. https://github.com/rails/rails-observers/issues/2


Eche un vistazo a las Concerns

Cree una carpeta en su directorio de modelos llamada inquietudes. Agrega un módulo allí:

module MyConcernModule extend ActiveSupport::Concern included do after_save :do_something end def do_something ... end end

A continuación, incluya eso en los modelos en los que desea ejecutar after_save:

class MyModel < ActiveRecord::Base include MyConcernModule end

Dependiendo de lo que estés haciendo, esto podría acercarte sin observadores.


El uso de devoluciones de registros activos simplemente cambia la dependencia de su acoplamiento. Por ejemplo, si tiene modelA y un observador de modelA estilo de modelA rails 3, puede eliminar CacheObserver sin problemas. Ahora, en su lugar, di que A debe invocar manualmente CacheObserver después de guardar, que sería rails 4. Simplemente has movido tu dependencia para que puedas eliminar A forma segura pero no CacheObserver .

Ahora, desde mi torre de marfil, prefiero que el observador dependa del modelo que está observando. ¿Me importa lo suficiente como para desordenar mis controladores? Para mi, la respuesta es no.

Presumiblemente, has pensado un poco en por qué quieres / necesitas al observador, y crear un modelo que dependa de su observador no es una tragedia terrible.

También tengo un disgusto (razonablemente fundamentado, creo) por cualquier tipo de observador que dependa de una acción de controlador. De repente, debe inyectar su observador en cualquier acción del controlador (u otro modelo) que pueda actualizar el modelo que desea observar. Si puede garantizar que su aplicación solo modifique las instancias a través de crear / actualizar acciones del controlador, más poder para usted, pero eso no es una suposición sobre una aplicación de rieles (considere formas anidadas, asociaciones de actualización de lógica de negocios modelo, etc.)


En algunos casos, simplemente uso la instrumentación de soporte activo

ActiveSupport::Notifications.instrument "my.custom.event", this: :data do # do your stuff here end ActiveSupport::Notifications.subscribe "my.custom.event" do |*args| data = args.extract_options! # {:this=>:data} end


Están en un complemento ahora.

¿Puedo recomendar también github.com/krisleech/wisper que le dará controladores como:

class PostsController < ApplicationController def create @post = Post.new(params[:post]) @post.subscribe(PusherListener.new) @post.subscribe(ActivityListener.new) @post.subscribe(StatisticsListener.new) @post.on(:create_post_successful) { |post| redirect_to post } @post.on(:create_post_failed) { |post| render :action => :new } @post.create end end


Mi alternativa a Rails 3 Observers es una implementación manual que utiliza una devolución de llamada definida dentro del modelo pero que logra (como agmin declara en su respuesta anterior) "voltear la dependencia ... acoplamiento".

Mis objetos heredan de una clase base que permite el registro de observadores:

class Party411BaseModel self.abstract_class = true class_attribute :observers def self.add_observer(observer) observers << observer logger.debug("Observer #{observer.name} added to #{self.name}") end def notify_observers(obj, event_name, *args) observers && observers.each do |observer| if observer.respond_to?(event_name) begin observer.public_send(event_name, obj, *args) rescue Exception => e logger.error("Error notifying observer #{observer.name}") logger.error e.message logger.error e.backtrace.join("/n") end end end end

(Por supuesto, en el espíritu de la composición sobre la herencia, el código anterior podría colocarse en un módulo y mezclarse en cada modelo).

Un inicializador registra observadores:

User.add_observer(NotificationSender) User.add_observer(ProfilePictureCreator)

Cada modelo puede definir sus propios eventos observables, más allá de las devoluciones de llamada de ActiveRecord. Por ejemplo, mi modelo de Usuario expone 2 eventos:

class User < Party411BaseModel self.observers ||= [] after_commit :notify_observers, :on => :create def signed_up_via_lunchwalla self.account_source == ACCOUNT_SOURCES[''LunchWalla''] end def notify_observers notify_observers(self, :new_user_created) notify_observers(self, :new_lunchwalla_user_created) if self.signed_up_via_lunchwalla end end

Cualquier observador que desee recibir notificaciones de esos eventos simplemente necesita (1) registrarse con el modelo que expone el evento y (2) tener un método cuyo nombre coincida con el evento. Como se podría esperar, múltiples observadores pueden registrarse para el mismo evento y (en referencia al segundo párrafo de la pregunta original) un observador puede observar eventos en varios modelos.

Las clases de observadores NotificationSender y ProfilePictureCreator a continuación definen los métodos para los eventos expuestos por varios modelos:

NotificationSender def new_user_created(user_id) ... end def new_invitation_created(invitation_id) ... end def new_event_created(event_id) ... end end class ProfilePictureCreator def new_lunchwalla_user_created(user_id) ... end def new_twitter_user_created(user_id) ... end end

Una advertencia es que los nombres de todos los eventos expuestos en todos los modelos deben ser únicos.


Mi sugerencia es leer la publicación del blog de James Golick en http://jamesgolick.com/2010/3/14/crazy-heretical-and-awesome-the-way-i-write-rails-apps.html (trate de ignorar cómo inmodesto el título suena).

De vuelta en el día, todo era "modelo gordo, controlador flaco". Luego, los modelos de grasa se convirtieron en un dolor de cabeza gigante, especialmente durante las pruebas. Más recientemente, el impulso ha sido para los modelos delgados, la idea es que cada clase debe manejar una responsabilidad y el trabajo de un modelo consiste en conservar sus datos en una base de datos. Entonces, ¿dónde termina toda mi compleja lógica de negocios? En las clases de lógica de negocios: clases que representan transacciones.

Este enfoque puede convertirse en un atolladero (risueño) cuando la lógica comienza a complicarse. Sin embargo, el concepto es sensato: en lugar de desencadenar cosas de forma implícita con devoluciones u observadores que son difíciles de probar y depurar, desencadenan cosas de manera explícita en una clase que aplica lógica a la capa superior de su modelo.




github.com/krisleech/wisper es una gran solución. Mi preferencia personal para las devoluciones de llamadas es que los disparan los modelos, pero los eventos solo se escuchan cuando llega una solicitud, es decir, no quiero que se activen las devoluciones de llamadas mientras estoy configurando modelos en pruebas, etc. pero las quiero disparado cuando los controladores están involucrados. Esto es realmente fácil de configurar con Wisper porque puedes decirle que solo escuche los eventos dentro de un bloque.

class ApplicationController < ActionController::Base around_filter :register_event_listeners def register_event_listeners(&around_listener_block) Wisper.with_listeners(UserListener.new) do around_listener_block.call end end end class User include Wisper::Publisher after_create{ |user| publish(:user_registered, user) } end class UserListener def user_registered(user) Analytics.track("user:registered", user.analytics) end end