How to render pdf into web browser in RoR - ruby-on-rails

I have files in server for whom i want to keep the url confidential. For this, i created a controller that fetch the data and ultimately render it to the web broswer.
In the view
<%= link_to "Click to view the file", file_proxy( user.pdf_file_url ) %>
In users_heper.rb
def file_proxy(url)
file_proxy_path(url: url)
end
In the routes.rb
get "file_proxy" => "file_proxy#fetch"
In the controller
def FileProxy < ApplicationController
def fetch
response = HTTParty.get params[:url]
render response
end
end
I'm getting an <HTTParty::Response:0x10cd6e6a8 parsed_response="%PDF-1.3......" is not an ActiveModel-compatible object. It must implement :to_partial_path.
Do you know how to tweak this code so that it can display the PDF file correctly ?
Thanks!

You can't call render that way. It's expecting very specific options. In this case it probably looks like:
pdf_content = HTTParty.get(params[:url])
send_data(pdf_content, disposition: 'inline', type: 'application/pdf')
As a note, you probably want to limit what sorts of things that tool fetches or someone will eventually abuse it.

Related

What happend after send_file in Rails controller?

In the index view, there is a link to download file:
<%= link_to filename, listing_download_path(:file => filename) %>
In the controller:
def download
pathname = File.join(USER_FOLDER, params[:file])
if File.file?(pathname)
send_file pathname
end
end
end
When the user click download, a file download popup is shown. What's happen after the file is downloaded? Does rails just sit there and do nothing more? If I delete the send_file line, dwonload.html.erb will be rendered. Does send_file skip view rendering?
What if I want to show soemthing like "You have downloaded ..."?
The send_file is a render itself. You could use the approach proposed in this question:
Rails: send_file never renders page or DoubleRender error
Basically your download link will send to a "success" view, from which you call the download file method automatically.

Using Private-pub with channels that have segment keys and coffeescript variant

I'm trying to replicate a push notification system similar to facebook's using private_pub. Ideally I would want to link this to show notifications using a gem such as gritter (other gem suggestions are welcome)
Whenever a certain action from a controller is called, I want to send a notification to all subscribers that are part of a specific id. As long you are logged in, you are subscribed to the channel, achieved by putting the subscribe_to in the layouts.
in the view:
<%= subscribe_to "/messages/#{#group_id}" %>
in the controller
PrivatePub.publish_to("/messages/#{#group_id}", "alert('test')")
this works just fine, however I would like to have something more sophisticated than an alert as a response (such as a gritter notification), so instead:
PrivatePub.publish_to("/messages/#{#group_id}", data: #some_data)
Following the tutorial, they use coffeescript for this. However, I cannot get the simple alert going (probably due to the id in the channel)
In this question, the OP was able to solve this using a js.erb view. But I can't get it to work.
disclaimer: my js and coffeescript knowledge is almost zero.
Any help is appreciated :)
EDIT
Some more info: I've a method in a controller that's part of a public API, and expects POST request. If everything is ok it sends out a JSON success response. Aside from this, the same method sends a notification to all users of a specific group.
I've actually managed to get this working, putting this in the controller:
callback method:
respond_to do |format|
format.js #-> calls callback.js.erb
#format.json { render json: {"success" => true}.to_json }
end
and putting the gritter stuff in my_api_controller/callback.js.erb:
<% publish_to "/messages/#{#group_id}" do %>
<%= add_gritter(
"Nova " + link_to("reserva", reservation_path(#r)) + " de #{#channel} para " +
link_to(#hostel_name, hostel_path(#hostel_id)),
:title => "Nova reserva!",
:sticky => true,
:image => :notice
) %>
<% end %>
note: since the subscription to the channel is done in every view (through the layout), you can receive a notification on any page/view
My problem at the momento is, as you can guess, the JSON response. Since I cant render two responses, only the js.erb is called, but the JSON response is never sent
Although I've not got much experience with this gem, here's something which may help:
JS
Client-side, your JS is basically running an eventlistener on the private_pub object (defined when you include the private_pub / gritter JS on your page), which you can use to perform other actions (call alerts, append data to page, etc)
It seems your back-end is working, it's just the receipt & processing of the data from the server you're struggling with. To remedy this, you can do 2 things: 1) run a standard JS call from application.js or run a js file from your controller action:
Controller
According to the private_pub documentation, you should do this to create a JS file:
#app/controllers/your_controller.rb
def create
#message = "Hello"
respond_to do |format|
format.html { PrivatePub.publish_to("/messages/#{#group_id}", "alert('test')") }
format.js #-> calls create.js.erb
end
end
#app/views/your_controller/create.js.erb
<% publish_to "/messages/new" do %>
$("#chat").append("<%= j render(#messages) %>");
<% end %>
Browser
#app/assets/javascripts/application.js.coffee
PrivatePub.subscribe("/messages/new", (data, channel) ->
alert data.message.content
I was able to accomplish this by directly adding the gritter script in the publish_to method of Privat pub.
In my controller:
PrivatePub.publish_to
"/some/URI/#{entity.id}"
,"jQuery.gritter.add({
image: '#{ActionController::Base.helpers.asset_path('notice.png')}'
, sticky: true
,title:'#{t('some_title')}'
, text: '#{t('some text'}'
});"
render json: {"error"=>{"code"=>20,"msg"=>e.message},"success" => false}.to_json
Basically, I was able to publish to PrivatePub witouth resorting to the html response, wich enabled me to return a JSON response as intended.
I guess, you can handle your problem with the help of gon gem like below:
In view
<%= subscribe_to "/messages/#{#group_id}" %>
In controller
gon.group_id = #group_id
PrivatePub.publish_to("/messages/#{#group_id}", message: #message)
In messages.coffee
if gon.group_id
PrivatePub.subscribe "/messages/#{gon.group_id}", (data, channel) ->
jQuery.gritter.add
image: '/assets/notice.png'
title: 'Notification!'
text: data.message.content
But, gon.group_id can make trouble sometimes so you need to take care of that.
So, I recommend to use js.erb which is easy and we can access to controller's variable easily in js.erb file.
Hope that answer your problem.

How to change the response format depending on the data sent?

I'm doing some controllers to render reports and here is my problem:
The user open a page with a form which let it change the download format and the date of the report.
The download format is set trough a select input.
When the user press the button I want to response depending on the selected format.
The problem is that it's specified trough the url. So trying to do something like:
case format
when "xlsx" then format.xlsx{...}
when "html" then format.html{...}
...
end
doesn't work because rails or the browser (I'm not sure) expects an html response.
I've though of two options:
Change the url of the form onsubmit which makes the application more dependent on javascript. Or.
redirect_to url + ".#{params[:download_format]}"
The second way looks better to me but I need to pass the :report_date in the url and I can't find the way to do it.
I've tried this:
url = my_custom_url_path
redirect_to url + ".#{params[:download_format]}", :date_format => params[:date_format]
But it's not working.
In the form:
<%= f.select :download_format, { "xlsx" => "xlsx, "Online" => "html" } %>
In the controller:
def action
if download_format = params[:download_format].delete
redirect_to my_action_path(options.merge( :format => download_format ) ) and return
end
# do some logic here
respond_to do |format|
format.xlsx{...}
format.html{...}
end
end

button to save current page in rails 3.2

I need to have a button to save the current web site (just like clicking on "Save as"), I created a method in the controller which works great for any external site (like http://www.google.com) but doesn't work for the sites inside my application, I get a timeout error!. This has no explanation to me :(
Any clue what is the issue?
#CONTROLLER FILE
def save_current_page
# => Using MECHANIZE
agent = Mechanize.new
page = agent.get request.referer
send_data(page.content, :filename => "filename.txt")
end
I tried also Open URI, same problem!
#CONTROLLER FILE
def save_current_page
# => USANDO OPEN URI
send_data(open(request.referer).read, :filename => "filename.txt")
end
I'm using rails 3.2 and ruby 1.9, any help is appreciated, I already spent like 10 hours trying to make it work!!
Rails can only handle one request at a time. It's a never-ending standoff between the two requests - the first request is waiting for the second request, but the second request is waiting for the first request, and therefore you get a Timeout error. Even if you're running multiple instances of the app with Passenger or something, it's a bad idea.
The only way I can think to get around it would be to use conditional statements like so:
referer = URI.parse(request.referer)
if Rails.application.config.default_url_options[:host] == referer.host
content = "via yoursite.com"
else
agent = Mechanize.new
page = agent.get request.referer
content = page.content
end
send_data content, filename: "filename.txt"
A little dirty but it should get around the Timeout problem. As far a getting the actual content of a page from your own site - that's up to you. You could either render the template, grab something from cache, or just ignore it.
A much better solution would be to enqueue this code into something like Resque or Delayed Job. Then the queue could make the request and wait in line to request the page like normal. It would also mean that the user wouldn't have to wait while your application make a remote request, which is dangerous because who knows how long the page will take to respond.
After several hours and lots of other posts I got to a final solution:
Bricker is right in that it is not possible for rails to render more than once in a call, as taken from http://guides.rubyonrails.org/layouts_and_rendering.html "Can only render or redirect once per action"
The site also states "The rule is that if you do not explicitly render something at the end of a controller action, Rails will automatically look for the action_name.html.erb template in the controller’s view path and render it."
Then, the solution that worked great for me was to tell the controller to render to a string if a download flag (download=true) was set in :params (I also use request.url to have it working from any view in my application)
View:
= link_to 'Download', request.url+"&downloadexcel=true", :class => 'btn btn-primary btn-block'
Controller:
def acontrolleraction
#some controller code here
if params[:downloadexcel]
save_page_xls
else
# render normally
end
end
def save_page_xls
#TRESCLOUD - we create a proper name for the file
path = URI(request.referer).path.gsub(/[^0-9a-z]/i, '')
query = URI(request.referer).query.gsub(/[^0-9a-z]/i, '')
filename = #project_data['NOMBRE']+"_"+path+"_"+query+".xls"
#TRESCLOUD - we render the page into a variable and process it
page = render_to_string
#TRESCLOUD - we send the file for download!
send_data(page, :filename => filename, :type => "application/xls")
end
Thanks for your tips!

How do I create a temp file and write to it then allow users to download it?

I'm working on my first application and I need some help with allowing my users to download a text file with certain variables that are being displayed on the page.
Take a shopping list for example.
Let's say you allow your users to create a shopping list of products, and then display the shopping list with the items on a shopping list page,
e.g. localhost:3000/list/my-list
Take a look at the example code below (which is probably incorrect):
File.open('shopping_list.txt', 'w') do |file|
file.puts 'Item 1: #{product_1.name}'
file.puts 'Item 2: #{product_2.name}'
file.puts 'Item 3: #{product_3.name}'
end
Which then creates a text file that has the following content:
Item 1: Eggs
Item 2: Butter
Item 3: Bread
Users should then be able to download this file (i don't want this file to be stored on the server) via a download link.
I have no idea how to achieve this, but I'm hoping you guys can guide me. :D
TL;DR
create text files populated with model data (perhaps create a method to achieve this?)
text files should not be stored on the server, but created as users click the download button (not sure if this is the rails way but perhaps someone could show me a better way)
I am assuming there is a resource for List with the attribute name as the name of the list and a list has_many Item which has an attribute description
First off, create a download path change your routes config/routes.rb
resources :lists do
member {get "download"}
end
Now if you run a rake routes in the console you should see a route like
/lists/:id/download
Whats more you should now have the helpers download_list_url & download_list_path to use in your view like
<ul>
<% #lists.each do |list| %>
<li> <%= list.name %> - <%= link_to 'Download List', download_list_path(list) %> </li>
<% end %>
</ul>
In your lists_controller add the action, and as you dont actually want to keep the file on the server disk just stream the data as a string
def download
list = List.find(params[:id])
send_data list.as_file,
:filename => "#{list.name}.txt",
:type => "text/plain"
end
Finally you see I have used a as_file method which you should add to the model (I prefer not to do this stuff in controllers, fat models, skinny controllers). So in the List model
def as_file
output = [self.name]
self.items.each {|item| output << item.description }
output.join("\n")
end
You say you don't want to store the file on the server, but "download" it on request; this sounds like you just want to generate and deliver a text document in response to the download link. There are several approaches, but you want to be sure of setting the mime-type so the browser sees it as a text file instead of an html document.
product_info = [
"Item 1: #{product_1.name}",
"Item 2: #{product_2.name}",
"Item 3: #{product_3.name}",
].join("\n")
render :text => product_info # implies :content_type => Mime::Type["text/plain"]
BTW, your example with open/puts above won't output what you think since single-quoted strings don't interpolate.
so, you wish to :
create text files populated with model data (perhaps create a method
to achieve this?)
text files should not be stored on the server, but
created as users click the download button (not sure if this is the
rails way but perhaps someone could show me a better way)
You have the right idea, here's what to do :
Create a method in your model to generate the text file contents. Let's say this method is called list_data
It seems like you have an existing controller action called my_list. Hence we can call our new method in the controller like so :
.
def my_list
# pre-existing code
respond_to do |format|
format.html # show html page as before
format.text do
send_data #list.list_data, :content_type => 'text/plain', :filename => 'my-shopping-list.txt'
end
end
end
To link to the download, just use link_to :action => my_list, :format => 'text'
See http://api.rubyonrails.org/classes/ActionController/DataStreaming.html#method-i-send_data for full docs on send_data
Caveat & explanations : Using the method above, there isn't really an explicit creation of files, Rails is streaming it for you. Hence this method is not suitable for very large files, or when the generation of the file content will take a while. Use a delayed method to generate the file and store it - the file contents somewhere if that's the case - but we can use send_data once it has been generated
You could try a combination of TempFile and send_file. In your controller action ..
file = Tempfile.new('foo')
file.write("hello world")
file.close
send_file file.path
At Rails 2.3 you can use Template Streaming. Working with Redmine I can remember something like that, you have to adapt for your case. Reference: Streaming and file downloads
require "prawn"
class ClientsController < ApplicationController
# Generate a PDF document with information on the client and return it.
# The user will get the PDF as a file download.
def download_pdf
client = Client.find(params[:id])
send_data(generate_pdf, :filename => "#{client.name}.pdf", :type => "application/pdf")
end
private
def generate_pdf(client)
Prawn::Document.new do
text client.name, :align => :center
text "Address: #{client.address}"
text "Email: #{client.email}"
end.render
end
end
Using the Thong Kuah you must just change the "content_type" param:
def my_list
# pre-existing code
respond_to do |format|
format.html # show html page as before
format.text do
send_data #list.list_data, :content_type => 'text/plain', :filename => 'my-shopping-list.txt'
end
end
end

Resources