Rails: invoking a mailer from a rake task with parameter/env variable - ruby-on-rails

Using this railscast http://railscasts.com/episodes/127-rake-in-background?autoplay=true as an example/inspiration (i.e. I'm not trying to implement the rails cast code, just use it as an inspiration), I tried to move a mailer, that was triggered by an after_create callback in the user.rb model, into a rake task, so it would run in the background. The mailer was working before I moved it into a rake task, but it's not working anymore.
Instead of calling the mailer from the User.rb model, which it was how it was set up originally (see the commented out code in user.rb), I instead call the rake task, which then invokes the UserMailer.welcome_email method.
In the original code, "self" was submitted (from User.rb) as a parameter to the method welcome_email(user) in user_mailer.rb. In my attempt to turn it into a rake task, I assigned "self" to USER_INSTANCE, which is supposed to be picked up in the mailer.rake as ENV["USER_INSTANCE"]. This was also suggested by the railscast.
Somewhere along the way it's not working. Any ideas?
User.rb
after_create :send_welcome_email
def send_welcome_email
system "rake :send_mailing USER_INSTANCE=self &" #note the & forks the task
#UserMailer.welcome_email(self).deliver <-- how it was originally.
end
mailer.rake
desc "Send mailing"
task :send_mailing => :environment do
UserMailer.welcome_email(ENV["USER_INSTANCE"]).deliver #moved from user.rb to here but now with environment variable instead of parameter
end
unchanged User_mailer.rb
class UserMailer < ActionMailer::Base
default :from => "blahblah#gmail.com"
def welcome_email(user)
mail(:to => user.email, :subject => "Invitation Request Received")
end
end

currently you are doing this
system "rake :send_mailing USER_INSTANCE=self &"
which is the same as going to the command line and typing
rake :send_mailing USER_INSTANCE=self &
self is just a literal string, I think what you are trying to do is this
system "rake :send_mailing USER_INSTANCE=#{self} &"
but that will end up being the equivalent of running this on the command line
rake :send_mailing USER_INSTANCE=<User::xxxxx>
rake won't serialize this into your User ActiveRecord object;
when you shell out with system there is no relation to the calling code
an alternative - your rake task could take an integer - user_id and then access the record via User.find
but it gets more complicated as after_create is going to be running in a transaction so once your rake task runs it may or may not have finished that transaction
I would advise against trying to re-invent a way to do background processing in rails, there are already good tried and true solutions available
see http://railscasts.com/?tag_id=32 for some options

Related

Uninitialized constant error when trying to refer to a database record within a service

I'm trying to create a rake task that uses a service. Within that service, I want to load the last saved record of a MonthlyMetrics table within my database.
Within my rake file:
require 'metrics_service'
namespace :metrics do
#metrics_service = MetricsService.new
task :calculate_metrics => [:check_for_new_month, :update_customers, :update_churn, :do_more_stuff] do
puts "Donezo!"
end
# ...more cool tasks
end
And my MetricsService within lib/metrics_service.rb:
class MetricsService
def initialize
#metrics = MonthlyMetric.last
#total_customer_count = total_customers.count
assign_product_values
end
# Methods to do all my cool things...
end
Whenever I try to run something like rake:db:migrate, I get the following error:
NameError: uninitialized constant MetricsService::MonthlyMetric
I'm not sure why it's trying to refer to MonthlyMetric the way it is... As a class within the MetricsService namespace..? It's not like I'm trying to define MonthlyMetric as a nested class within MetricsService... I'm just trying to refer to it as an ActiveRecord query.
I've done other ActiveRecord queries, for example User, in other services within the same directory.
What am I doing wrong here?
I think if you just add => :environment to the end of your rake task, that may fix the problem.
As in:
task :calculate_metrics => [:check_for_new_month, :update_customers, :update_churn, :do_more_stuff] => :environment do
I've run into similar problems where Rails does not initialize the correct environment without this tacked on to each rake task.

ActiveRecord::Dirty and Rake

I have a Model (let's call it A) in a Rails project that checks an attribute (let's call it a) with the ActiveRecord::Dirty a_changed? function on before_save. I want to be able to save an instance of A in a Rake task, but simply including :environment isn't cutting it--I'm getting a "no method a_changed? defined on A" message in the Rake task. How do I get ActiveRecord to remember about ActiveRecord::Dirty within a Rake task?
Rails version is 2.3.11
namespace :some_namespace do
namespace :some_subnamespace do
desc "This is a Rake Task"
task :some_taskname, [:some_arg] => [:environment] do |t,arg|
foo = A.find(11111)
foo.save #<=== fails with "no method a_changed? defined on A"
end
end
end
Since that's a pretty dense bunch of info, here's the breakdown:
I have a model A with an attribute a.
Model A has a before_save trigger defined that calls a_changed?, which is a method added by ActiveRecord::Dirty in the Rails environment. There are no problems calling this from a controller.
In my Rake task, however, the a_changed? call in the before_save trigger causes a NoMethodError exception to be raised, presumably because the [:environment] requirement is not sufficient to include ActiveRecord::Dirty. My question is how to make this not happen (my workaround is to rescue NoMethodError from inside the before_save, which is an obvious hack).
Looks like your question has already been answered on a previous question asked on StackOverflow.
In order to determine what methods your object has you can do this:
...
desc "This is a Rake Task"
task :some_taskname, [:some_arg] => :environment do |t, args|
foo = A.find(11111)
p foo.methods
...
This will print out a list of the available methods. If the array includes :some_attr_changed? (where some_attr is an attribute), then you can be certain that ActiveRecord::Dirty is indeed working fine in the rake task. If those methods don't show up in the array, then your assumptions are correct.

Sending email through resque: object treated as hash

I'm sending emails out through resque. All emails send properly except this one, which sends fine in development locally but fails on staging server.
It seems to view the 'Admin' object as a hash instead of treating it as an admin object. Any ideas?
account.rb
class Account < ActiveRecord::Base
after_commit :send_welcome_email
def send_welcome_email
SubscriptionNotifier.welcome(self).deliver
end
end
subscription_notifier.rb
class SubscriptionNotifier < ActionMailer::Base
def welcome(account)
#subscriber = account
mail(:to => account.admin.email, :subject => "Welcome!")
end
end
Resque error
SubscriptionNotifier Arguments
"welcome"
{"account"=>{"address_line1"=>nil, "address_line2"=>nil, "city"=>nil, "created_at"=>"2012-02-08T10:56:22-08:00", "currency"=>"United States Dollar (USD)", "deleted_at"=>nil, "description"=>nil, "email"=>"test2#test.com", "full_domain"=>"www.test.net", "id"=>3, "initial_plan"=>nil, "latitude"=>nil, "longitude"=>nil, "name"=>"macs", "phone"=>nil, "setup_steps_complete"=>0, "state"=>nil, "time_zone"=>"Pacific Time (US & Canada)", "updated_at"=>"2012-02-08T10:56:22-08:00", "website"=>nil, "zip"=>nil}}
Exception
NoMethodError
Error
undefined method `admin' for #<Hash:0x0000000585aa70>
I think you should just pass in the account ID into the queue and have the worker fetch the Account object when it does its perform method. That should lessen your Hash woes.
This is an old question, but still relevant. The answers here give workarounds, but don't describe why the problem arises or how to design jobs to avoid it.
Basically for Resque to persist jobs in Redis, the arguments need to be serialized so they can be saved. You can't save a Ruby object in a database (for example), so the arguments are serialized to JSON (which can be persisted). In your case, it's calling account.to_json and stores that as an argument to your job.
The likely reason this is an issue in staging but not in development, is your development Redis is only storing the jobs in memory (and therefore they don't need to be serialized). Staging is persisting them to disk or a database for example, so they have to be serialized.
To avoid this problem, arguments to jobs should be strings, numbers, or data structures that can be converted to json.
You will need to load the environment by running 'rake environment resque:work QUEUE='*' RAILS_ENV=staging'
that is unless you have
task "resque:setup" => :environment
defined in a resque.rake file.
Try this:
in account.rb
def send_welcome_email
SubscriptionNotifier.welcome(self.admin.email).deliver
end
in subscription_notifier.rb
def welcome(account_admin_email)
#subscriber = account
mail(:to => account_admin_email, :subject => "Welcome!")
end
I had the same error, apparently you are passing account as a hash and it has no email admin method. So just get the email in the send_welcome_email method and pass it as a parameter, instead to passing a hash and trying to access the email in the welcome method.
NOTE: for the #subscriber, you would need to pass the parameters, just like the email, you use in the email template for example self.admin.name in the model and #name = account_admin_name in the welcome method
Hope this helps.

Delayed_job not sending Rails 3 emails

Note: Using Rails 3.1 and current delayed_job gem.
I have a User model that calls after_create :mail_confirmation.
The mail_confirmation method looks like the following, per the delayed_job instructions:
def mail_confirmation
UserMailer.delay.registration_confirmation(self)
end
The UserMailer is:
class UserMailer < ActionMailer::Base
default from: "test#mysite.com"
def registration_confirmation(user)
#user = user
mail(:to => "#{user.full_name} <#{user.email}>", :subject => "Test registration email")
end
end
The job is queued, and the output from rake jobs:work makes it seem as if it completed successfully:
[Worker(host:mymac.local pid:73694)] Starting job worker
[Worker(host:mymac.local pid:73694)] Class#registration_confirmation completed after 1.3659
[Worker(host:mymac.local pid:73694)] 1 jobs processed at 0.7288 j/s, 0 failed ...
There is no error but the email is never sent. It works fine if I remove delayed from the method call in the User model and go with the standard deliver method:
def mail_confirmation
UserMailer.registration_confirmation(self).deliver
end
How can I find out what is happening when the job is processed? Any idea how to fix it?
Update It appears that it is related to this:
NoMethodError with delayed_job (collectiveidea gem)
Yeah i had this same issue. #Clay is correct, there is an issue at the moment: https://github.com/collectiveidea/delayed_job/issues/323
I resolved this problem by reverting back to the previous version of delayed_job.
gem 'delayed_job', '2.1.2'
I'm having the same issues here. I discovered that for some reason the delay method called on Mailer classes is being handled by the method Delayed::MessageSending#delay instead of Delayed::DelayMail#delay which instantiates the right performable (which is PerformableMailer instead of PerformableMethod). It doesn't crash the job because PerformableMethod just calls the method without the deliver.
Take a look at:
delayted_job/lib/delayed/message_sending.rb
delayted_job/lib/delayed/performable_mailer.rb

Options with Rails for triggering an email to all (or some) users?

Newb question: We've got a live site with registered users. Some new functionality has been added (including support for Mailers). We would like to trigger an email to all existing users (similar but not identical to one that will now automatically go out to new users).
What options do we have for triggering the sending of that email? This message will likely only be sent once so we don't need the code (other than the message itself) in the Rails app. Nor do we really need to store who received it because it will be assumed that all users have received such a message once we can get this one out.
I'm thinking Rake task but all the examples I seem to be able to find are for build script?!? Or should we just use the Rails console in production? Perhaps get an array of all users we want to send email to and then deliver message to them?
Not sure. I haven't worked with ActionMailer much.
I'd probably do it like this:
In order to determine if the system has sent an email to a user, you should add an attribute let's say 'sent_email' which is basically just a boolean.
I'd create a cron job for a rake task that checks all users with sent_email=0. Then, I'll loop through each array and send the email and set sent_email=1. The cron job can be run daily, depending on your preference. You can use whenever gem to setup the cron job.
schedule.rb (whenever stuff)
job_type :run_script, "RAILS_ENV=:environment ruby :path/script/:task"
every 1.day do
run_script('send_email.rb')
end
script/send_email.rb
#!/usr/bin/env ruby
puts "Loading rails environment..."
require(File.dirname(__FILE__) + "/../config/environment") unless defined?(Rails)
class SendEmail
def send_email
users = User.send_email_to
users.each do |user|
OneTimeMailer.deliver_one_time_email(user)
end
end
end
mailers/one_time_mailer.rb
class OneTimeMailer < ActionMailer::Base
def one_time_email(user)
recipients user.email
from 'your system'
subject 'hello world'
body 'this is a one time email. thank you'
end
end
I hope this helps.
I suggest doing a rake task, run it once and you are done.
rails g task my_namespace my_task1 my_task2
Now you loop through your database:
namespace :sent_email_to_everyone do
desc "TODO"
task send_that_mail: :environment do
user = User.all
user.each do |user|
Yourmailer.send_email(user.email)
end
end
end
end
Now you just run it and done
rake sent_that_mail

Resources