In a rails4 app I have a very long task (can take hours) handled by resque (like railscasts #271).
The create action in the controller looks like this:
def create
#longstuff = Longstuff.new(params[:longstuff])
if #longstuff.save
Resque.enqueue(LongStuffHandler, #longstuff.id)
redirect_to #longstuff, :notice => "Success."
else
render 'new'
end
end
I want to update the user on the status of the queue and give him useful information (stats, elapsed time, progressbars etc.)
My idea is to create a div in the "new" view that autorefreshes with javascript every 3 seconds and displays some content that the LongStuffHandler class provides.
So far I've been able to achieve that making the LongStuffHandler class write some html to a file that is loaded by the javascript.
This leads to several problems, for example writing to a file 10 times per second is less than ideal.
I would like to have the LongStuffHandler class to be able to export some variables in real time and making them available to the "new" view so that just reloading a partial will make my page look updated.
An example would be that the LongStuffHandler class has a variable called "#lastlog" and makes it available to the "new" view where there is a div like this autoupdated by javascript:
<div class="lastlog">
<h2>Last Log</h2>
<p><%= #lastlog %></p>
</div>
Is it possible for the background job to export such a variable and for the view to import it?
Related
So I'm working on a web application and I have a blog page, with two controllers, a posts controller for the blog and then a subscribers controller that simply allows the user to add a new subscriber. So currently I render the content using a view defined in posts, and then within that I have a partial, _subscriber, to handle the subscription model.
The problem comes when the user tries to subscribe. I want to be able to render the new action to show validations, like this:
def create
#subscriber = Subscriber.new(subscriber_params)
if #subscriber.save
flash[:success] = "Thank you for subscribing!"
redirect_to subscribers_url
else
render :new, status: :unprocessable_entity
end
end
Besides the fact that this results in a duplicate view (new.html.erb is identical to the partial), because it is not a partial view, it reloads the entire page and now replaces all of the post with just the subscription form. Currently, my workaround is to do a redirect when the user clicks the button, but then validation errors don't show and it isn't really an ideal solution.
I tried just rendering the partial, instead of the :new action, like this:
render partial: 'subscriber', status: :unprocessable_entity
But has the same effect as just calling redirect; my validation errors won't show.
Perhaps the ideal solution is to use something like AJAX. I tried to understand Turbo Frames and Turbo Streams, but I'm a little confused how I would implement that in this situation. Essentially, I could wrap the subscribe section of my posts page in a turbo frame but then how would I update the turbo frame? I don't want to have a different post page because that wouldn't make sense. I only want to change the content of the view inside.
What would the correct/best practices way of implementing this functionality be? Any suggestions would be appreciated!
If you don't want to actually reload the page, then yes, you'll need to do something with either AJAX / UJS or Turbo frames.
My favorite AJAX / UJS tutorial
A good Turbo tutorial
(Or go to the dark side with React or Node or some other JS solution)
I the top of my application template body I have:
<%= turbo_frame_tag :toasts %>
Which I use to display messages (https://github.com/excid3/tailwindcss-stimulus-components)
In my controllers I have an update_index patch route so that I can do some updates from the index page. The controller and corresponding turbo rendering works on the UI. In that update_index controller action I have this:
#project.broadcast_append_later_to #project, target: 'toasts', partial: 'shared/toast', locals: { user: current_user, type: :notice, message: "Project #{#project.name} Updated" }
In this action (for context is it matters this was the first model I added to my app). When I update via that action the toast is displayed as expected. The crazy part here is if I add this same logic to my other models the toast gets appended TWICE every-time.
I checked the logs and I confirmed that Sidekiq is not firing a job
twice
I tried broadcast_append_to vs later and the same result If I
comment this out both toasts are not displayed on the update so I
know it's not being triggered somewhere else
I confirmed that I don't have any broadcasts in my models the exact same line of code in the default update action only displays once
I have gone through model and controllers and they are almost identical except for the odd items here and there - nothing related to this toast (which simply displays the message passed in)
Some context to perhaps trigger ideas:
the Project model was the first model I added to the app and the rest came afterwards
all the other models are descendants of Project directly or indirectly via belong_to / has_many etc.
the issue only occurs in that update_index action
This seems like a wild goosechase and looking for help - perhaps someone may be able to point me to something obvious I am missing here.
This happens when you add a turbo_stream_from tag twice 🤦♂️
After reading Running Rails jobs in background threads I tried to refactor an controller action that is basically rendering an report in a xml format and sends the report file to the user browser. Now the action runs c. 30 sec, so I thought this a good candidate for a background job. Hovewer, I have run into a problem: it seems I can't access the report erb template from within the new thread.
This is my first attempt with threads and I am not sure if I am able to accomplish my refactoring with the approach.
Simplified code of action before refactoring:
#orders = Order.onrange(range).includes(:logo => [:simulations, :pantones])
respond_to do |format|
format.xml {
orders_xml = render_to_string template: 'orders/bom'
send_data orders_xml, filename: "BOM raport - #{range.to_s}.xml"
}
end
I don't know if you already see this guide: http://guides.rubyonrails.org/active_job_basics.html if not, please take a closer look.
From what you describe you can achieve your goal with:
Make a first request, that will call a controller action to produce the report and use the callback 'after_perform' to update the view with the produced report.
Split the reports from "asking the report" and "view the report". The first will enqueue the report generation and once it's ready will be available for user in a reports section.
I would prefer the second option if you plan the report generation time will increase or if it's unacceptable to have the user waiting 30 secs for a report. Other option could be to generate the report after you have the needed data, so when the user ask for it it's already generated. (just guessing because I don't know your application flow)
Let's take a scenario:
counter 10 seconds
User visited show.html.erb page
show.html.erb fetch the value from database using <%= #post.value %>.
Counter started and each iteration of counter is for 10 seconds.
After every 10 seconds I wanted to change the #post.value using utility class.
update the #post.value in database.
Refresh the show.html.erb automatically and <%= #post.value %> will show the updated value
Above process will be run in loop until user move to other pages.
If I have to simplify the problem in code then it would like this:
View Page
<%= #post.value %>
<%= #post.name %>
Controller Method
def show
#post = Post.find(params[:id])
end
def update
.... #It's empty right now
end
def fetching_value_from_PostUpdate(current_value)
.. # Now I need to update the value in database
end
Utility class
I want to update the post.value on the basis of method written in this class. Pseudo code:
class PostUpdate
##instance_variable
def initialize(post_value)
##instance_variable = Marshal.load(Marshal.dump(post_value))
end
#Method required to calculate the value
def update_data_in_database
...
return data
end
Questions
Where do I have to put the counter? client side or server side? I don't want to use background jobs in rails.
What changes do I need to make in show method so that after every interval page will refresh automatically and pass the updated value in #post.value?
Thanks for your help!
I would go with Firebase as opposed to polling the server.
However, if you're wanting to poll the server periodically, I would just have a JavaScript method which executes every 10 seconds, or at whatever interval you'd like, and fetches whatever data you'd like asynchronously and subsequently updates the DOM.
Also, ruby wrapper for firebase api, in case that may be of interest
I would say the easiest approach doing it would be using ActionController::Live. Using it you'll be able to send SSE(Server Sent Event) to your client while having js script there to catch and update your <%= #post.value %>. Here's a pretty nice manual on using it.
Still from my point of view the most appropriate way to implement things you want to do will be using something like Faye. It is a publish/subscribe messaging system which will allow you to update your client with new data as soon as it appears, e.g., you can set an after_save in your model and publish an update to all subscribed clients. And of course there is a gem also called faye to wrap its usage.
I have a task which will take around 20 - 30 seconds. I want this task to run immediately after a view is rendered. I want the output of this task to be stored in a session variable. What is the simplest way to do it rails 3.
To explain what i need exactly. I am showing a list of tweets to users (this is the view rendered) They have to go through them. I want a method to be invoked after this view is rendered which will take the same tweets and cluster them (20-30 seconds process). I should have the results of the clustering in the session so that i can display them in the next page.
How do I do this?
You should be able to do it in the controller method that renders the view. Just add the code after 'render' call. For example:
def index
#tweets = Tweet.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #tweets }
end
# do something with tweets
puts "#{#tweets}"
end
This is how I solved the problem
I used resque for background tasks. I enqueued the task just before the render was called.
I coded the background worker to put the result of the clustering in a redis cache.
The controller where the clusters are supposed to be shown, I read the results from redis, deserialized them and rendered them.
It was not perfect but it was fast enough for a research feedback collection tool.