rails controller action contain an algorithm - ruby-on-rails

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

Related

Execute task in another thread

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

How to Make the Controller wait for a Delayed Job while the rest of the App continues on?

(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.

How to force to complete response in controller action?

I need to make some slow operations on controller action. But it's not necessary to wait this operation for response rendering.
class ProductController < ActionController
def update
slow_operations()
render json: {status: 'ok'}
end
end
Even I move my code after render in Product#update action, it's not reduces response time.
class ProductController < ActionController
def update
render json: {status: 'ok'}
slow_operations()
end
end
How to force to return complete response before executing of slow operations?
Because of the way Rails works, it's still going to do the actual "rendering" after the action is complete - so, as you found out, moving "render" higher in your action doesn't help. What you need to do is shuffle off the long-running operation into a background process. There's lots of gems to do this, including BackgroundRb, Delayed Job and Sidekiq (my personal favourite, largely because it is multi-threaded, cutting down on the number of processes you need to start, and because of its nice web-based admin/monitoring interface).
There's even a Railscast to get you started for most of these, like this one: http://railscasts.com/episodes/366-sidekiq
You need to integrate something like Resque or Girl Friday to offload the slow process to a background task.

Providing updates during a long Rails controller action

I have an action that takes a long time. I want to be able to provide updates during the process so the user is not confused as to whether he lost the connection or something. Can I do something like this:
class HeavyLiftingController < ApplicationController
def data_mine
render_update :js=>"alert('Just starting!')"
# do some complicated find etc.
render_update :js=>"alert('Found the records!')"
# do some processing ...
render_update :js=>"alert('Done processig')"
# send #results to view
end
end
No, you can only issue ONE render within a controller action. The render does NOTHING until the controller terminates. When data_mine terminates, there will be THREE renders, which will result in an error.
UPDATE:
You'll likely have to set up a JavaScript (jquery) timer in the browser, then periodically send an AJAX request to the server to determine the current status of your long running task.
For example the long running task could write a log as it progresses, and the periodic AJAX request would read that log and create some kind of status display, and return that to the browser for display.
It is impossible to handle the request that way. For each request, you have just one answer.
If your action takes a long time, then maybe it should be performed asynchronously. You could send user e-mails during the process to notify him of the progress.
I suggest that you to take a look on DelayedJob gem:
http://rubygems.org/gems/delayed_job
It will handle most difficult parts of dealing with assync stuff for you (serializing / deserializing your objects, storage, so on...).
Hope it helps you!

Change view dynamically from controller - Progress bar with threading in Rails

I have a time taking task in my controller for which I want to trigger a simple progress bar/progress status in my view.
In my controller I have something like
threads = []
total.times do |x|
Thread.new {
the_time_taking_task(x)
#Something here to trigger the progressbar in the view
}
end
threads.each { |aThread| aThread.join }
render :action => 'some_action'
I can't put a render in there because it throws a double render error.
I tried putting a render and return there and I got 'return can't jump across threads' error
I am trying render as it seems to be the only way my controller can talk to view. Is my approach wrong ?
Would want something simple like this in the view which applies the progress bar width through that call
<div style="width: <%= (percent_val)%>px; ">
Please advice.
Yes it's a wrong approach.
The Web is stateless, and you can't block a process to render view and get it after by another request.
The good way is :
1) Generate a queue tasking instead of your thread. You can use delayed_job, resque or queue system, like rabbitMQ or beanstalkD
2) Add a controller to check if the process in your queue is end and how is progress. Inside, you put the queue id and check if is end or not and return the progress percent.
The easiest way is do that by Ajax.
Yes, your approach is wrong.
shingara explains this well.
I'm offering a slightly more concrete solution for you.
To get the effect you want, you basically need the following steps:
Offload the thread into a background job server. Checkout resque or delayed_job.
Have the background job write the progress to database or some persistant medium.
In Rails, write an action that returns the current progress (as json or xml)
In your view, write a periodic ajax request that calls the action written in step 3.
update the view accordingly.
You're done! :D

Resources