With Observers officially removed from Rails 4.0, I\'m curious what other developers are using in their place. (Other than using the extracted gem.) While Observers were cer
Wisper is a great solution. My personal preference for callbacks is that they're fired by the models but the events are only listened to when a request comes in i.e. I don't want callbacks fired while I'm setting up models in tests etc. but I do want them fired whenever controllers are involved. This is really easy to setup with Wisper because you can tell it to only listen to events inside a block.
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
You could try https://github.com/TiagoCardoso1983/association_observers . It is not yet tested for rails 4 (which wasn't launched yet), and needs some more collaboration, but you can check if it does the trick for you.
It's worth mentioning that Observable module from Ruby standard library cannot be used in active-record-like objects since instance methods changed?
and changed
will clash with the ones from ActiveModel::Dirty.
Bug report for Rails 2.3.2
My suggestion is to read James Golick's blog post at http://jamesgolick.com/2010/3/14/crazy-heretical-and-awesome-the-way-i-write-rails-apps.html (try to ignore how immodest the title sounds).
Back in the day it was all "fat model, skinny controller". Then the fat models became a giant headache, especially during testing. More recently the push has been for skinny models -- the idea being that each class should be handling one responsibility and a model's job is to persist your data to a database. So where does all my complex business logic end up? In business logic classes -- classes that represent transactions.
This approach can turn into a quagmire (giggity) when the logic starts getting complicated. The concept is sound though -- instead of triggering things implicitly with callbacks or observers that are hard to test and debug, trigger things explicitly in a class that layers logic on top of your model.
How about using a PORO instead?
The logic behind this is that your 'extra actions on save' are likely going to be business logic. This I like to keep separate from both AR models (which should be as simple as possible) and controllers (which are bothersome to test properly)
class LoggedUpdater
def self.save!(record)
record.save!
#log the change here
end
end
And simply call it as such:
LoggedUpdater.save!(user)
You could even expand on it, by injecting extra post-save action objects
LoggedUpdater.save(user, [EmailLogger.new, MongoLogger.new])
And to give an example of the 'extras'. You might want to spiffy them up a bit though:
class EmailLogger
def call(msg)
#send email with msg
end
end
If you like this approach, I recommend a read of Bryan Helmkamps 7 Patterns blog post.
EDIT: I should also mention that the above solution allows for adding transaction logic as well when needed. E.g. with ActiveRecord and a supported database:
class LoggedUpdater
def self.save!([records])
ActiveRecord::Base.transaction do
records.each(&:save!)
#log the changes here
end
end
end
In some cases I simply use Active Support Instrumentation
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