I am trying to call a rake task to send an email to a list of subscribers from a controller action like so:
Controller action:
def send_digest
#article_ids = params[:article_ids]
#subject = params[:subject]
EmailDigest.send_email_digest("weekly_digest_task", #article_ids, #subject)
redirect_to new_digests_path
end
email_digest.rb:
require 'rake'
class EmailDigest < ActiveRecord::Base
def self.send_email_digest(weekly_digest_task, article_ids, subject)
load File.join(Rails.root, 'lib', 'tasks', 'send_email_digest.rake')
Rake::Task['weekly_digest_task'].invoke("\\\"#{article_ids}\\\"","\\\"#{subject}\\\"")
end
end
And here is the send_email_digest.rake:
task :weekly_digest_task, [:article_ids,:subject] => :environment do |task, args|
articles = args.article_ids
subject = args.subject
article_objects = []
articles.each do |m|
article_objects << Article.find_by_id(m)
end
EmailDigest.all.each do |subscriber|
DigestMailer.weekly_digest(subscriber, article_objects, subject).deliver
end
end
The redirect occurs and I am taken to that page with no errors, however no emails get sent. As such, I have no idea why this is not working.
Any ideas?
You are passing a string to your task, so unless you extract the array of ids back from the string, you are essentially enumerating over the string rather than the items. I would like to know what version of ruby you are using because in 1.9+ string is no longer an enumerable and so you should be getting an error.
However all this is not necessary because you can directly pass an array to invoke.
Your code is also unnecessarily convoluted. Especially it is not advisable to load file from within a method (as it will cause the file contents to be evaluated on each invocation of the method). It could easily be required at the top.
Ideally, it would be an better if you move the logic from rake task to a method in the model or a mediator and just call that from both the rake task as well as the mailer.
Related
I'm trying to add the function that is defined in the controller.rb file to the rake file. The Name of this controller file is "home_controller.rb". Here I put a part of the code.
class HomeController < ApplicationController
def rclass_creation
#output = "rclass is created"
a = Rclass.all
a.destroy_all...
I made my_namespace.rake file under tasks in the lib folder. Here I put the code a bit.
namespace :my_namespace do
desc "TODO"
task my_task2: :environment do
a = Userpool.all
a.each do |user|
puts user.name
end
puts "wow it is working"
I was able to call the database. I think this is because the rake file has an access to the database ( Userpool ). In this rake file, I want to call the function "rclass_creation" that was stated in the "home_controller.rb" because I want to avoid the situation that I have to do hardcopy.
The sudo code might look like this ( I hope (: )
namespace :my_namespace do
desc "TODO"
task my_task2: :environment do
status = rclass_creation <= from home_controller.rb
a = Userpool.all
a.each do |user|
puts user.name
end
puts "wow it is working"
The function I want to call is "rclass_creation" from the home_controller.
How can I call the function from the controller to rake file ? I'm looking forward to seeing opinions from the experts!!
You don't call controller methods from anywhere but the controller.
If you want to use a controller action from a rake file you should invoke it with a HTTP request. Just like any client would do.
You can initialize a controller and call methods on it but that's really hacky. Don't.
If you need to share code between your rake task and controller than it should not be in a controller at all.
So where do you put the code?
Does it act on a model? Is it independent from application state? Does it belong in a model? Put in a your model.
Is a mixin? Or just a plain method with takes some input and shoots something out? Put in into a helper or a plain old module.
Is it some kind of service that takes input and does something with models? Put it in a service object.
Using Rails 2.3.*
Say I have a method called some_method() in two rake files - A.rake and B.rake.
I'm finding that if I call some_method() in B.rake, the method in A.rake is what actually gets called.
So what's the best approach to defining helpers methods inside rake files that will be "local" to the rake task defined in that file?
Thanks
You can define your helper within a task to make it available to that task and all subsequent ones:
desc 'has access to local helper'
task :accessible do
def helper
return "the helper"
end
puts "I have access to #{helper}"
end
desc 'has access too'
task 'after-accessible' => ['accessible'] do
puts "this ran after 'accessible' but still has access to '#{helper}"
end
desc 'does not have access to the helper'
task :outside do
puts helper # fails if runs before :accessible
end
Perhaps the best thing to do though is to refactor your Rakefiles and the helper code so that the two Rakefiles do not load each other.
I have a delayed job that runs perfect against a public schema in postgresql.
Most of my operations however are against other schemas (one for each client)
To handle different schemas I've followed the instructions and put code to switch search path, in my before_filter (in application controller).
I've noticed. That the code in the before_filter gets called perfectly during typical operations, but not at all during delayed job.
I trimmed and trimmed out everything but the simplest thing I could think of, to show entrance.
class ApplicationController < ActionController::Base
protect_from_forgery
def write_to_log(text)
File.open('c:\temp.txt', 'ab') do |f|
f.write text + "\r\n"
f.close
end
end
before_filter :on_before_filter
def on_before_filter
write_to_log('hey dave');
return if(use_token() == false);
set_active_schema if(goto_log_in? == false);
end
The code in the worker class
def run_job(id)
upload = Upload.find(id)
upload.run_job();
end
handle_asynchronously :run_job, :priority => 10, :queue => 'public'
Quite standard stuff? Though the code in the job runs, the before_filter code doesn't get called.
So my question is. Did I do something wrong? Or more importantly, how can I do something right?
I'm not recommending this approach; I'm just answering your question by providing this code. Since you essentially want your code to run before any attempted call to the database, you can monkey patch ActiveRecord. Add the following code to config/initializers/active_record_monkey_patch.rb
class ActiveRecord::ConnectionAdapters::ConnectionPool
# create an alias for the old 'connection' method
alias_method :old_connection, :connection
# redefine the 'connection' method
def connection
# output something just to make sure the monkey patch is working
puts "*** custom connection method called ***"
# your custom code is here
write_to_log('hey dave');
return if(use_token() == false);
set_active_schema if(goto_log_in? == false);
# call the old 'connection' method
old_connection
end
end
You'll see your custom connection method getting called frequently now, and it will work without a controller. You can test it by opening up a rails console and performing any database query, and you should see the "custom connection method called" message displayed several times.
If you want to manipulate the ActiveRecord search path for Postgres and schemas you can use a full-featured gem like apartment: https://github.com/bradrobertson/apartment
You can switch to a new schema:
Apartment::Database.switch('database_name')
Regardless if you call this in an application controller request or a background job.
I need to do a delayed job to count fbLikes in Model but I have the error report of "undefined send_later() method". Is there any way to do delayed job to my fb_likes function in model?
==============================Latest===================================================
This is my latest code in my project. Things still the same, fb_likes does not display likes count.
[Company.rb]-MODEL
require "delayed_job"
require "count_job.rb"
class Company < ActiveRecord::Base
before_save :fb_likes
def fb_likes
Delayed::Job.enqueue(CountJob.new(self.fbId))
end
end
[config/lib/count_job.rb]
class CountJob<Struct.new(:fbId)
def perform
uri = URI("http://graph.facebook.com/#{fbId}")
data = Net::HTTP.get(uri)
self.fbLikes = JSON.parse(data)['likes']
end
end
[controller]
def create
#company = Company.new(params[:company])
if #company.save!
flash[:success] = "New company successfully registered."
----and other more code----
Library files are not required by default.
Rename the job file to count_job.rb. Using camelCase for filenames is insane and will burn you in unpredictable ways.
Create an initializer and add require 'count_job.rb'
One way is to create a separate worker that will get queued, the run to fetch the updated Model and call its fb_likes method on it, but the method will need to be public. Or take the logic into the worker itself.
I get the following error:
Delayed::Job SomeMailJob# (NoMethodError) "undefined method `subject' for #<YAML::Object:0x2b0a191f4c78>"
This comes from the following code which references the SombMailJob above:
class SomeMailJob < Struct.new(:contact, :contact_email)
def perform
OutboundMailer.deliver_campaign_email(contact,contact_email)
end
end
Here is the mailer:
class OutboundMailer < Postage::Mailer
def campaign_email(contact,email)
subject email.subject
recipients contact.email
from 'me.com>'
sent_on Date.today
body :email => email
end
This is the cron task that invokes the mailer:
Contact.all.each do |contact|
email = contact.email_today #email_today is a contact method returning email object if <= today
unless contact.email_today == "none" || email.nil?
puts "contact info inside cron job"
puts contact.first_name
puts email.days
puts contact.date_entered
puts contact.colleagues
puts "substituted subject:"
puts email.substituted_subject(contact,contact.colleagues)
# create the Contact Email object that gets created and sent
contact_email = ContactEmail.new
contact_email.contact_id = contact.id
contact_email.email_id = email.id
contact_email.subject = email.substituted_subject(contact,contact.colleagues)
puts contact_email.subject
contact_email.date_sent = Date.today
contact_email.date_created = Date.today
contact_email.body = email.substituted_message(contact, contact.colleagues)
contact_email.status = "sent"
#Delayed::Job.enqueue OutboundMailer.deliver_campaign_email(contact,contact_email)
Delayed::Job.enqueue SomeMailJob.new(contact,contact_email)
contact_email.save #now save the record
Question: why am I getting this error? I don't even know what the object is because it is coming up with the code, so I can't really drill-down further to debug.
This worked for me. Seems to be the same issue you're having.
Rails Delayed Job & Library Class
When you enqueue a delayed job, it serializes the things involved (the class, the name of hte method you're calling on it, the arguments) into YAML so it can pull them out later when running the job and work with them.
It looks like in your case the email argument is not getting deserialized properly from YAML before .subject is called on it.
I've found that delayed_job tends to have trouble serializing/deserializing anything that's not a simple stored ActiveRecord object or primitive type (integer, string). I always try to set things up so my Job objects only take record IDs (integers), then in the perform method I find for the objects there and work with them. This would definitely avoid the trouble you're seeing.