I have what I think is a working setup to send emails via Delayed_Job. However, I haven't received my test email and it isn't practical to wait for more to happen with a delay of days. I need to figure out:
What's wrong that's causing the email not to send.
How to test it without waiting days at a time.
I'm new to Delayed_Job, so pardon the newbie mistakes.
Here's the model that includes the send_reminder_emails method. They were fully functional without the .delay(run_at: self.mail_date) bit, so at least I know that much works:
class Reminder < ActiveRecord::Base
belongs_to :user
before_save :create_mail_date
after_save :send_reminder_emails
extend FriendlyId
friendly_id :name, use: [:slugged, :finders]
def create_mail_date
#schedule = IceCube::Schedule.new(self.date)
case self.repeating
when "Weekly"
#schedule.add_recurrence_rule(
IceCube::Rule.weekly
)
when "Monthly"
#schedule.add_recurrence_rule(
IceCube::Rule.monthly.day_of_month(self.date.mon)
)
when "Yearly"
#schedule.add_recurrence_rule(
IceCube::Rule.yearly.day_of_year(self.date.yday)
)
end
if self.repeating
self.date = #schedule.next_occurrence(Time.now)
end
self.mail_date = self.date - 7.days
end
private
def send_reminder_emails
if self.reminder
ReminderMailer.delay(run_at: self.mail_date).reminder_send(self.user, self).deliver_now
self.create_mail_date
end
end
handle_asynchronously :send_reminder_emails
end
The references to schedule are via the Ice_Cube gem and all of the date stuff has been tested via my console and is working. Here is my reminder_mailer.rb:
class ReminderMailer < ApplicationMailer
default from: "man#manlyartofbbq.com"
def reminder_send(user, reminder)
#user = user
#reminder = reminder
mail(to: user.email, subject: "Reminder! #{reminder.name} is fast approaching!")
end
end
I installed Delayed_Job step by step from their readme for Rails 4. Any help getting the delayed part of this mailer ironed out is appreciated!
You can use Active Job for sending mails by scheduling the job at a particular time.
Active Job is a framework for declaring jobs and making them run on a variety of queuing backends. These jobs can be everything from regularly scheduled clean-ups, to billing charges, to mailings. Anything that can be chopped up into small units of work and run in parallel, really.
For enqueuing and executing jobs in production you need to set up a queuing backend, that is to say you need to decide for a 3rd-party queuing library that Rails should use. Rails itself only provides an in-process queuing system, which only keeps the jobs in RAM. If the process crashes or the machine is reset, then all outstanding jobs are lost with the default async back-end. This may be fine for smaller apps or non-critical jobs, but most production apps will need to pick a persistent backend.
Active Job has built-in adapters for multiple queuing backends (Sidekiq, Resque, Delayed Job and others). To get an up-to-date list of the adapters see the API Documentation for ActiveJob::QueueAdapters.
Once after configuring the queuing adapters, then create Job for sending mails
class RemainderMailerJob < ApplicationJob
queue_as :default
def perform(user, remainder)
ReminderMailer.reminder_send(user, reminder).deliver
end
end
For sending the mail on a specific time you need to enqueue the job and make it perform at a specific time. If you need to send mail 7 days from now then call this piece of code where ever you need.
RemainderMailerJob.set(wait: 1.week).perform_later(user, remainder)
Now this Job will execute after 1 week, which in turn calls the mailer at that time, so your mails will be sent on the specific day.
IF you knew the specific Date Time then you can use
RemainderMailerJob.set(wait_untill: <Specific date>).perform_later(user, remainder)
If you have any doubts regarding the active job kindly refer Active Job Basics
When you send mails using delayed job, there is no need to add 'deliver' or 'deliver_now' when initializing the delayed_job for mailer. So
ReminderMailer.delay(run_at: self.mail_date).reminder_send(self.user, self)
will itself work.
Convert your 'self.mail_date' to also include the time at which you want to send the mail. Passing only date will not work. Basically the run_at should specify both date and time.
Please check the 'delayed_job' github link under the heading "Rails 3 Mailers"
https://github.com/collectiveidea/delayed_job
I have seen this happen when attribute protection is misconfigured. (Usually on legacy apps that left some kludge in when upgrading rails version.)
See if you're able to set the field from the console: Delayed::Job.new(run_at: 1.year.from_now). If not, that's the culprit and you'll need to track down the cause in your app.
If it works, then we still need to verify whether this is an app problem or a DJ bug. Make a new minimal rails app with just a delayed_job table and try to reproduce this. If you can't then there's something else odd in your app.
Related
I have a use case where user schedules a 'command' from the web interface. The user also specifies the date and time the command needs to be triggred.
This is sequence of steps:
1.User schedules a command 'Restart Device' at May 31, 3pm.
2.This is saved in a database table called Command.
3.Now there needs to be a background job that needs to be triggered at this specified time to do something (make an api call, send email etc.)
4.Once job is executed, It is removed or marked done, until a new command is issued.
There could be multpile users concurrently performing the above sequence of steps.
Is delayed_job a good choice for above? I couldnt find an example as how to implement above using delayed job.
EDIT: the reason I was looking at delayed_job is because eventually I would need to leverage existing relational database
I would advise to use Sidekiq. With it you can use scheduled jobs to tell sidekiq when to perform the jobs.
Example :
MyWorker.perform_at(3.hours.from_now, 'mike', 1)
EDIT : worker example
#app/workers/restart_device_worker.rb
class RestartDeviceWorker
include Sidekiq::Worker
def perform(params)
# Do the job
# ...
# update in DB
end
end
see doc: https://blog.codeship.com/how-to-use-rails-active-job/
https://guides.rubyonrails.org/active_job_basics.html
If you are using Rails 5 then you have best option of ActiveJob(inbuilt feature)
Use ActiveJob
"Active Job – Make work happen later. Active Job is a framework for declaring jobs and making them run on a variety of queuing backends. These jobs can be everything from regularly scheduled clean-ups, to billing charges, to mailings. Anything that can be chopped up into small units of work and run in parallel, really."
Active Job has built-in adapters for multiple queuing backends (Sidekiq, Resque, Delayed Job and others). You just need to tell them.
Scenario: I want to delete my story after 24 hours(1 day). Then we do create a job named "StoriesCleanupJob". Call this job at the time of the creation of story like below
StoriesCleanupJob.set(wait: 1.day).perform_later(story)
It will call the Job after 1 day.
class StoriesCleanupJob < ApplicationJob
queue_as :default
def perform(story)
if story.destroy
#put your own conditions like update the status and all, whatever you want to perform.
end
end
end
So I've been looking for the simplest way to send an e-mail when X column of Payments table in the database is == 'condition'. Basically what I want is to add a payment and set a date like 6 months. When 6 months have passed I want to send the mail. I've seen many solutions like using Whenever cron jobs and others but I want to know the absolute simplest way (perhaps using Rails only without relying on outside source) to keep my application light and clean. I was thinking I could use the auto generated created_at to evaluate when x time has passed.
Since you have a column in your db for the time to send email, make it a datetime datatype and you can set the email date as soon as the event payment event is created. Then, you can have a rake task where,
range = Time.now.beginning_of_day..Time.now.end_of_day
Payment.where(your_datetime_custom_column: range).each do |payment|
payment.user.send_email
end
and you can run this task everyday from the scheduler.
The "easiest" way is to use Active Job in conjunction with a state machine:
EmailJob.set(wait: 6.months).perform_later(user.id) if user.X_changed?
The problem with this is that the queue will accumulate jobs since jobs don't get handled right away. This may lead to other performance issues since there are now more jobs to scan and they're taking up more memory.
Cron jobs are well suited for this kind of thing. Depending on your hosting platform, there may be various other ways to handle this; for example, Heroku has Heroku Scheduler.
There are likely other ways to schedule repeating tasks without cron, such as this SO answer.
edit: I did use a gem once called 'fist_of_fury', but it's not currently maintained and I'm not sure how it would perform in a production environment. Below are some snippets for how I used it in a rails project:
in Gemfile
gem 'fist_of_fury'
in config/initializers/fist_of_fury.rb
# Ensure the jobs run only in a web server.
if defined?(Rails::Server)
FistOfFury.attack! do
ObserveAllJob.recurs { minutely(1) }
end
end
in app/jobs/observe_all_job.rb
class ObserveAllJob
include SuckerPunch::Job
include FistOfFury::Recurrent
def perform
::Task.all.each(&:observe)
end
end
I have a rake task send_emails which send e-mails to lot of people. I call this rake task from controller as mentioned in Rake in Background railscast. But I want to schedule this rake task to run at a particular date and time, which is not same for everyday (it's not a cron job). The date and time are set dynamically from a form.
For the above implemented rake task for sending emails, I want to show the status of the mailing process to the end-user. For instance, say there is a response object in the rake task which I can use as response.status,response.delivered?,response.address, etc. How can I access this object ( or any variable) in the rake file in my controller?
I don't want to use delayed_job but want to implement it's functionality of run_at and in_the_future. Also the whenever gem won't be able to solve my first problem coz I won't be able to pass date and time to it's scheduler.
First thing, calling rake task from controller is a bad practice. Ryan published that video at 2008 since that many better solution have came up. You shouldn't ignore it.
I suggest you to use delayed_job, it serves your needs in a great way. Since, if you want to invoke task dynamically, there should be some checker which will continuously check the desire field every second. Delayed job keep checking its database every time, you can use that.
Anyway,You can use something like this
def self.run_when
Scheduler.all.each do |s|
if d.dynamically_assigned_field < 1.second.ago
d.run_my_job!
d.status = "finished"
d.save
end
end
end
And, in model you can do something like this
def run_my_job!
self.status = "processing"
self.save
long_running_task
end
One thing also you should keep in mind that if too many workers/batch/cron job starts at run at same it will fight for resources and may enter into deadlock state. As per your server capacity, you should limit the running jobs.
Sidekiq is also a good option you can consider. Personally, i like sidekiq because it doesn't hit my database everytime , scales very effectively. It uses redis but it is expensive.
I would create new model for mail job, like this:
app/models/mail_job.rb
class MailJob
attr_accessible :email, :send_at, :delivered
scope :should_deliver, -> { where(delivered: false).where('send_at <= ?', Time.now) }
def should_deliver?
!delivered? && send_at <= Time.now
end
...
end
And use Sidekiq + Sidetiq, running every minute (or any other interval) and checking for mail jobs that should be delivered.
Hope this helps!
I have a rails application where I want to run a job in the background, but I need to run the job 2 hours from the original event.
The use case might be something like this:
User posts a product listing.
Background job is queued to syndicate listing to 3rd party api's, but even after original request, the response could take a while and the 3rd party's solution is to poll them every 2 hours to see if we can get a success acknowledgement.
So is there a way to queue a job, so that a worker daemon knows to ignore it or only listen to it at the scheduled time?
I don't want to use cron because it will load up a whole application stack and may be executed twice on overlapping long running jobs.
Can a priority queue be used for this? What solutions are there to implement this?
try delayed job - https://github.com/collectiveidea/delayed_job
something along these lines?
class ProductCheckSyndicateResponseJob < Struct.new(:product_id)
def perform
product = Product.find(product_id)
if product.still_needs_syndicate_response
# do it ...
# still no response, check again in two hours
Delayed::Job.enqueue(ProductCheckSyndicateResponseJob.new(product.id), :run_at => 2.hours.from_now)
else
# nothing to do ...
end
end
end
initialize job first time in controller or maybe before_create callback on model?
Delayed::Job.enqueue(ProductCheckSyndicateResponseJob.new(#product.id), :run_at => 2.hours.from_now)
Use the Rufus Scheduler gem. It runs as a background thread, so you don't have to load the entire application stack again. Add it to your Gemfile, and then your code is as simple as:
# in an initializer,
SCHEDULER = Rufus::Scheduler.start_new
# then wherever you want in your Rails app,
SCHEDULER.in('2h') do
# whatever code you want to run in 2 hours
end
The github page has tons of more examples.
I have been running delayed_job and was hitting some errors, but now I don't know what is sitting in the job queue or what's going on with them....
How can I figure that out so I can debug whether it is able to execute what has been put in the queue?
Here is where I call the job (it is part of a cron task) and the mailer it calls:
class SomeMailJob < Struct.new(:contact, :contact_email)
def perform
OutboundMailer.deliver_campaign_email(contact,contact_email)
end
end
#class OutboundMailer < ActionMailer::Base
class OutboundMailer < Postage::Mailer
def campaign_email(contact,email)
subject email.subject
recipients contact.email
from 'Timothy Fong <tim.fong#opshub.com>'
sent_on Date.today
body :email => email
end
You can gain insight into your delayed_job queue through the Delayed::Job model (I think the name of it might have changed in later versions). It's just an ActiveRecord model and you can do all the things you'd do to a normal one. Find all, find ones with failed_at set, find ones with locked_by set (currently being worked).
I find they're much easier to read if you to_yaml them first: y Delayed::Job.all from console.
You have to be careful to set Delayed::Job.destroy_failed_jobs = false because by default after 25 tries a job will be deleted. Then you can see which jobs failed (and what time in the failed_at field). There's a rake task to clean them up.
Another queue to look at is Resque, which comes with a little management app to monitor queues. Its introduction is a good read.