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 certainly abused and could easily become unwieldily at times, there were many use-cases outside of just cache-clearing where they were beneficial.
Take, for example, an application that needs to track changes to a model. An Observer could easily watch for changes on Model A and record those changes with Model B in the database. If you wanted to watch for changes across several models, then a single observer could handle that.
In Rails 4, I'm curious what strategies other developers are using in place of Observers to recreate that functionality.
Personally, I'm leaning towards a sort of "fat controller" implementation, where these changes are tracked in each models controller's create/update/delete method. While it bloats the behavior of each controller slightly, it does help in readability and understanding as all the code is in one place. The downside is that there's now code that is very similar scattered throughout several controllers. Extracting that code into helper methods is an option, but you're still left with calls to those methods littered everywhere. Not the end of the world, but not quite in the spirit of "skinny controllers" either.
ActiveRecord callbacks are another possible option, though one I don't personally like as it tends to couple two different models too closely together in my opinion.
So in the Rails 4, no-Observers world, if you had to create a new record after another record was created/updated/destroyed, what design pattern would you use? Fat controllers, ActiveRecord callbacks, or something else entirely?
Thank you.
Take a look at Concerns
Create a folder in your models directory called concerns. Add a module there:
module MyConcernModule
extend ActiveSupport::Concern
included do
after_save :do_something
end
def do_something
...
end
end
Next, include that in the models you wish to run the after_save in:
class MyModel < ActiveRecord::Base
include MyConcernModule
end
Depending on what you're doing, this might get you close without observers.
They are in a plugin now.
Can I also recommend an alternative which will give you controllers like:
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
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.
Using active record callbacks simply flips the dependency of your coupling. For instance, if you have modelA and a CacheObserver observing modelA rails 3 style, you can remove CacheObserver with no issue. Now, instead say A has to manually invoke the CacheObserver after save, which would be rails 4. You've simply moved your dependency so you can safely remove A but not CacheObserver.
Now, from my ivory tower I prefer the observer to be dependent on the model it's observing. Do I care enough to clutter up my controllers? For me, the answer is no.
Presumably you've put some thought into why you want/need the observer, and thus creating a model dependent upon its observer is not a terrible tragedy.
I also have a (reasonably grounded, I think) distaste for any sort of observer being dependent on a controller action. Suddenly you have to inject your observer in any controller action (or another model) that may update the model you want observed. If you can guarantee your app will only ever modify instances via create/update controller actions, more power to you, but that's not an assumption I would make about a rails application (consider nested forms, model business logic updating associations, etc.)
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
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
My alternative to Rails 3 Observers is a manual implementation which utilizes a callback defined within the model yet manages to (as agmin states in his answer above) "flip the dependency...coupling".
My objects inherit from a base class which provides for registering observers:
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
(Granted, in the spirit of composition over inheritance, the above code could be placed in a module and mixed in each model.)
An initializer registers observers:
User.add_observer(NotificationSender)
User.add_observer(ProfilePictureCreator)
Each model can then define its own observable events, beyond the basic ActiveRecord callbacks. For instance, my User model exposes 2 events:
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
Any observer that wishes to receive notifications for those events merely needs to (1) register with the model that exposes the event and (2) have a method whose name matches the event. As one might expect, multiple observers can register for the same event, and (in reference to the 2nd paragraph of the original question) an observer can watch for events across several models.
The NotificationSender and ProfilePictureCreator observer classes below define methods for the events exposed by various models:
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
One caveat is that the names of all events exposed across all the models must be unique.
I think the the issue with Observers being deprecated is not that observers were bad in and of themselves but that they were being abused.
I would caution against adding too much logic in your callbacks or simply moving code around to simulate the behavior of an observer when there is already a sound solution to this problem the Observer pattern.
If it makes sense to use observers then by all means use observers. Just understand that you will need to make sure that your observer logic follows sound coding practices for example SOLID.
The observer gem is available on rubygems if you want to add it back to your project
https://github.com/rails/rails-observers
see this brief thread, while not full comprehensive discussion I think the basic argument is valid.
https://github.com/rails/rails-observers/issues/2
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.
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
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
I have the same probjem! I find a solution ActiveModel::Dirty so you can track your model changes!
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
Related
Learning Rails, I just faced something where some lights could be helpful.
I have the classes, A, B, C that all do an action.
And I have a Message model and I would like, when I am going to save, to call one of those classes, based on the user output.
I am struggling now on what would be the more rubyist way to write the code for the model but also the classes, depending on the model method.
Option A:
case #user.flag:
when 'alpha'
A.new(message)
when 'beta'
B.new(message)
when 'gamma'
C.new(message)
Option B:
Moving A,B,C from classes to user flag Instance methods of a Module called Functions
Functions.send(#user.flag.to_sym,message)
Since I have little knowledge of Rails, I am looking for how to write the most clean and reusable code. Thanks in advance.
As with many design decisions, there's numerous approaches you could, each of which would be "correct" mostly based on preference. Here's how I'd do it.
Firstly, I'd make sure #user.flags can only take on certain values since its value is being used to decide other actions. In Ruby the generally accepted way of handling these values is also as symbols since a given symbol is immutable.
Secondly, since you're doing something with the Message model after it's saved you can utilize the after_save callback and keep the action inside the Message model itself. This makes it more tied to the message model and makes it more readable in general.
Lastly, you'll want some sort of guarantee that your save/transaction rolls back if there's an error with your after_save action. Going off this answer you can do that by raising an error in `after_save_
In app/models/user.rb
class User < ActiveRecord::Base
FLAGS = %w[alpha beta gamma].freeze
# Ensuure that `flag` field can only take on certain pre-defined values
# Also validate that flag can never be nil. You may need to change that
# as needed for your application
validates :flag, presence: true, inclusion: FLAGS
def flag
# This method isn't 100% necessary but I like to personally follow
# the pracitce of returning symbols for enumerated values
super(flag).try(:to_sym)
end
end
In app/models/message.rb
class Message < ActiveRecord::Base
after_save :post_process_message
private
# I'd recommend a better name for this method based on what you're
# specifically doing
def post_process_message
# Notice the more descriptive method name
# Also no need to pass `message` as a param since it's now located
# inside this model. You could also move it to a separate class/service
# as needed but don't over-optimize until you need to
send("handle_post_process_for_flag_#{user.flag}")
rescue StandardError => e
# Something went wrong, rollback!
# It isn't "great practice" to rescue all errors so you may want to replace
# this with whatever errrors you excpect your methods to throw. But if you
# need to, it's fine to be conservative and rescue all on a case-by-case
# basis
raise ActiveRecord::RecordInvalid.new(self)
end
def handle_post_process_for_flag_alpha
end
def handle_post_process_for_flag_beta
end
def handle_post_process_for_flag_gamma
end
end
This is an interesting question, as #user2490003 said, there is no write/wrong way of doing this.
Your approach will change depending on how your A,B and C classes implement and their representation and also what your method does in each class.
Let's take an example, a method called talk and two classes Man, Women.
So, you may implement this as
Individual class methods
class Man
def talk
# talk like an adult
end
end
class Women
def talk
# talk like an adult
end
end
However as you can see, this talk method is same for both Man and Women and also you can see they both normally share the same functionalities and attributes. So, create a base class called Human and move the talk method there
As a base class method
class Human
def talk
# talk like an adult
end
end
class Man < Human
end
class Woman < Human
end
Now let's get an example of a baby and say baby talk differently than Man and Woman, although baby still inherits from Human. In such cases you can do
class Baby < Human
def talk
# baby talk
end
end
What happens here is, Baby will inherit from Human , but when you call
Baby.new.talk # => baby talk
it execute the talk method in Baby class (not in the Human class)
Extracting the method to a module
Let's get a Parrot class, and assume that it has a talk method too, and also it's same as Human talk.
Now the problem we have is we cannot inherit Parrot class from Human, but we still want to have the code in the talk method. In a case like that, you can use a Module, so, you can do
module Talkable
def talk
# talk like an adult
end
end
class Human
include Talkable
end
class Parrot
include Talkable
end
As, as I explained (or at least tried..), your implementation will depend on how your class A,B,C and Message classes are related.
What I personally do in situations like this is, get a pen and paper and try to map these objects without thinking about how to implement then in ruby or any language. Once you have an idea on how they all hang together, it's easy to find the syntax to implement it
Say I have product model(app/models/product.rb) and the following method def in the model.
def update_stock product
product.stock -= 1
product.save(:validation => false)
end
Is this acceptable?
Thanks in advance.
This is a good practice to keep the database operations and business logic of an application in model. Model directly interacts with database with ORM (Object Relational mapping). Also in rails convention controller should be thin and model should be fat .This answer explains it in details
What is Ruby on Rails ORM in layman's terms? Please explain
This looks suspicious to me - you say that this is a method in your Product model, but then the method updates an entirely different product instance (the one you pass in, not the one you call the method on).
If this was something like:
class Product < ActiveRecord::Base
def update_stock
self.stock -= 1
save
end
end
Then it would be much more appropriate. (update_stock doesn't seem like the best name for the method, either, and skipping the validations likely isn't a good idea.)
As you want to update something better use update_attributes!
It may be write in this way:
class Product < ActiveRecord::Base
def update_stock product
self.update_attributes!(stock: stock - 1)
end
end
It's up to you!
The simplest way I use to deduce how to do things is to look at it from a modular perspective. If you have "heavy" code in a controller, will you benefit from using that code in another area of the app?
Rails is a series of classes & modules
Code is accessible to different parts of the app, depending on which modules are called. If you feel like you'll benefit from re-using the method in different areas of the app, you should include either an instance or class method in the model, as this will open this method to those objects
I would use the code provided by RSB & sevenseacat, probably using the decrement! method in sevenseacats answer:
self.decrement!(:stock)
In regards to your question, I would personally do that, but would make it an instance method. sevenseacat basically explained what I'd do
I'm working on an app at work. Basic stuff, user signs up (with an associated organization).
Initially I started off with a simple controller -
# Need to check if organization exists already; deny user creation if it does
if #organization.save
#user.save
redirect_to user_dashboard_path...
I soon found myself in a callback soup:
After the organization is validated, we save the user.
When the organization is created, I create another two models, EmailTemplate and PassTemplate (an organization has_one :email_template, has_one :pass_template)
after_create :init_company, :init_email_template, :init_pass_template, :init_form
Each of those callbacks generally calls method on the model, something like:
def init_email_template
self.email_template.create_with_defaults
end
Initially I thought this was quite clever - doing so much behind the scenes, but I've been reading Code Complete by Steve McConnell, and feel this is not simple at all. If I didn't know what was happening already, There's no hint that any time an organization is created it creates 3 associated objects (and some of those objects in turn initialize children objects).
It seems like a bad programming practice, as it obfuscates what's going on.
I thought about moving all of those initalizations to the controller, as an organization is only ever created once:
class OrganizationsController < AC
...
def create
if #organization.save
#organization.create_user
#organization.create_email_template
#organization.create_pass_template
end
That seems like cleaner code, and much easier to follow.
Question 1
*Are there better solutions, or best practices for handling creating associated objects upon creation of the hub object that I'm unaware of?*
Side note - I would have to rewrite a bunch of tests that assume that associations are automatically created via callbacks - I'm okay with that if it's better, easier to understand code.
Question 2
**What about a similar situation with after_save callbacks?**
I have a customer model that checks to see if it has an associated user_account after creation, and if not, creates it. It also creates a Tag model for that user_account once we've created the user_account
class Customer < AR
after_create :find_or_create_user_account
def find_or_create_user_account
if !self.user_account_exists?
#create the user
end
Tag.create(:user_id => self.user_account.id)
end
end
Somewhat simplified, but again, I believe it's not particularly good programming. For one, I'm putting logic to create two different models in a third model. Seems sloppy and again the principle of separating logic. Secondly, the method name does not fully describe what it's doing. Perhaps find_or_create_user_account_and_tag would be a better name, but it also goes against the principle of having the method do one thing- keeping it simple.
After reading about observers and services, my world was thrown for a bit of a loop.
A few months ago I put everything in controllers. It was impossible to test well (which was fine because I didn't test). Now I have skinny controllers, but my models are obese and, I think, unhealthy (not clear, not obvious, harder to read and decipher for another programmer/myself in a few months).
Overall I'm just wondering if there are some good guides, information, or best practices on separation of logic, avoiding callback soup, and where to different sorts of code
Why not the following?
after_create :init_associated_objects
def init_associated_objects
init_company
init_email_template
init_pass_template
init_form
end
My interpretation with "a method should do one thing" isn't strict and that I usually have a method that calls other method (much like the one above). At the end of the day, it's a divide and conquer strategy.
Sometimes I create utility POROs (plain old ruby objects) when it doesn't make sense to have an AR model but a group of functionalities is a class' responsibility. Reports, for instance, are not AR-backed models but it's easier when a report that needs to call multiple models is just instantiated once where the reporting period start and end are instance variables.
A rule of thumb that I follow: if I instantiate the models outside of the whole MVC stack (e.g. Rails console), the things that I expect to happen should stay inside the model.
I don't claim best practices but these have worked for me so far. I'm sure other people would have a better idea on this.
I have an ActiveRecord model Reservation.
It got to the point that the class is to large and does too much.
I would like to split it into a few different ones and place those under the Reservation module.
Unfortunately this will break the app.
At this moment I see following options:
namespace it to something like ReservationConcerns or similar
add the functionality to the Reservation class itself, but physically move it to the subdir (Reservation would be in app/models/reservation.rb, Reservation::Pipeline would be in app/models/reservation/pipeline.rb etc).
So the question is how to structure the different concerns of a feature already having it as one single, bulky class without breaking the app.
If you want to split up a Ruby class into different components without changing its public interface, one solution is to use modules:
# app/models/reservation.rb
class Reservation < ActiveRecord::Base
# associations, validations, etc.
include Pipeline
end
# app/models/reservation/pipeline.rb
module Reservation::Pipeline
def some_pipeline_method
# ...
end
def other_pipeline_method
# ...
end
end
ActiveRecord also provides observers, which "is a great way to reduce the clutter that normally comes when the model class is burdened with functionality that doesn't pertain to the core responsibility of the class". Observers often make heavy use of the ActiveModel::Dirty methods.
These suggestions are complimentary: modules can help you group your interface into more local chunks, while observers can make backend details more self-contained. From here, it's difficult to be more specific without knowing exactly what pieces you have that you're trying to break out.
To preserve data integrity, I need to prevent some models from being modified after certain events. For example, a product shouldn't be allowed to be written off after it has been sold.
I've always implemented this in the controller, like so (pseudo-ish code):
def ProductsController < ApplicationController
before_filter require_product_not_sold, :only => [ :write_off ]
private
def require_product_not_sold
if #product.sold?
redirect_to #product, :error => "You can't write off a product that has been sold"
end
end
end
It just struck me that I could also do this in the model. Something like this:
def Product < ActiveRecord::Base
before_update :require_product_not_sold
private
def require_product_not_sold
if self.written_off_changed?
# Add an error, fail validation etc. Prevent the model from saving
end
end
end
Also consider that there may be several different events that require that a product has not been sold to take place.
I like the controller approach - you can set meaningful flash messages rather than adding validation errors. But it feels like this code should be in the model (eg if I wanted to use the model outside of my Rails app).
Am I doing it wrong?
What are the advantages of handling this in my model?
What are the disadvantages of handling this in my model?
If I handle it in the model, should I really be using validates rather than a callback? What's the cleanest way to handle it?
Thanks for your ideas :)
It seems like you already have this one covered, based on your question. Ideally a model should know how to guard its state, as data objects are typically designed with portability in mind (even when they'll never be used that way).
But in this case you want to prevent an action before the user even has access to the model. Using a model validation in this case means you're too late and the user has already gone farther than he should by having access to and attempting to write off a product which should never have been accessible based on its sold status.
So I guess the ideal answer is "both." The model "should" know how to protect itself, as a backup and in case it's ever used externally.
However in the real world we have different priorities and constraints, so I'd suggest implementing the change you listed if feasible, or saving it for the next project if not.
As far as using a model callback versus a validation, I think that's a trickier question but I'll go with a validation because you'd likely want to present a message to the user and validation is built for exactly that use (I'd consider this more of a friendly and expected user error than a hostile or security-related one which you might handle differently).
Is that along the lines of what you've been considering?