How do you delay a rendering job? - ruby-on-rails

This method works OK, but if I add delayed_job's handle_asychronously, I get can't convert nil into String:
def onixtwo
s = render_to_string(:template=>"isbns/onix.xml.builder")
send_data(s, :type=>"text/xml",:filename => "onix2.1.xml")
end
handle_asynchronously :onixtwo
So rendering with delayed job is clearly having a problem with params being passed. I've tried putting this job in a rake task but render_to_string is a controller action - and I'm using a current_user variable which needs to be referenced in the controller or view only. So... what's the best way to delay a rendering job?
/////////update////////
Given that I'm currently pair-programming with a toddler, I don't have the free hands to investigate additional class methods as wisely recommended in the comments - so as a quick and dirty I tried this:
def onixtwo
system " s = render_to_string(:template=>'isbns/onix.xml.builder') ; send_data(s, :type=>'text/xml',:filename => 'onix2.1.xml') & "
redirect_to isbns_path, :target => "_blank", :flash => { :success => "ONIX message being generated in the background." }
end
Why doesn't it work? No error message just no file produced - which is the case when I remove system ... &

For what it's worth, this is what I did, bypassing render_to_stream entirely. This is in /lib or app/classes (adding config.autoload_paths += %W(#{config.root}/classes into config/application.rb):
#classes/bookreport.rb
# -*- encoding : utf-8 -*-
require 'delayed_job'
require 'delayed/tasks'
class Bookreport
# This method takes args from the book report controller. First it sets the current period. Then it sorts out which report wants calling. Then it sends on the arguments to the correct class.
def initialize(method_name, client, user, books)
current_period = Period.where(["currentperiod = ? and client_id = ?", true, client]).first
get_class = method_name.capitalize.constantize.new
get_class.send(method_name.to_sym, client, user, books.to_a, current_period.enddate)
end
end
#app/classes/onixtwo.rb
require 'builder'
class Onixtwo
def onixtwo(client_id, user_id, books, enddate)
report_name = "#{Client.find_by_id(client_id).client_name}-#{Time.now}-onix-21"
filename = "#{Rails.root}/public/#{report_name}.xml"
current_company = Company.where(:client_id => client_id).first
File.open(filename, "w") do |file|
xml = ::Builder::XmlMarkup.new(:target => file, :indent => 2)
xml.instruct!(:xml, :version => "1.0", :encoding => "utf-8")
xml.declare! :DOCTYPE, :ONIXMessage, :SYSTEM, "http://www.editeur.org/onix/2.1/03/reference/onix-international.dtd"
xml.ONIXMessage do
xml.Header do
#masses of Builder code goes here...
end
end #of file
xmlfile = File.open(filename, "r")
onx = Onixarchive.new(:client_id => client_id, :user_id => user_id)
onx.xml = xmlfile
onx.save!
end #of method
handle_asynchronously :onixtwo
end #of class
Called from the view like this:
= link_to("Export to ONIX 2.1", params.merge({:controller=>"bookreports" , :action=>:new, :method_name => "onixtwo"}))
Via a controller like this:
class Books::BookreportsController < ApplicationController
#uses Ransack for search, hence the #q variable
def new
#q = Book.search(params[:q])
#books = #q.result.order('pub_date DESC')
method_name = params[:method_name]
Bookreport.new(method_name, #client, #user, #books)
redirect_to books_path, :flash => {:success => t("#{method_name.to_sym}").html_safe}
end
end

Related

Download zip file from activeadmin batch action

I want to send_data to the admin who is using the activeadmin interface in our website. This data is a zip file and can be downloaded if certain conditions on the selected items are met.
I created a service that handles the logic (quite complex) behind it. So from activeadmin I can call:
batch_action :action_name, form: {selection: ['...']} do |ids, inputs|
response = MyService.new(ids, inputs[:selection]).my_method
redirect_to collection_path
end
In my service MyService.rb:
...
def my_method
...
if condition
zip_data = Zip::OutputStream.write_buffer do |zip|
zip.put_next_entry("#{original_file_name}.xml")
zip << File.read(original_file)
end
send_data(zip_data.read, :type => 'application/zip', :filename => "#{original_file_name}.zip")
# here send_data throws an error because it's a controller method
else
...
end
...
end
...
But how do I use the send_data method properly? Maybe I have to restructure something? I know you can probably do ActionController::DataStreaming.send_data(...) outside of the controller, but this is not recommended for the code's sake.
Solved. I put the send_datain the batch_action code like this:
batch_action :action_name, form: {selection: ['...']} do |ids, inputs|
response = MyService.new(ids, inputs[:selection]).my_method
redirect_to collection_path
send_data(response[:zip][:data].read, :type => 'application/zip', :filename => "#{response[:zip][:name]}.zip") if response[:zip].present?
end
where the response contains the zip data to send (which needs to be rewinded with zip_data.rewind before being sent). my_service.rb is now like:
...
def my_method
...
if condition
zip_data = Zip::OutputStream.write_buffer do |zip|
zip.put_next_entry("#{original_file_name}.xml")
zip << File.read(original_file)
end
zip_data.rewind
response[:zip] = {data: zip_data, name: original_file_name}
else
...
end
...
end
...

NoMethodError: undefined method `sort` from Twilio voice example

I am trying to set up an example Twilio Rails project that calls a person. I am following the tutorial associated with this repo and have basically a carbon copy of the codebase. I'm getting an error that I think is from this line #validator = Twilio::Util::RequestValidator.new(##twilio_token).
Here's my twilio_controller.rb
class TwilioController < ApplicationController
# Before we allow the incoming request to connect, verify
# that it is a Twilio request
skip_before_action :verify_authenticity_token
before_action :authenticate_twilio_request, :only => [
:connect
]
##twilio_sid = ENV['TWILIO_ACCOUNT_SID']
##twilio_token = ENV['TWILIO_AUTH_TOKEN']
##twilio_number = ENV['TWILIO_NUMBER']
##api_host = ENV['TWILIO_HOST']
# Render home page
def index
render 'index'
end
def voice
response = Twilio::TwiML::Response.new do |r|
r.Say "Yay! You're on Rails!", voice: "alice"
r.Sms "Well done building your first Twilio on Rails 5 app!"
end
render :xml => response.to_xml
end
# Handle a POST from our web form and connect a call via REST API
def call
contact = Contact.new
contact.user_phone = params[:userPhone]
contact.sales_phone = params[:salesPhone]
# Validate contact
if contact.valid?
#client = Twilio::REST::Client.new ##twilio_sid, ##twilio_token
# Connect an outbound call to the number submitted
#call = #client.calls.create(
:from => ##twilio_number,
:to => contact.user_phone,
:url => "#{##api_host}/connect/#{contact.encoded_sales_phone}" # Fetch instructions from this URL when the call connects
)
# Let's respond to the ajax call with some positive reinforcement
#msg = { :message => 'Phone call incoming!', :status => 'ok' }
else
# Oops there was an error, lets return the validation errors
#msg = { :message => contact.errors.full_messages, :status => 'ok' }
end
respond_to do |format|
format.json { render :json => #msg }
end
end
# This URL contains instructions for the call that is connected with a lead
# that is using the web form.
def connect
# Our response to this request will be an XML document in the "TwiML"
# format. Our Ruby library provides a helper for generating one
# of these documents
response = Twilio::TwiML::Response.new do |r|
r.Say 'FUCK.', :voice => 'alice'
# r.Dial params[:sales_number]
end
render text: response.text
end
# Authenticate that all requests to our public-facing TwiML pages are
# coming from Twilio. Adapted from the example at
# http://twilio-ruby.readthedocs.org/en/latest/usage/validation.html
# Read more on Twilio Security at https://www.twilio.com/docs/security
private
def authenticate_twilio_request
twilio_signature = request.headers['HTTP_X_TWILIO_SIGNATURE']
# Helper from twilio-ruby to validate requests.
#validator = Twilio::Util::RequestValidator.new(##twilio_token)
# the POST variables attached to the request (eg "From", "To")
# Twilio requests only accept lowercase letters. So scrub here:
post_vars = params.reject {|k, v| k.downcase == k}
is_twilio_req = #validator.validate(request.url, post_vars, twilio_signature)
unless is_twilio_req
render :xml => (Twilio::TwiML::Response.new {|r| r.Hangup}).text, :status => :unauthorized
false
end
end
end
Error image:
I am using ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-darwin15] and Rails 5.1.0.
Your code is most likely failing at is_twilio_req = #validator.validate(request.url, post_vars, twilio_signature) because upon inspection of the gem's code, it is failing at sort below
data = url + params.sort.join
This is because in Rails 5.1, ActionController::Parameters no longer inherits from Hash, so Hash methods like sort (see Hash docs) will no longer work.
You will need to convert params into hash explicitly:
def authenticate_twilio_request
twilio_signature = request.headers['HTTP_X_TWILIO_SIGNATURE']
#validator = Twilio::Util::RequestValidator.new(##twilio_token)
# convert `params` which is an `ActionController::Parameters` object into `Hash`
# you will need `permit!` to strong-params-permit EVERYTHING so that they will be included in the converted `Hash` (you don't need to specifically whitelist specific parameters for now as the params are used by the Twilio gem)
params_hash = params.permit!.to_hash
post_vars = params_hash.reject {|k, v| k.downcase == k}
is_twilio_req = #validator.validate(request.url, post_vars, twilio_signature)
unless is_twilio_req
render :xml => (Twilio::TwiML::Response.new {|r| r.Hangup}).text, :status => :unauthorized
false
end
end

Delayed job argument error

I'm having bother with delayed_job using the active_record fork (link).
In the controller:
guide = Rightsguide.new
guide.run(#works, current_user)
in the Rightsguide ruby class:
require 'delayed_job'
require 'delayed/tasks'
require 'prawn'
require 'open-uri'
class Runrightsguide
def run(works, current_user)
pdf = Rightsguidereport.new(works, current_user)
filename = "#{Rails.root}/public/#{Date.today}_rightsguide.pdf"
pdf.render_file(filename)
pdf_file = File.open(filename)
archive = RightsguideArchive.new(:user_id => current_user)
archive.pdf = pdf_file
archive.save!
User.find(current_user).notice "<a href='/rightsguide_archives' target='_blank'>View Rights Guide</a>", :level => :notice, :sticky => true, :title => "AIs generated."
end
end
The above works fine, but when I use one of the delayed_job calls such as handle_asynchronously :run after the run method I get wrong number of arguments (2 for 1).
Hmm. Turns out the #works argument was the problem. It's an ActiveRecord relation. Delayed_job didn't like it. Turning the relation into an array of IDs did the job.

Ruby on Rails: Equating items in controllers (or maybe models?)

I'm trying to make attributes equal predetermined values, and I'm not sure if I'm doing that efficiently with the following (in my orders controller):
def create
#order = Order.find(params[:id])
#order.price = 5.99
#order.representative = Product.find(params[:product_id]).representative
#order.shipping_location = SHIPPING_LOCATION
#order.user = current_user
respond_to do |format|
...
end
end
Is there a more efficient way to equate attributes in Rails (maybe using models)? If I'm using two different controllers, do I just repeat what I did above for the new controller?
Use before_create callback in model to assign default values.
Your code is a little off, it looks like a controller action for create, but the code reads like it's for an update.
Regardless...
You could use a parameter hash to update everything at once.
In the case where you're creating:
order_update = {:price => 5.99, :representative =>
Product.find(params[:product_id]).representative,
:shipping_location => SHIPPING_LOCATION,
:user => current_user}
#order = Order.new(order_update)
In the case where you're updating:
#order.update_attributes(order_update) #attempts to save.
Mixing it into your controller code we get:
def create
#order = Order.find(params[:id])
order_update = {:price => 5.99, :representative =>
Product.find(params[:product_id]).representative,
:shipping_location => SHIPPING_LOCATION,
:user => current_user}
respond_to do |format|
if #order.update_attributes(order_update)
# save succeeded. Redirect.
else
# save failed. Render with errors.
end
end
end
Another solution:
class Example < ActiveRecord::Base
DEFAULTS = HashWithIndifferentAccess.new(:some => 'default', :values => 'here')
def initialize(params = {})
super(DEFAULTS.merge(params))
end
end
Either use initialize and merge with params, or use an ActiveRecord hook like before_create etc.

What's the best way to refactor this Rails controller?

I'd like some advice on how to best refactor this controller. The controller builds a page of zones and modules. Page has_many zones, zone has_many modules. So zones are just a cluster of modules wrapped in a container.
The problem I'm having is that some modules may have some specific queries that I don't want executed on every page, so I've had to add conditions. The conditions just test if the module is on the page, if it is the query is executed. One of the problems with this is if I add a hundred special module queries, the controller has to iterate through each one.
I think I would like to see these module condition moved out of the controller as well as all the additional custom actions. I can keep everything in this one controller, but I plan to have many apps using this controller so it could get messy.
class PagesController < ApplicationController
# GET /pages/1
# GET /pages/1.xml
# Show is the main page rendering action, page routes are aliased in routes.rb
def show
#-+-+-+-+-Core Page Queries-+-+-+-+-
#page = Page.find(params[:id])
#zones = #page.zones.find(:all, :order => 'zones.list_order ASC')
#mods = #page.mods.find(:all)
#columns = Page.columns
# restful params to influence page rendering, see routes.rb
#fragment = params[:fragment] # render single module
#cluster = params[:cluster] # render single zone
#head = params[:head] # render html, body and head
#-+-+-+-+-Page Level Json Conversions-+-+-+-+-
#metas = #page.metas ? ActiveSupport::JSON.decode(#page.metas) : nil
#javascripts = #page.javascripts ? ActiveSupport::JSON.decode(#page.javascripts) : nil
#-+-+-+-+-Module Specific Queries-+-+-+-+-
# would like to refactor this process
#mods.each do |mod|
# Reps Module Custom Queries
if mod.name == "reps"
#reps = User.find(:all, :joins => :roles, :conditions => { :roles => { :name => 'rep' } })
end
# Listing-poc Module Custom Queries
if mod.name == "listing-poc"
limit = params[:limit].to_i < 1 ? 10 : params[:limit]
PropertyEntry.update_from_listing(mod.service_url)
#properties = PropertyEntry.all(:limit => limit, :order => "city desc")
end
# Talents-index Module Custom Queries
if mod.name == "talents-index"
#talent = params[:type]
#reps = User.find(:all, :joins => :talents, :conditions => { :talents => { :name => #talent } })
end
end
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #page.to_xml( :include => { :zones => { :include => :mods } } ) }
format.json { render :json => #page.to_json }
format.css # show.css.erb, CSS dependency manager template
end
end
# for property listing ajax request
def update_properties
limit = params[:limit].to_i < 1 ? 10 : params[:limit]
offset = params[:offset]
#properties = PropertyEntry.all(:limit => limit, :offset => offset, :order => "city desc")
#render :nothing => true
end
end
So imagine a site with a hundred modules and scores of additional controller actions. I think most would agree that it would be much cleaner if I could move that code out and refactor it to behave more like a configuration.
You should check out this gem:
http://github.com/josevalim/inherited_resources/tree/master
It is very elegant, and solves all the problems you have.
I'd move your snippet-specific queries into helper methods and get them out of the controller so that the snippets themselves can execute the query via erb and kept DRY and readable via a helper. So instead of referring to #refs in your module, you can instead refer to find_all_refs or somesuch in a module and have that execute and possibly memoize the response.

Resources