How to update partial after completing job - ruby-on-rails

This is my first post here but I have gotten some great info from this site already. So I thought someone may be able to help.
In my view I have a form that once submitted, the controller passes the job off to navvy and it really works well. The problem is I would like to have another partial on the same page as the form update with the new info once navvy is done working. So in my controller I have:
Navvy::Job.enqueue( GetZip, :get_zip, #series, :job_options => {:priority => 8})
And then in my navvy block which is located in config/initializers/navvy.rb I have:
class GetZip
def self.get_zip(params)
fetch = Fetch.new
fetch.get_zip(params)
# What to put here to update partial #
end
end
Which works as expected. I am just not sure how I can have the partial in my view updated once the navvy job is completed. Any suggestions would be greatly appreciated.

The problem here is that once you've fired up a background process you're no longer tied into that user's request (which is obviously the point of the background process).
So your background job is not aware of what the user is doing in the meantime. For example they could navigate to another page, or even leave your website.
You could get your background job to create a record in a database that indicates it has started processing and update this status when it has finished processing. That way when the user next refreshes the page you can check the status of that record and the partial can be updated accordingly.
If you want the page to auto-update you could keep polling the status of that record with ajax requests. On the initial submission of the form you could start the polling rather than have it running all the time.

Here's what I'm using.
This is where the Job is called:
Setting::progress = "Starting..."
Navvy::Job.enqueue(EmailWorker, :async_email, stuff)
Settings is super simple:
class Setting < ActiveRecord::Base
def Setting::progress=(value)
setn = Setting.find_or_initialize_by_name("email_status")
setn.value = value
setn.save
end
end
and the navvy job EmailWorker is:
class EmailWorker
def self.async_email(options)
# send one at a time
total = options[:list].length
errors = []
options[:list].each_with_index do |email_addr, n|
begin
Setting::progress = "#{n+1}/#{total}: #{email_addr}"
Postoffice.one_notice(email_addr, options).deliver
rescue Exception => e
Setting::progress = "#{email_addr} #{e.message}"
errors << "<span class='red'>#{email_addr}</span> #{e.message}"
end
# get stack level too deep errors at random when this is removed. I don't get it.
sleep 0.05
end
Setting::progress = "DONE sending #{total} with #{errors.length} errors<br/>#{errors.join("<br/>")}"
"Done" # just for display in Navvy console output
end
end
Then the triggering page has this:
<%-
# had to add this back as rails 3 took it away
def periodically_call_remote(options = {})
frequency = options[:frequency] || 10 # every ten seconds by default
code = "new PeriodicalExecuter(function() {#{remote_function(options)}}, #{frequency})"
javascript_tag(code)
end
-%>
<script type="text/javascript">
//<![CDATA[
check_var = true;
//]]>
</script>
<%= periodically_call_remote(
:condition => "check_var == true",
:url => { :controller => "application",
:action => "update" },
:frequency => '3') -%>
<div id="progress">
<p>Monitoring progress of emails</p>
</div>
And heres the method that's called repeatedly:
def update
raise "not for direct" if (!request.xhr?)
#progress = Setting::progress
#again = !(/^DONE/ =~ #progress)
render :action => "update"
end
Which trigers an in-place update via update.rjs
page.assign('check_var', #again)
page.replace_html "progress", :partial => "info", :locals => { :info => #progress }
page.visual_effect :highlight, 'progress', :startcolor => "#3333ff", :restorecolor => '#ffffff', :duration => 0.5
One word of warning about Navvy - since it runs as a background task until killed, it will stay executing your old app when you cap deploy. You must kill navvy in old "current" and move to new "current".

Related

Create a link for next page in rails

I'm using the Twilio API in a rails app to show a user a list of their recordings. Say a user has 11 recordings total, and I'm showing them 3 per page.
twilio_controller.rb
def calls
#user = current_user
#account_sid = #user.twilio_account_sid
#auth_token = #user.twilio_auth_token
#page_size = 3
#page = params[:page_id] || 0
#sub_account_client = Twilio::REST::Client.new(#account_sid, #auth_token)
#subaccount = #sub_account_client.account
#recordings = #subaccount.recordings
#recordingslist = #recordings.list({:page_size => #page_size, :page => #page})
end
calls.html.erb
<% #recordingslist.each do |recording| %>
<tr>
<td><%= recording.sid %></td>
</tr>
<% end %>
<%= link_to "Next Page", twilio_calls_path(#page + 1) %>
routes.rb
#twilio routes
post 'twilio/callhandler'
get 'twilio/calls'
match 'twilio/calls' => 'twilio#page', :as => :twilio_page # Allow `recordings/page` to return the first page of results
match 'twilio/calls/:page_id' => 'twilio#page', :as => :twilio_page
Paging info is built into the Twilio response such that
#recordingslist.next_page
gives me the next 3 recordings (verified in rails console). How do I link to that so that when a user clicks the link, the table loads the next 3 results?
Thanks!
You can use a gem like Kaminari for Pagination.
https://github.com/amatsuda/kaminari
I would recommend utilizing the paging functionality that ships with twilio-ruby. According to the docs:
ListResource.list() accepts paging arguments.
Start by create a route for your Twilio list view. Make sure you can pass a page_id parameter – this is how your controller action will know which page to render:
# config/routes.rb
match 'recordings/page/:page_id' => 'twilio#page', :as => :twilio_page
match 'recordings/page' => 'twilio#page', :as => :twilio_page # Allow `recordings/page` to return the first page of results
Then, in the page action, pull the page_id parameter (or set if to 1 if there is no page_id, and pass the page_number and page_size as arguments to #recordings.list:
# app/controllers/twilio_controller.rb
def page
page_size = 3
#page = params[:page_id] || 1
#sub_account_client = Twilio::REST::Client.new(#account_sid, #auth_token)
#subaccount = #sub_account_client.account
#recordings = #subaccount.recordings
#recordingslist = #recordings.list({:page_size => page_size, :page => page)})
end
Finally, in your view, pass the page number to twilio_page_path in your link_helper – just make sure to adjust the page number accordingly (+1 for the next page, -1 for the previous page:
# view
<%= link_to "Next Page", twilio_page_path(#page.to_i + 1) %>
<%= link_to "Previous Page", twilio_page_path(#page.to_i - 1) %>
Note that – if you're at the start or end of your list – you may inadvertently end up passing an invalid page_id. Therefore, you may want to implement some exception handling in your page controller action:
# app/controllers/twilio_controller.rb
def page
begin
#page = params[:page_id] || 1 # If `page_id` is valid
rescue Exception => e
#page = 1 # If `page_id` is invalid
end
# Remaining logic...
end

How do I use a controller method in the main layout?

I have built a 'NewsItem' controller that contains a method called 'top'. This is called by a javascript request to update a DIV on the page every 10 seconds. This is all working well.
def top(number = false)
# set the default value to use for the number
default_number = 10;
# perform checks on the variable
if number == false
if params.include?(:number)
number = params[:number]
else
number = default_number
end
end
# Run query to get the required news items
items = NewsItem.all( :order => ("created_at DESC"),
:include => [:profile],
:limit => number)
# iterate around the items that have been returned
#top_news = ""
items.each do |item|
#top_news += render_to_string :partial => "partials/news_item", :locals => {:item => item}
end
respond_to do |format|
format.html { render :partial => "partials/news_top"}
end
end
This is called with '/news/top' or '/news/top/20' to change the number of items that are returned.
The problem is that when the page is first loaded the 'news' DIV is empty for 10 seconds, until the JavaScript runs to update the DIV. So I want to ensure that the DIV is already populated by calling this function.
Now as I want the 'news' DIV to be available in all pages it is defined in the 'layouts/application.html.erb' template. So I need to call the 'top' method in the 'NewsItem' controller so that it can be rendered into the initial HTML. This is where I am struggling as I cannot work out how to use the 'helper_method' to make this available at this stage.
I have a feeling that I am missing something here and not understanding the whole process.
Thanks very much for any assistance.
Regards, Russell
Try separating out the logic. Maybe you make a method called top_news that returns the value you use in top_news. And since you're passing back a partial, use it to build the string. Partials are meant to be used to iterate over a list.
def index
#top_news = top_news
end
def top
#top_news = top_news
respond_to do |format|
format.html { render :partial => "partials/news_top"}
end
end
private
def top_news(number = false)
# set the default value to use for the number
default_number = 10;
# perform checks on the variable
if number == false
if params.include?(:number)
number = params[:number]
else
number = default_number
end
end
# Run query to get the required news items
items = NewsItem.all( :order => ("created_at DESC"),
:include => [:profile],
:limit => number)
# iterate around the items that have been returned
return render_to_string :partial => "partials/news_item", :collection => items, :as => :item
end
The other solution you can follow is to not render any partials in the controller at all except for your one javascript method and instead make a method in your model to do exactly what you're doing here.
class NewsItem
def top_news(number=20) # pass in number with default 20
NewsItem.where( :order => ("created_at DESC"),
:include => [:profile],
:limit => number)
end
end
Then just call it from your controllers to get that so you can use it in your views and iterate over it using partials in your views.

Change pagination dynamically in ActiveAdmin or solutions for printing

I'm new to Activeadmin and rails and I need some help.
I have a model that is paginated and I want to allow the user to change the pagination value or disable it completely, so it can print (to a printer) all the records (or filtered ones) for instance.
I know I can set the pagination using #per_page in :before_filter, but I can't figure out how I can change this value during execution.
To solve the problem of needing to show all the unpaginated records I defined a custom page, but in this page the filter or scope don't work so it's kind of useless.
How can I create a Print button in Active Admin?
This is a workaround to do it, I know it is not the best solution but it works ! :)
This is the app/admin/mymodel.rb file
ActiveAdmin.register MyModel do
before_filter :paginate
#other code
controller do
def paginate
#per_page = params[:pagination] unless params[:pagination].blank?
end
end
index do
panel "Pagination" do
render partial: "paginate", locals: {resource: "mymodels"}
end
#other code
end
#other code
end
And for the app/views/admin/articles/paginate.html.haml
#pagination_form
= form_tag do
= label_tag :pagination, "Number of " + resource + " per page : "
= text_field_tag :pagination
= submit_tag "Filter"
:javascript
$("#pagination_form form").submit(function(e){
e.preventDefault();
window.location = "/admin/#{resource}?pagination=" + $("#pagination").val();
})
Hoping that my answer can people with the same problem :)
I found a solution and I'm answering my own question for someone who has the same problem.
It may not be the best solution but it works, if someone has a better way please share:
ActiveAdmin.register mymodel do
before_filter :apply_pagination
# other code
index :download_links => false, :as => :table, :default => true do
if params[:pag].blank?
div link_to(I18n.t("text_for_the_link"), 'mymodel?pag=1', :class => "class_for_link")
else
div link_to(I18n.t("print.print"), 'mymodel', :class => "class_for_link")
end
# other code
end
controller do
def apply_pagination
if params[:pag].blank?
#per_page = 50
else
#per_page = 99999999
end
# other code
end
end
I found out you can define this by registering the following line on the resource:
ActiveAdmin.register MyModel do
config.per_page = [20, 50, 100, 200]
end
It automatically adds a select box in the index pagination with the preset values given in the array.

Screen scraping when results can't be found?

I have the following code in a screen scraping rake task
page = agent.get("https://domainname.co.uk/unit/27/logs?type=incoming&page=8")
page = agent.page.search("table tbody tr").each do |row|
next if (!row.at('td'))
time, source, destination, duration = row.search('td')[1..5].map{ |td| td.text.strip }
parsed_time = Time.parse(time)
unless Call.find_by_time(parsed_time)
Call.create({:time => parsed_time, :source => source, :destination => destination, :duration => duration})
end
end
This section of the script navigates to page 8 and then creates a call record for each table row of data.
If the page I have navigated to doesn't contain any calls logs, it show the following code:
<tr class='no-data'>
<td colspan='7'>There are no call records matching the search criteria</td>
</tr>
When the rake task navigates to a page with no call logs the tasks fails to complete. It shows the following error:
rake aborted!
can't convert nil into String
So, is there a way when using Nokogiri and Mechanize to recover from nil? Is there a simple way of checking if <tr class='no-data'> exists before trying to import the data?
Update with suggested code
Error message
Scraping Page 9
rake aborted!
can't convert nil into String
Code
puts 'Scraping Page 9'
if agent.page.root.css('tr.no-data').empty?
page = agent.get("https://domaindname.co.uk/27/logs?type=incoming&page=9")
page = agent.page.search("table tbody tr").each do |row|
next if (!row.at('td'))
time, source, destination, duration = row.search('td')[1..5].map{ |td| td.text.strip }
parsed_time = Time.parse(time)
unless Call.find_by_time(parsed_time)
Call.create({:time => parsed_time, :source => source, :destination => destination, :duration => duration})
end
end
else
puts 'No calls on this page'
end
You can check to see if that element exists
if agent.page.root.css('tr.no-data').empty?
# it doesn't exist
else
# do the normal thing
end

Rails: Passing new child object placeholder (build) to parent view

I've got 2 classes of objects... Magician has_many Rabbits and Rabbit belongs_to Magician.
When viewing a Magician (show.html) I'd like to list all the associated Rabbits and then have some blank fields with a submit button to add a new Rabbit. To do so I build a new rabbit (associated to the current magician) in the Magician's show method (below).
Edit2: found way to make it work but not sure if it's the "Rails way"?
see comments inline (below):
If I build the rabbit in Magician's show method then when show is rendered an empty (and invalid) rabbit ends the list before the new rabbit form fields are then shown.
If I build it in the view itself then everything works & renders correctly. I was led to believe that we should not be doing this type of stuff in the view...if so, what's the proper way to address this?
#/app/controllers/magicians_controller.rb
class MagiciansController < ApplicationController
respond_to :html, :json
def show
#magician = Magician.find(params[:id])
#rabbit = #magician.rabbits.build # <=== build here and empty rabbit gets
# included in #magician.rabbits when they're rendered...
# but is NOT included in #magician.rabbits.count for some reason?!?!?
respond_with(#magician)
end
...
end
#view/magicians/show.html.haml
%p
%b Name:
= #magician.name
%h2 Rabbits
= "There are #{pluralize(#magician.rabbits.count, "rabbit")}"
= render #magician.rabbits, :target => #magician
%h2 Add a rabbit:
- #rabbit = #clown.rabbits.build -# <=== build here and everything SEEMS to work
= render :partial =>"rabbits/form", :locals => { :parent => #magician, :foreign_key => :magician_id, :flash => flash }
Edit1: Adding generated html from partial as per request:
<p>
<b>Rabbit:</b>
<b>Color:</b>
|
<b>ID:</b>
Kill Rabbit
</p>
And I suppose you probably want to see the partial that generates this:
%p
%b Rabbit:
= rabbit.name
%b Color:
= rabbit.color
|
%b ID:
= rabbit.id
= link_to("Kill Rabbit", [target, rabbit], :method => :delete, :confirm => "Sure? A bunny will die")

Resources