rails2 custom callbacks - ruby-on-rails

I'm using rails 2.3.10 and I'm trying to do the following
I have many models in which I have methods to do certain tasks that do not save or update a model, and I want to keep track of who and when these tasks were performed
I'd like to be able to do something like this in certain model:
# myModel.rb
track :send_email, :send_fax, :accept_invite
def send_email
...
end
def send_fax
...
end
def accept_invite
...
end
and after each of these, perform another set of tasks, such as saving this info to the db
How could I set callbacks such as after_send_email, after_send_fax, etc?

You can look at this railscasts: http://railscasts.com/episodes/249-notifications-in-rails-3
But I don't think it's the best way to go. Still interesting though.
edit: sorry, forget about that, I was thinking about controller callbacks...
Maybe you can just do something like:
after_filter :after_send_email, :only => [:send_email]
def after_send_email
end

Related

How to skip all callbacks except paper-trail when updating an ActiveRecord model?

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

How call custom class method, when Spree order finalize! method executed

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.

Create Rails model with argument of associated model?

I have two models, User and PushupReminder, and a method create_a_reminder in my PushupReminder controller (is that the best place to put it?) that I want to have create a new instance of a PushupReminder for a given user when I pass it a user ID. I have the association via the user_id column working correctly in my PushupReminder table and I've tested that I can both create reminders & send the reminder email correctly via the Rails console.
Here is a snippet of the model code:
class User < ActiveRecord::Base
has_many :pushup_reminders
end
class PushupReminder < ActiveRecord::Base
belongs_to :user
end
And the create_a_reminder method:
def create_a_reminder(user)
#user = User.find(user)
#reminder = PushupReminder.create(:user_id => #user.id, :completed => false, :num_pushups => #user.pushups_per_reminder, :when_sent => Time.now)
PushupReminderMailer.reminder_email(#user).deliver
end
I'm at a loss for how to run that create_a_reminder method in my code for a given user (eventually will be in a cron job for all my users). If someone could help me get my thinking on the right track, I'd really appreciate it.
Thanks!
Edit: I've posted a sample Rails app here demonstrating the stuff I'm talking about in my answer. I've also posted a new commit, complete with comments that demonstrates how to handle pushup reminders when they're also available in a non-nested fashion.
Paul's on the right track, for sure. You'll want this create functionality in two places, the second being important if you want to run this as a cron job.
In your PushupRemindersController, as a nested resource for a User; for the sake of creating pushup reminders via the web.
In a rake task, which will be run as a cron job.
Most of the code you need is already provided for you by Rails, and most of it you've already got set in your ActiveRecord associations. For #1, in routes.rb, setup nested routes...
# Creates routes like...
# /users/<user_id>/pushup_reminders
# /users/<user_id>/pushup_reminders/new
# /users/<user_id>/pushup_reminders/<id>
resources :users do
resources :pushup_reminders
end
And your PushupRemindersController should look something like...
class PushupRemindersController < ApplicationController
before_filter :get_user
# Most of this you'll already have.
def index
#pushup_reminders = #user.pushup_reminders
respond_with #pushup_reminders
end
# This is the important one.
def create
attrs = {
:completed => false,
:num_pushups => #user.pushups_per_reminder,
:when_sent => Time.now
}
#pushup_reminder = #user.pushup_reminders.create(attrs)
respond_with #pushup_reminder
end
# This will handle getting the user from the params, thanks to the `before_filter`.
def get_user
#user = User.find(params[:user_id])
end
end
Of course, you'll have a new action that will present a web form to a user, etc. etc.
For the second use case, the cron task, set it up as a Rake task in your lib/tasks directory of your project. This gives you free reign to setup an action that gets hit whenever you need, via a cron task. You'll have full access to all your Rails models and so forth, just like a controller action. The real trick is this: if you've got crazy custom logic for setting up reminders, move it to an action in the PushupReminder model. That way you can fire off a creation method from a rake task, and one from the controller, and you don't have to repeat writing any of your creation logic. Remember, don't repeat yourself (DRY)!
One gem I've found quite useful in setting up cron tasks is the whenever gem. Write your site-specific cron jobs in Ruby, and get the exact output of what you'd need to paste into a cron tab (and if you're deploying via Capistrano, total hands-off management of cron jobs)!
Try setting your attr_accessible to :user instead of :user_id.
attr_accessible :user
An even better way to do this however would be to do
#user.pushup_reminders.create
That way the user_id is automatically assigned.
Use nested routes like this:
:resources :users do
:resources :pushup_reminders
end
This will give you params[:user_id] & params[:id] so you can find your objects in the db.
If you know your user via sessions, you won't need to nest your routes and can use that to save things instead.
Using restful routes, I would recommend using the create action in the pushup_reminders controller. This would be the most conventional and Restful way to do this kind of object creation.
def create
#user = User.find(params[:user_id]
#reminder = #user.pushup_reminders.create()
end
If you need to check whether object creation was successful, try using .new and .save

Is it better practice to have one URL for each bulk update or many URLs?

I have a user maintenance page. This page has a list of users where the admin can do bulk updates on the users he selects. Bulk updates include: activate, deactivate, and update roles to.
Should there be one URL I POST to, such as /users/bulk_update.json, where I then pass in the list of IDs and the method type. And in my bulk_update action, I update the IDs according to the method.
Or should there be a URL for each method, such /users/bulk_update_activate, /users/bulk_update_deactivate, and /users/bulk_update_roles?
The quick answers is: it depends! :)
If your updates type share many of the code logic.
1) Use filter:
class FirstController < ApplicationController
# Other controller code
before_filter :prepare_update
after_filter :finalize_update
def bulk_update_activate
# Do something here
end
def bulk_update_deactivate
# Do something here
end
end
2) Use a single action:
class SecondController < ApplicationController
# Other controller code
def bulk_update
case params[:operation]
when :activate then
# Do something here
when :deactivate then
# Do something here
end
end
end
If your updates are completely indipendent, then you should write different actions.
In my projects I usually find myself in using the first approach.
Hope it will be useful.

What is the best way of accessing routes in ActiveRecord models and observers

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.

Resources