I'm new in Rails developing, and I have one question. There the following code:
def create
#order = current_user.orders.create!(order_params)
OrderMailer.send_order_info(#order).deliver
end
This code creates a new order, render json result and send e-mail about it. Mail sending takes some time, and I think I should do it in another thread or something similar. Please, give me advice how I can do it good. Thanks!
You should use delay the email sending. You could do it using Sidekiq, Delayed Job or Resque for example.
You will also be able to delay any other jobs with these gems.
You should look at the docs and see which one is the best for your use.
I personally use Sidekiq but Delayed Job is the easiest to install if you only want to use it for mailer.
We use Spawn for this: it's changed its name to "Spawnling" now
https://github.com/tra/spawnling
Very easy to use: (in the controller)
#user = User.create(params[:user])
spawn do
#user.do_some_slow_background_stuff
end
or, if you want to monitor whether the background process has finished yet (#spawn_id is the pid)
#user = User.create(params[:user])
spawner = spawn do
#user.do_some_slow_background_stuff
end
#spawn_id = spawner.handle
Related
I have made a rails app. Users can upload images. Once the images are saved into database. An algorithm being called to process those pictures.
For now, it was realized within a controller action like this:
def create
#post = current_user.posts.build(params)
if #post.save
flash[:success]="post created"
redirect_to root_url
image_names = []
#post.picture.each do |imgs|
image_names << imgs.url
end
my_algorithm(image_names)
else
render 'static_pages/home'
end
end
It works correctly. The problem is the page didn't show until the algorithm finishing. And the algorithm took long time. How to fix it. Or maybe call my_algorithm other places? Or delay_job?
I think you should use Active Job for that it will make your job background job
You should try the background jobs to perform that action in particular time.
You may use sidekiq gem to trigger the events in background.
Or You can refer the following URL to, do the active jobs,
http://edgeguides.rubyonrails.org/active_job_basics.html#enqueue-the-job
Refer the above url and configure the app and perform the operations in background.
I have additionally mention the one more link for scheduled jobs,
How to perform a background job now?
First of all, this should be done in a call back, here in the after_create callback of the Post model. And from the callback you can en queue one delayed job or other background job which will fire the algorithm. In front end you can show some messages like under processing in the place where you want to show the processed information.
Thanks
I have a typical ActionMailer with a method specifying an email delivery.
def some_email(user_id)
#user = User.find(user_id)
if #user.eligible_for_email?
mail(to: #user.email, from: "me#me.com", subject: "The Subject")
#user.email_sent = Date.today
#user.save
end
end
I want to delay the sending of this using Sidekiq so I use:
Mailer.delay_for(2.days).some_email(user.id)
The eligible_for_email method:
def eligible_for_email?
!unsubscribed? && email_sent.nil?
end
In the meantime, the user could have unsubscribed, which is why there is a method in the User model called eligible_for_email? which I can use to conditionally send the email - but obviously this condition needs to be tested just before the email is sent, not when the job is scheduled.
So the problem is that when I use Sidekiq to process this, the conditional logic doesn't seem to be run when the job is done.
Does Sidekiq work by executing the some_email method on runtime and then queuing the resulting email to be sent out two days later, thereby negating my conditional code?
Your understanding is 100% correct and that's exactly what you want to do.
I'd guess you aren't restarting Sidekiq to pick up your code changes. Sidekiq does not auto-reload changed code like Rails does.
I am using delayed_job gem to send email upon the destroy of a record.
comments_controller.rb
def reject_appeal
#comment = Comment.find(params[:id])
if #comment.destroy
#comment.rejection(#comment)
flash[:alert] = "You have succesfully rejected the appeal."
redirect_to post(#comment.post_id)
end
end
comments.rb
def rejection(comment)
RejectAppeal.delay.notify(comment)
end
Now if I remove the delay method and just have it as RejectAppeal.notify(comment), the email gets sent out perfectly fine. But with delay i don't know what happens. I don't see anything in the delayed job lob log. Although in development log i do notice that the entry gets stored in or at least it BEGINS the action.
Any help on this? I am using delay method on several other mailer functions in this same app and they all get sent out fine but i am unsure whats wrong with this
P.S I am using Rails 4.0.1
Is the delayed job getting serialized correctly? (Check the delayed_jobs table). Is there an error running the job? The error gets stored in the last_error column if so.
It's probably because you are destroying your comment and then passing it through to the delayed job so, when the delayed job runs and tries to load the comment to do something with it, the comment can no longer be found.
I want to automatically send out emails to a list of users from my Rails app.
The volume ranges from really a few users (5-10) to groups of users (50-70).
The maximum would be all users (currently 5000).
I have understood that sending emails can block the Rails app, so sending emails should be done with the help of a queueing system and a background job, e.g. by using DelayedJob, Resque or Sidekiq.
Unfortunately, having such a background job requires a worker process on the Heroku platform. And I want to avoid that due to the increased cost (at least for the beginning).
Is there any alternative approach that I could take? E.g., a second Rails app with just one worker process which only does the email handling (well I guess that would then also result in a paid worker process)?
Is it possible to send out mass emails without such a worker process via SendGrid, MailGun or any other service that integrates nicely with Heroku?
Props to #phoet for his link
We've got a similar idea working on Heroku which we thought would be free. Turns out they billed us for the scheduler hours... but here's what we did:
Resque
Resque is a queueing system which runs on Rails to queue items in Redis. It's totally recommended to use this on Heroku, and is very efficient & scalable
It works like this:
Send "data" (typically ID's) to resque queue
Resque sends ID's to Redis
The Resque rake job processes the Redis Queue
Perform your mailout when queue is processed (sending the email to Mandrill / SendGrid)
The reason for having a queue is as #apneadiving said - your controller will timeout AND (more importantly), your Rails app will lock up until the process has completed
There is a very good Railscast on Resque here:
Code
This is just basic code - can add more if you want:
#app/controllers/messages_controller.rb
def send_message
id = params[:id]
message = Message.find(id).broadcast!
flash[:notice] = "Broadcast Queued!"
redirect_to root_path
end
#app/models/message.rb
def broadcast!
self.subscribers.each do |subscriber|
Resque.enqueue(MailoutQueue, id, subscriber.id, queue.id)
end
end
#app/workers/mailout_queue.rb
class MailoutQueue
#queue = :mailer
def self.perform(message_id, recipient_id, queue_id)
MessageMailer.send_message(message_id, recipient_id).deliver
end
end
class MessageMailer < ActionMailer::Base
default from: '****************'
def send_message(message_id, subscriber_id)
#Declarations
#message = Message.find(message_id)
#subscriber = Subscriber.find(subscriber_id)
#Send
mail(:to => #subscriber.email, :subject => #message.title)
end
end
Take a look here - https://github.com/stephenb/sendgrid#delivering-to-multiple-recipients
This allows you to send multiple emails with only one SMTP call.
(This question is a follow-up to How do I handle long requests for a Rails App so other users are not delayed too much? )
A user submits an answer to my Rails app and it gets checked in the back-end for up to 10 seconds. This would cause delays for all other users, so I'm trying out the delayed_job gem to move the checking to a Worker process. The Worker code returns the results back to the controller. However, the controller doesn't realize it's supposed to wait patiently for the results, so it causes an error.
How do I get the controller to wait for the results and let the rest of the app handle simple requests meanwhile?
In Javascript, one would use callbacks to call the function instead of returning a value. Should I do the same thing in Ruby and call back the controller from the Worker?
Update:
Alternatively, how can I call a controller method from the Worker? Then I could just call the relevant actions when its done.
This is the relevant code:
Controller:
def submit
question = Question.find params[:question]
user_answer = params[:user_answer]
#result, #other_stuff = SubmitWorker.new.check(question, user_answer)
render_ajax
end
submit_worker.rb :
class SubmitWorker
def check
#lots of code...
end
handle_asynchronously :check
end
Using DJ to offload the work is absolutely fine and normal, but making the controller wait for the response rather defeats the point.
You can add some form of callback to the end of your check method so that when the job finishes your user can be notified.
You can find some discussion on performing notifications in this question: push-style notifications simliar to Facebook with Rails and jQuery
Alternatively you can have your browser periodically call a controller action that checks for the results of the job - the results would ideally be an ActiveRecord object. Again you can find discussion on periodic javascript in this question: Rails 3 equivalent for periodically_call_remote
I think what you are trying to do here is little contradicting, because you use delayed_job when do done want to interrupt the control flow (so your users don't want to want until the request completes).
But if you want your controller to want until you get the results, then you don't want to use background processes like delayed_job.
You might want to think of different way of notifying the user, after you have done your checking, while keeping the background process as it is.