I have a system where users come in to go through an application process that has multiple parts - sometimes users will save their progress and come back later.
I want to send users an e-mail if they haven't come back in 48 hours - would it be best to do this using cron, delayed_job, or whenever?
I've noticed that whenever I run operations in the console (such as bundle install or rake db:migrate) it runs cron as well, which makes me suspicious that we may have instances where users get multiple reminders in the same day.
What are your recommendations for this?
First of all, Whenever and Cron are synonymous. All Whenever does is provide a way for you to write cronjobs using Ruby (which is awesome, I love Whenever).
Delayed_job is not the answer here. You definitely want to use cronjobs. Create a method on your Application model that will get applications which have an updated_at value of < 2.days.ago and e-mail its applicant.
def notify_stale_applicants
#stale_applications = Application.where('updated_at < ?', 2.days.ago) # or 48.hours.ago
#stale_applications.each do |app|
UserMailer.notify_is_stale(app).deliver
end
end
And your UserMailer:
def notify_is_stale(application)
#application = application
mail(:to => application.user.email, :from => "Application Status <status#yourdomain.com>", :subject => "You haven't finished your Application!"
end
Using whenever to create this cron:
every :day, :at => '8am' do
runner 'Application.notify_stale_applicants'
end
Related
I want to trigger mail to be sent one hour before an appointment comes up. I am using the at field from the #appointment instance variable.
class AppointmentController < ApplicationController
def create
if DateTime.now + 1.hour > #appointment.at
AppointmentReminder.send_appointment_email(#appointment).deliver_now
end
end
end
This works if the appointment was created within an hour, but if the appointment was created in the future... then our poor customer won't be notified. Is there a mechanism where Rails can automatically deliver the email at the right time? I don't want to use a cronjob or rake task.
I'd recommend looking at background processing systems like Sidekiq or Sucker Punch which can be configured to perform jobs "later".
This way when the appointment is created you can schedule the job to execute at the correct time. You'll need to add checks to make sure when the job finally runs that it's still legitimate, etc.
http://sidekiq.org
https://github.com/brandonhilkert/sucker_punch
As you tagged your question as related to rails 4.2 then Active Job exactly what you need.
You could use whenever to run a block of code on a schedule. Say, ever 5 minutes, looks for appointments that are starting within the next hour and send an email.
To prevent multiple servers from sending an email, you could have a status on the appointment to keep track of if the email has been sent.
Then, using postgres, you can use this SQL to grab records to send and use the database to decide which server is going to send out a specific email:
Email.find_by_sql("WITH updated AS (
UPDATE emails SET status = 'processing' where lower(status) = 'new' RETURNING id
)
SELECT *
FROM emails
WHERE id IN (SELECT id FROM updated)
order by id asc
")
I will share how I have done it. It works just fine.
First, install whenever gem.
You should have your mailer. Here is mine:
class WeeklyDigestMailer < ApplicationMailer
default :from => "bla#bla.org"
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
# en.weekly_digest_mailer.weekly_promos.subject
#
helper ApplicationHelper
def weekly_promos(suscriptor, promos)
#promos = promos
mail(:to => "<#{suscriptor.email}>", :subject => "Mercadillo digital semanal")
end
end
Of course, you need to style your view.
Then, you create a rake task (in lib/tasks). Just like this:
desc 'send digest email'
task send_weekly_email: :environment do
#promociones = Promo.where("validez > ?", Time.zone.now).order("created_at DESC")
if (#promociones.count > 0)
#suscriptors = Suscriptor.where(email_confirmation: true)
#suscriptors.each do |suscriptor|
WeeklyDigestMailer.weekly_promos(suscriptor, #promociones).deliver_now
end
end
end
Finally, you configure your schedule with whenever gem. As I want to send the mails all thrusdays at 9 am, I just put it:
every :thursday, at: '9:00 am' do # Use any day of the week or :weekend, :weekday
rake "send_weekly_email"
end
One important point: since you are using a rake task, use deliver_now instead of deliver_later because if the task finish before all emails have been sent, the rest will be undelivered.
That's all.
I need to run a background thread in the Ruby on Rails application that should send an emails when certain date has occurred, depending on the values in DB (and email body should contain some info from this DB).
What is the best way to achieve such behavior?
I'm using Ruby on Rails 4.1.4 btw.
Thanks in advance.
You would be better off using a framework like Sidekiq or Resque than doing it yourself.
With Sidekiq, you can use the Sidekiq Pro or various third-party projects to schedule jobs. See Recurring jobs on Sidekiq's wiki for projects that provide scheduling capability.
You can use whenever gem to perform background jobs according to your requirement.
Check out the github docs here. Whenever
Ryan Bates created a great Railscast about Whenever: Cron jobs Intro in rails
In config/schedule.rb
every 2.hours do
runner User.send_email_to_users
end
In User model
def self.send_email_to_users
//write your logic here and call to action to send mails.
UserMailer.send_mail_persons(user).deliver
//pass any other data if required in arguments.
end
In app/mailers/user_mailer.rb
def send_mail_persons(user)
#user = user
mail :to => #user.email, :subject => "Amusement.", :from => MAIL_ADDRESS
end
Create a html template as per requirement, app/views/user_mailer/send_mail_persons.html.erb
lib/tasks/mail_task.rake
namespace :mail_task do
desc "Send Mail"
task :send_mail => :environment do
...
end
end
Command line
rake mail_task:send_mail
I have a "Status" page. On which I am displaying the status of my local machines such as current upload/download speed, is recording going or not.
For getting above information I am ssh into that machine. Here is my sample code for it
Net::SSH.start('localhost','ubuntu', :password => 'ubuntu') do |session|
upload_speed = session.exec!("speedtest | grep Upload:").chomp.strip
return upload_speed
end
But it is taking time (about 3-4 minutes) for fetching those status. And it returns me "Connection time out error". So I am trying to add this process in the background. For this I am using delayed_job gem
Here is my code for it
My controller method
def unit_additional_status
#machine = MachineInfo.find(params[:unit_id])
stat = Delayed::Job.enqueue(LongerTask.new(#machine), 3, :run_at => 1.seconds.from_now)
end
Here is my longer_task.rb file
require 'rubygems'
require 'net/ssh'
class LongerTask < Struct.new(:machine)
def perform
port = #machine.port
#status = Hash.new
Net::SSH.start('localhost','ubuntu', :password => 'ubuntu', :port => port) do |session|
upload_speed = session.exec!("speedtest | grep Upload:").chomp.strip
status["upload_speed"].push(upload_speed)
end
#status
end
end
After execution I have to pass this #status to my controller action so that I can pass it to my status.html.erb view.
So I have a question how can I pass it to my controller method or how can get the output of execution of delayed job.
Also, if any one have better solution then let me know.
I am using rails 3.2.14 and ruby 1.8.7
You need to create some kind of additional status model, e.g. Job (status:string, message:string). Then you pass an instance of this model to your delayed job task when it is scheduled. When the task starts executing, you set the status to 'running'. When it finishes you update the message field with the desired result information and set status to 'finished'. This has several benefits like you have a good overview of your job queue and it can be extended to reflect execution time, errors etc.
To display the machine status in your example, you simply select the latest Job with status='finished' and show its timestamp and message.
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
I read the documentation on workers and delayed_job and couldn't follow exactly, so wanted to get head-start with some strategy and sample code.
I have a controller which I use to send emails one by one. Now each day I want to check which emails need to be sent for the day, and then send them through heroku as a delayed_job.
How do I begin to approach this? thanks.
This is what I'm coming up with based on the answers:
Using the 'whenever' gem, I created the following schedule.rb
every 1.day, :at => '4:30 am' do
heroku = Heroku::Client.new(ENV['HEROKU_USER'], ENV['HEROKU_PASS'])
heroku.set_workers(ENV['HEROKU_APP'], 1)
Contact.all.each do |contact|
contact_email = contact.email_today
unless contact.email_today == "none"
puts contact.first_name
puts contact_email.days
puts contact.date_entered
Delayed::Job.enqueue OutboundMailer.deliver_campaign_email(contact,contact_email)
end
end
heroku.set_workers(ENV['HEROKU_APP'], 0)
end
To determine whether I should send an email today or not, I created the method for contact.rb:
def email_today
next_event_info = self.next_event_info # invokes method for contact
next_event = next_event_info[:event]
delay = next_event_info[:delay]
if next_event.class.name == "Email" && from_today(self, next_event.days) + delay < 0 #helper from_today
return next_event
else
return "none"
end
end
Does this look right? I am developing on windows and deploying to heroku so don't know how to test it...thanks!
If you're sending emails out once a day, you probably want to start by add the cron addon to your application, which will fire a rake task once-per day.
Obviously, you'll also need to add the delayed_job plugin (http://docs.heroku.com/delayed-job). In addition, your app will need to be running at least one worker.
Then it's just a matter of doing your mail work from within your cron rake task. For example, if you had a mailer called 'UserMailer', your cron could look something like this:
#lib/cron.rb
task :cron => :environment do
User.all.each do |user|
Delayed::Job.enqueue UserMailer.deliver_notification(user)
end
end
If you're only using background tasks to send these emails, you could probably add also some logic in your cron task, and your mailer methods to add and remove workers as required, which will save you having to pay for the workers while they're not in use.