Observers vs. Callbacks - ruby-on-rails

i thought about using observers or callbacks.
What and when you should use an observer?
F.e. you could do following:
# User-model
class User << AR
after_create :send_greeting!
def send_greeting!
UserNotifier.deliver_greeting_message(self)
end
end
#observer
class UserNotifier << AR
def greeting_message(user)
...
end
end
or you could create an observer and let it watch when users becomes created...
What dou you recommened?

One really important distinction to keep in mind, which is related to Milan Novota's answer, is that callbacks on an ActiveRecord have the ability to cancel the action being called and all subsequent callbacks, where as observers do not.
class Model < ActiveRecord::Base
before_update :disallow_bob
def disallow_bob
return false if model.name == "bob"
end
end
class ModelObserver < ActiveRecord::Observer
def before_update(model)
return false if model.name == "mary"
end
end
m = Model.create(:name => "whatever")
m.update_attributes(:name => "bob")
=> false -- name will still be "whatever" in database
m.update_attributes(:name => "mary")
=> true -- name will be "mary" in database
Observers may only observe, they may not intervene.

You can use observers as a means of decoupling or distribution of responsibility. In the basic sense - if your model code gets too messy start to think about using observers for some unessential behavior. The real power (at least as I see it) of observers lies in their ability to serve as a connection point between your models and some other subsystem whose functionality is used by all (or some) of the other classes. Let's say you decide to add an IM notification to your application - say you want to be notified about some (or all) of the CRUD actions of some (or all) of the models in your system. In this case using observers would be ideal - your notification subsystem will stay perfectly separated from your business logic and your models won't be cluttered with behavior which is not of their business. Another good use case for observers would be an auditing subsystem.

A callback is more short lived: You pass it into a function to be called once. It's part of the API in that you usually can't call the function without also passing a callback. This concept is tightly coupled with what the function does. Usually, you can only pass a single callback..
Example: Running a thread and giving a callback that is called when the thread terminates.
An observer lives longer and it can be attached/detached at any time. There can be many observers for the same thing and they can have different lifetimes.
Example: Showing values from a model in a UI and updating the model from user input.

Related

Default creation of has_one dependency in rails

I'd like to understand best practices for creating a dependency of a model in rails. The scenario is simple. The two models are:
class Main < ActiveRecord::Base
has_one :dependent
validates :dependent, presence: true
end
class Dependent < ActiveRecord::Base
end
(Note that I want to validate to ensure the dependent always exists)
Whenever a Main object is created I want a Dependent object to be created and "default initialized". I come from a background of C++ hence I view this problem as one of constructing a member variable whose type is some class which has a default constructor.
There are a bunch of ways I can solve this.
Put logic in before_validation to create a Dependent.
This feels very "un-railsy". I wanted to do this in before_create but validations are done before that callback. Doing it in before_validation is ugly as this callback is called both on create and on update, which makes the logic tricky/messy.
Put logic in .new
This feels very very "un-railsy" and is probably conceptually wrong. I'd see new as performing ActiveRecord construction which happens before the model is built.
Make the caller do the work
Whenever a Main object is created it must be done via new-save rather than create. The calling code then has to create Dependent itself, albeit with default values, e.g.
Main.new do |m|
m.dependent = Dependent.create
end
This is annoyingly burdensome on callers and causes a lot of duplicate code. I could pack this into a factory type method but the same problem exists that calling code needs to do some legwork.
Is there a canonical solution to this?
You should try using after_create callback and create Dependent instance in this method.
To answer my own question, I found a callback I hadn't see listed before: after_initialize. I can do what I want here.
A note for others:
My particular case is quite straightforward as I always want my dependent class to be default initialized and the user doesn't ever need to set anything. However, in a more complex situation this wouldn't work and initializing dependents may require:
Explicit initialization at the site of creation
UI for user to initialize the dependent using #accepts_nested_attributes_for

Best practice CFWheels (or RoR, or any other framework) sending email

From my research, it seems there's a general consensus that sending email is something that belongs in the Controller. So for example, if I have a signup form for People, when a user submits a signup form, I would validate the Person, and then once the Person is saved, the People controller would do more stuff - for example, send an email confirmation message, send a welcome email with attachments, and send an email to an admin.
That's fine until there's another part of the application that ALSO creates people. Easy enough to call the Person model and create(), but what about all that extra stuff that might (or might not!) need to happen... should the developer have to remember to do all that stuff in any controller of the application? How do you keep your code DRY in this case?
My inclination was to make an "after create" filter in the Person model, and perhaps add an optional parameter that would disable sending of email when a Person is created, but testing becomes a nightmare, etc.
How do you keep from having all the other parts of the application have to know so many rules about creating a new Person? I want to refactor, but not sure which direction to go.
So, you create users in controllers and you create them somewhere else, and you want to keep DRY? This calls for a builder!
class UserBuilder
attr_reader :user_params, :user, :send_welcome_email
def initialize(user_params, send_welcome_email: true)
#user_params = user_params
#send_welcome_email = send_welcome_email
end
def build
instantiate_user
end
def create
instantiate_user
before_create(user)
return false unless user.save
after_create(user)
end
private
def instantiate_user
#user ||= User.new(user_params)
end
def before_create(user)
end
def after_create(user)
# or do whatever other check you can imagine
UserMailer.welcome_email(user) if send_welcome_email
end
end
Usage:
# in controller
UserBuilder.new(params[:user]).create
# somewhere else
user_params = { email: 'blah#example.com' }
UserBuilder.new(user_params, send_welcome_email: false)
RE additional info
Also, CFWheels only provides sendEmail() for controllers, not models
This is ruby, it has built-in email capabilities. But fine, I'll play along. In this case, I would add some event/listener sauce on top.
class UserBuilder
include Wisper::Publisher
...
def after_create(user)
# do whatever you always want to be doing when user is created
# then notify other potentially interested parties
broadcast(:user_created, user)
end
end
# in controller
builder = UserBuilder.new(params[:user])
builder.on(:user_created) do |user|
sendEmail(user) # or whatever
end
builder.create
This is probably better suited for StackExchange's Software Engineering.
You have to consider that sending follow-up emails (like a welcome message) must not be linked to the creation of a new person. A function should always do just one thing and shall not depend on or require other functions to execute.
// Pseudocode
personResult = model.createPerson(data)
if personResult.Successful {
sendWelcomeMessage(personResult.Person)
sendAdminNotification(personResult.Person)
} else {
sendErrorNotification(personResult.Debug)
}
Your goal is to decouple every step in a process flow, so you can easily change process flow details without needing to change functions.
If your process flow occurs in different locations in your application, you should wrap the process in a function and call it. Imagine the above code in a function named createPersonWithNotifictions(data). Now you are flexible and can easily wrap an alternative person creation flow in another function.
Just add a Mail entity to the model. It will have the responsibility of sending a message of a particular type (Welcome, Notification, Error) to Person, Admin or Debugger. The Controller will then have the responsibility of collaborating with entities such as Person, Mail and Message to send the respective types of messages.
My final solution in CFWheels was to create a model object called "PeopleManager". I hated using "manager" in the name at first, but now it makes sense to me. However, if this fits a specific design pattern/name, i'm all ears.
Basically, the convention in my application will be that all the various modules that want a "new person" will need to go through the manager to get it. In this way, it is easy to control what happens when a new person is created, and for which areas of the application. For example, if a user creates a new Comment and their email address is not already a record in the People table, the Comment controller will be requesting a new person. When it makes that request of the PeopleManager, it is in that object that the business logic "When a new person is created from a Comment, send out a welcome message" will exist. While I'm not sure yet how the method names will pan out, so far I am considering going the route of "getNewPersonForComment"... and each module will have it's own types of calls. Repeated code in the PeopleManager (i.e. several of these distinct functions may all use the same steps) will be abstracted into private methods.
This provides a layer between modules and the data access layer, and also keeps the DAO type wheels objects from getting too "smart" and straying from the single responsibility principle.
I haven't worked out all the details yet. Especially whether or not a controller that will be using a Manager should be explicitly 'handed' that manager or whether simply treating the Manager as an object like any other (in cfwheels, model("PeopleManager").doSomething() is sufficient.
With regards to the differences between RoR and CFWheels when it comes to emailing, CFW does not have the concepts of a "Mailer" like RoR does, and the sendMail() function is a controller-only function. So, I have basically developed a mail queue feature that gets processed asynchronously instead, and will (hopefully) act much like the RoR analogue. This may become a CFWheels plugin. I have a feeling the need for this type of workaround revolves around the fact that controllers in CFW cannot easily call other controllers, and the debugging becomes nightmare-ish.
It's still evolving, and I welcome comments on my solution.

Independent observation of model events in Rails

What is the best way in rails to implement the situation where when Model A is created, Model B can observe this and change.
One solution is some kind of filter such as an after_create in Model A. However having some Model B code in Model A seems like a bad practise.
Another solution is to have some code in Model B's controller to handle this which also seems like a bad idea.
Ideally Model B or some kind of independent observer class should be able to observe the creation of all Model A's and then act as required.
Update:
Thanks to OP for pointing out that this was for Rails4 as the question was originally tagged which I had missed to notice. Rails 4 alternative to Observers question here at SO has several great answers.
Original Answer:
This can be done using Observer. Say you want a ModelAObserver, where you'd define the operations required on ModelB, you can create a new file app/models/model_a_observer.rb manually or use the generator rails g observer ModelA.
Then define the required callbacks in the ModelAObserver:
# app/models/model_a_observer.rb
class ModelAObserver < ActiveRecord::Observer
observe :model_a
def after_create(new_model_a_record)
...
# Operations on ModelB
...
end
end

Rails - how do I avoid having to trigger the create action in one controller from the create action in another controller

Forgive a basic question here.
From reading around, I understand it's not best practice to trigger the create action of a controller from another - I'm looking for advice on how I should be organizing my code in the following situation:
I have two controllers: cases and notifications.
When a new case is created I want it to also create a new
notification.
The notification create action creates a new instance in it's model,
and sends an email.
Am I right to think that I shouldn't just be calling Notification#create from my cases controller? Should I be extracting this to a helper or module? If so, what would it look like? The other roughly similar posts on this topic don't elaborate.
Thank you.
Since create is a POST method, I don't think there is a way for calling create of notifications controller from cases controller. My suggestion would be to move the creation of notification instance and the sending mail logic to the before save of your case model. This would create a notification each time a case is created and would also take care of the mail sending mechanism. Since this is a part of your business requirement it is better to move the business logic into your model.
This logic should be in your models. Good solution is to use ActiveRecord callbacks for it.
# app/models/case.rb
class Case < ActiveRecord:Base
after_create :create_notification
private # <--- bottom of your model
def create_notification
Notification.create!(some_options_for_notification)
end
end
# app/models/notification.rb
class Notification < ActiveRecord:Base
after_create :send_notification
def send(opts)
... # <--- some logic to send notification here
end
private # <--- bottom of your model
def send_notification
send(some_options_for_sending)
end
end
As one of options you can use rails observers for creating notification http://api.rubyonrails.org/classes/ActiveRecord/Observer.html
There are few differences between observers and callbacks, for example observers can't cancel any model action, but in your case I think there is no matter what to use.
But observers also used to adhere to the Single Responsibility principle.
Example
class CaseObserver < ActiveRecord::Observer
def after_create(case)
#create notification
end
end
should be saved to /app/models/case_observer.rb.
in your config/application.rb
config.active_record.observers = :case_observer
You just need to write Notification.create in your case controllers action, that's it.
You can write as much of code, create as much of model in your controller action as much you want.

Rails: Indicating that a model has an observer

A model I've implemented needs to escape/format one of the fields into an html-friendly version of the entered text (for the sake of argument, lets say it's a blue/redcloth stlye thing).
I implemented this in an observer with the before_save callback, all working, no complaints. I am learning rails as I code and it struck me that from just looking at the model there is no indication that it has an observer.
Is there a neat way of indicating (for other programmers who may view/edit my code) that a model has an observer or should I just do this in a comment (or not at all).
As far as I know, you do not need to specify on the target class/model that it has an observer all you need to do is make sure that the observer lists which models it is observing.
class ContentObserver < ActiveRecord::Observer
observe :answer, :audio_clip, :document
#This right here ^
def after_update(record)
record.recent_activity.save!
end
end
Have a spec/test file for the observer. If, in the future, someone changes the model in a way that upsets the observer then the spec/test will fail which will alert them.

Resources