I'm using restful_authentication in my app. I'm creating a set of default users using a rake task, but every time I run the task an activation email is sent out because of the observer associated with my user model. I'm setting the activation fields when I create the users, so no activation is necessary.
Anyone know of an easy way to bypass observers while running a rake task so that no emails get sent out when I save the user?
Thanks.
Rails 3.1 finally comes with API for this:
http://api.rubyonrails.org/v3.1.0/classes/ActiveModel/ObserverArray.html#method-i-disable
ORM.observers.disable :user_observer
# => disables the UserObserver
User.observers.disable AuditTrail
# => disables the AuditTrail observer for User notifications.
# Other models will still notify the AuditTrail observer.
ORM.observers.disable :observer_1, :observer_2
# => disables Observer1 and Observer2 for all models.
ORM.observers.disable :all
# => disables all observers for all models.
User.observers.disable :all do
# all user observers are disabled for
# just the duration of the block
end
Where ORM could for example be ActiveRecord::Base
You could add an accessor to your user model, something like "skip_activation" that wouldn't need to be saved, but would persist through the session, and then check the flag in the observer. Something like
class User
attr_accessor :skip_activation
#whatever
end
Then, in the observer:
def after_save(user)
return if user.skip_activation
#rest of stuff to send email
end
As a flag for the observer I like to define a class accessor called "disabled" so it reads like this:
class ActivityObserver < ActiveRecord::Observer
observe :user
# used in tests to disable the observer on demand.
cattr_accessor(:disabled)
end
I put it as a condition in the sensitive callbacks
def after_create(record)
return if ActivityObserver.disabled
# do_something
end
and I just turn the flag on when needed
ActivityObserver.disabled=true
Another one you can try (rails 3)
config.active_record.observers = :my_model_observer unless File.basename($0) == 'rake'
In generally, for these sorts of situations, you can:
Set up a mock object to "absorb" the unwanted behavior
Have an externally accessible flag / switch that the observers respect to inhibit the behavior
Add logic to the observer to detect when the behavior is unneeded in general (e.g. what dbarker suggests)
Have a global flag "testing", "debug", "startup" or whatever that changes low level behavior
Introspect and remove the observers
Add a method to your model that performs an alternative, unobserved version of the task (sharing implementation with the normal method as much as possible).
In this case, I'd say #3 is your best bet.
When running tests on an app I am working on, I use the following:
Model.delete_observers
Disabling observers for Rails 3 it's simple:
Rails.configuration.active_record.observers = []
You can take the method off the observer;
MessageObserver.send(:remove_method, :after_create)
Will stop the :after_create on MessageObserver by removing it.
I came here looking for the an answer to the same... none of the above seemed to do the trick (or involve adding migration-specific logic to my application code -- boo).
Here's what I came up with (a bit lame that it needs to go in each relevant migration, but...)
class ChangeSomething < ActiveRecord::Migration
# redefine...
class MessageObserver < ActiveRecord::Observer
def after_create(observed) ; end
def after_update(observed) ; end
end
def self.up
# Message create/update stuff...
end
end
User.skip_callback("create", :after, :send_confirmation_email)
....
User.set_callback("create", :after, :send_confirmation_email)
More on this:
Disabling Callbacks in Rails 3
There isn't a straightforward way to disable observers that I know of, but it sounds possible to add logic to your observer to not send an email when the activation code is set...
As others have hinted; I would wrap the unwanted logic in your Observer with a simple if statement.
def after_create
send_email if RAILS_ENV == "production"
end
Related
I have a model with paper-trail enabled. In one of my API routes, I have to run the_model.update_columns ... so that the model instance can be modified (and saved) without triggering all of the associated callbacks (these callbacks have a ton of side-effects which I don't want for this specific route).
However, I still want this change to be recorded by paper-trail. Is there a reasonable way I can achieve that?
This is simplistic and clunky, but it'd get done exactly what you want:
class ThisModel < ActiveRecord::Base
...
def update_me_no_callbacks(att_1, attr_2, attr_3, ...)
self.update_columns(
attr_1: attr_1,
attr_2: attr_2,
attr_3: attr_3,
...
)
# Do paper-trail code
end
end
I'm trying to send an email notification when the email address is changed. To do that i need to detect first if the attribute actually changed. I think the best place to add the code is in an after_filter in the controller.
# app/controllers/users_controller.rb
after_filter :email_change_notification, only: [:update]
def email_change_notification
UserMailer.notify_new_email if #user.email_changed?
end
My problem now is that email email_changed? does not return expected value when used in this context. It is always false. As an alternative, I can do it in the model after_save
# app/models/user.rb
after_save :email_change_notification
def email_change_notification
UserMailer.notify_new_email if email_changed?
end
This works but I think the former is a better approach since calling a mailer is not part of the model's responsibility.
My question would be:
(1) Where should I put such a callback (model or controller)?
(2) Is there a better way to make the controller approach work?
(3) Is there a better approach than the ones mentioned?
What you may want to implement is an ActiveRecord::Observer
http://api.rubyonrails.org/v3.2.0/classes/ActiveRecord/Observer.html
This is designed exactly for your requiements... a class that performs trigger-like behavour on changes to the original class, but outside the class.
Very easy to set up and use!
I am developing spree extension. What I want to do, is to create new database record when order finalized. I need some suggestion how to do it.
How I understand one way is to create custom method in order class, and register new hook (where I should register new hook? in initializer?).
Other way is to use Activators, but I don't have any idea how to subscribe events. And where I should put code which subscribe order events.
module Spree
class Credit < ActiveRecord::Base
def create_new_line(order)
#I need call this method when order finalized
end
end
end
I found solution. My order decorator looks like this.
Spree::Order.class_eval do
register_update_hook :add_user_credits
def add_user_credits
if (!self.user.nil? and !self.completed_at.nil?)
# do some stuff, only for registered users and when order complete
end
end
end
In your solution I think that the hook will be called every time you update the oder. So if you change something after the order is completed that method will be called again. If it's like this by design that could be the right solution anyway Spree suggests to directly use state machine callback to do stuff like this. For example:
Spree::Order.class_eval do
state_machine do
after_transition :to => :complete, :do => :add_user_credits
end
def add_user_credits
# do some stuff
end
end
This way the code will be executed immediately after the order goes into the complete state.
i'm on the way of redesigning my activity feed, i already implemented the logic with redis and rails (wich works great by the way) but i'm still unsure how to create/trigger the events.
In my first approach i used observer, which had the downside of not having current_user available. and, using observer is a bad idea anyways :)
My preferred method would be to create/trigger the events in the controller, which should look sth like:
class UserController < LocationController
def invite
...
if user.save
trigger! UserInvitedEvent, {creator: current_user, ...}, :create
....
end
end
end
The trigger method should
create the UserInvitedEvent with some params. (:create can be default option)
could be deactivate (e.g. deactivate for testing)
could be executed with e.g. resque
i looked in some gems (fnordmetrics, ...) but i could not find a slick implementation for that.
I'd build something like the following:
# config/initializers/event_tracking.rb
modlue EventTracking
attr_accessor :enabled
def enable
#enabled = true
end
def disable
#enabled = false
end
module_function
def Track(event, options)
if EventTracking.enabled
event.classify.constantize.new(options)
end
end
end
include EventTracking
EventTracking.enable unless Rails.env.test?
The module_function hack let's us have the Track() function globally, and exports it to the global namespace, you (key thing is that the method is copied to the global scope, so it's effectively global, read more here: http://www.ruby-doc.org/core-1.9.3/Module.html#method-i-module_function)
Then we enable tracking for all modes except production, we call event.classify.constantize in Rails that should turn something like :user_invited_event into UserInvitedEvent, and offers the possibility of namespacing, for example Track(:'users/invited'). The semantics of this are defined by ActiveSupport's inflection module.
I think that should be a decent start to your tracking code I've been using that in a project with a lot of success until now!
With the (new) rails intrumentation and ActiveSupport::Notifications system you can completely decouple the notification and the actual feed construction.
See http://railscasts.com/episodes/249-notifications-in-rails-3?view=asciicast
I have a situation where I want to make a request to third-party API(url shortening service) after creating a record in the database (updates a column in the table which stores the short url), in order to decouple the API request from the Model, I have set up an ActiveRecord Observer which kicks in every time a record is created, using after_create callback hook, here is the relevant code:
class Article < ActiveRecord::Base
has_many :comments
end
class ArticleObserver < ActiveRecord::Observer
def after_create(model)
url = article_url(model)
# Make api request...
end
end
The problem in the above code is article_url because Rails Routes are not available in either Model or ModelObservers, same as ActionMailer (similar problem exists in Mails where if we need to put an URL we have to configure "ActionMailer::default_options_url"). In theory accessing routes/request object in Model is considered a bad design. To circumvent the above issue I could include the url_helpers module as described in the following URL:
http://slaive-prog.tumblr.com/post/7618787555/using-routes-in-your-model-in-rails-3-0-x
But this does not seem to me a clean solution, does anybody have a pointer on this issue or any advice on how it should be done?
Thanks in advance.
I would definitely not let your models know about your routes. Instead, add something like attr_accessor :unshortened_url on your Article class. Set that field in your controller, and then use it from your observer. This has the added benefit of continuing to work if you later decide to set your shortened URL asynchronously via a background task.
Edit
A couple of things, first of all.
Let's get the knowledge of creating a short_url out of the model
entirely.
We could nitpick and say that the short_url itself doesn't belong in the model at all, but to remain practical let's leave it in there.
So let's move the trigger of this soon-to-be-background task into the controller.
class ArticlesController < ApplicationController
after_filter :short_url_job, :only => [:create]
# ...
protected
def short_url_job
begin
#article.short_url = "I have a short URL"
#article.save!
rescue Exception => e
# Log thy exception here
end
end
end
Now, obviously, this version of short_url_job is stupid, but it illustrates the point. You could trigger a DelayedJob, some sort of resque task, or whatever at this point, and your controller will carry on from here.