How to save a rendered view as a static file? - ruby-on-rails

My Rails 2.3 app generates a page in HTML/CSS or as a word doc. I'd like to save that html file to the filesystem as a static file (ie. filename.html or filename.doc). I plan to have a preview action w/ the fully rendered page and a 'save report' button. Our users will access those static files later. (I'll save the path to the db.)
Any suggestions for how to do this?
I'm as far as creating a file and saving it, but I'm not sure how to get my rendered view into it. Bonus points if anyone knows how to save it up to S3! Many thanks!

render_to_string is your friend. One you have it in a string, burn it to file in the usual way.
class FooController
def save_foo_to_disk
data = render_to_string( :action => :index )
File.open(file_path, "w"){|f| f << data }
flash[:notice] = "saved to #{file_path}"
end
end
As far as S3 goes, see the aws-s3 gem. It seem to do what you are after. Usage is a little like this.
AWS::S3::Base.establish_connection!(
:access_key_id => 'abc',
:secret_access_key => '123'
)
S3Object.store(file_name, data, 'bucket-name')
Have fun, and don't run with scissors.

Another way is adding an after_action to the controller, and in that action using response.body to access rendered content. In this way, your controller can respond to client as normal, save rendered content to database in meanwhile.

Related

Can't add parameters to the picture model with the use of Ckeditor, Paperclip and Ruby on Rails

I have a CKeditor picture model like this:
class Ckeditor::Picture < Ckeditor::Asset
before_save :set_vars
has_attached_file :data,
:url => "/ckeditor_assets/pictures/:id/:style_:basename.:extension",
:path => ":rails_root/public/ckeditor_assets/pictures/:id/:style_:basename.:extension",
:styles => { :content => '600>',:medium => '300x300', :quintet => '150x150', :thumb => '118x100#' }
validates_attachment_size :data, :less_than => 2.megabytes
validates_attachment_presence :data
def url_content
url(:content)
end
protected
def set_vars
#self.assetable_id = id
#self.assetable_type = controller_name
end
end
What I want is that the 'assetable_id' and 'assetable_type' are being filled during the creation of a new picture (because there are columns for those in the database table). And I want to pass them variables. Like the id from the post/event/user that the picture is linked to. And ofcourse the type of 'model' the picture is assigned to - again post/event/user.
I don't know if this is the right solution but I don't know how else to fix it. Documentation online about the CKeditor gem, the config settings and Rails is horrible - I'm searching for hours and hours and I can't find a single thing that closely resembles what I want - so please help.
I know how to adjust the parameters for the upload but none of them seem to do something that I want:
Started POST "/ckeditor/pictures?CKEditor=post%5B14%5D&CKEditorFuncNum=1&
langCode=en&%3Aassetable-id=0&assetable_type=post&
authenticity_token=fsKA68sxkzQpiSMmtcP782i4oI%2FA6KSIsSZuwO5zDWA%3D" for 127.0.0.1 at
2013-04-15 10:05:06 +0200
Processing by Ckeditor::PicturesController#create as HTML
Parameters: {"upload"=>#<ActionDispatch::Http::UploadedFile:0x007f94f80beef8
#original_filename="kb_new.png", #content_type="image/png", #headers="Content-
Disposition: form-data; name=\"upload\"; filename=\"kb_new.png\"\r\nContent-Type:
image/png\r\n", #tempfile=#<File:/tmp/RackMultipart20130415-3130-1ls814r>>,
"CKEditor"=>"post[14]", "CKEditorFuncNum"=>"1", "langCode"=>"en", ":assetable-id"=>"0",
"assetable_type"=>"post",
"authenticity_token"=>"fsKA68sxkzQpiSMmtcP782i4oI/A6KSIsSZuwO5zDWA="}
User Load (0.2ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
[ALL THE FORMATING STUFF]
SQL (0.6ms) INSERT INTO `ckeditor_assets` (`assetable_id`, `assetable_type`, `created_at`, `data_content_type`, `data_file_name`, `data_file_size`, `height`, `type`, `updated_at`, `width`) VALUES (NULL, '0', '2013-04-15 08:05:07', 'image/png', 'kb_new.png', 291770, 419, 'Ckeditor::Picture', '2013-04-15 08:05:07', 450)
[paperclip] Saving attachments.
(62.5ms) COMMIT
Rendered text template (0.0ms)
Completed 200 OK in 929ms (Views: 0.5ms | ActiveRecord: 63.6ms)
Also check out this question:
How to set the size of an image within CKeditor in Ruby On Rails according to the picture model?
I actually figured this out and have a solution that works. I too tried googling everything and found literally ZERO information about how to add custom params to a Ckeditor upload within Rails.
Here's what I did:
Let's start with the form itself. With Ckeditor, everything is driven by the URL. So when you click the image icon, and the pop-up window appears, and then you click the BROWSE SERVER button, you'll notice it opens a new pop-up which has a URL. This is the 1st URL you need to modify. Then, when you actually upload an image, it will send a post request to another URL. That's the 2nd URL you need to modify.
By default, these URL's are set to the "index" and "create" actions on the Ckeditor::PicturesController. Here's a link to this controller file in the gem's repo, you will need this later - https://github.com/galetahub/ckeditor/blob/2dd963fae117726976e5bf1dc264e27d3741fea8/app/controllers/ckeditor/pictures_controller.rb
So logically, what we need to do is modify these URL's so that we can add our own custom code to control what happens. So here's what I did to do that (notice the "ckeditor" hash):
<%= cktext_area_tag 'column1_body', #section,
:ckeditor => {
filebrowserImageUploadUrl: "/ckeditor/pictures?website_id=#{#website.id}",
filebrowserImageBrowseUrl: "/picture_overrides"
} %>
cool.
2.So first I modified the filebrowserImageBrowseUrl with '/picture_overrides'. This means that when someone clicks the BROWSE SERVER button, it will call the 'index' action on my PictureOverridesController. So I added the route, created the controller file, and views.
For the views, I copied the view file exactly from the gem, which is here - https://github.com/galetahub/ckeditor/blob/master/app/views/ckeditor/pictures/index.html.erb . Note: there is a partial in there, so you'll need to grab that too.
Now on my new 'PictureOverridesController' file, I edited the 'index' action as follows:
def index
# I need to find the website
#website = Website.find_by_subdomain(request.subdomain)
# I can scope #pictures to only contain the images the user should see.
#pictures = Ckeditor::Picture.where(website_id: #website.id)
end
Then, some other private methods are called during this process, so you need to add the following code to this controller file as well (just cut & paste). You'll notice I grabbed this code from the same controller on the gem file.
protected
def find_asset
#picture = Ckeditor.picture_adapter.get!(params[:id])
end
def authorize_resource
model = (#picture || Ckeditor.picture_model)
#authorization_adapter.try(:authorize, params[:action], model)
end
With this, now my users will only see the images that have the correct website_id.
Now, I need to make sure the website_id is set when a user uploads an image through Ckeditor.
3.While you could certainly modify the create action on this controller file, and set the URL to this, I chose a different way. I simply added the website_id to the URL, as you could see above. Unfortunately, the Ckeditor::Picture instance wont save this automatically. So I decided to accomplish this with an after_filter in the ApplicationController, like so:
after_filter :ckeditor_add_website_id
def ckeditor_add_website_id
if params[:controller] == 'ckeditor/pictures' && params[:action] == 'create'
#website = find_website_by_domain_subdomain(request)
#picture.update_attributes(website_id: #website.id)
end
end
It just works.
Lastly -- DONT FORGET to add the custom URL's to any form where you are utilizing Ckeditor.
In my opinion the simplest solution:
#controllers/application_controller.rb
protected
def ckeditor_pictures_scope(options = { :assetable_id => "#{current_page.id}" ,:assetable_type => "Page" })
ckeditor_filebrowser_scope(options)
end
def ckeditor_attachment_files_scope(options = { :assetable_id => "#{current_page.id}" ,:assetable_type => "Page" })
ckeditor_filebrowser_scope(options)
end
def ckeditor_before_create_asset(asset)
asset.assetable = current_page if current_page
return true
end
Thank you to nfriend21 and to wacaw for their solutions.
This problem have distressed me for a week!
The solution of wacaw is the simplest and cleanest, but I am inspired by nfriend21 to complete it. It can be useful for inexperienced like me.
I have a page with many editors, each with a different ID (named column_id).
I want that the column_id is saved as asset_id of type Column (object defined in my application).
1) I change the urls for all the editors in the page:
for (instance in CKEDITOR.instances) {
var editor = CKEDITOR.instances[instance];
var id = editor.name.substr(6);
if(editor) {
editor.config.filebrowserImageBrowseUrl = "/ckeditor/pictures?column_id=" + id;
editor.config.filebrowserImageUploadUrl = "/ckeditor/pictures?column_id=" + id;
}
}
note that I extract the id of the editor as a substring of the ID of the div containing it... i.e. editor134, editor245, etc.: i remove first 6 characters.
Note also I use the original PicturesController of ckeditor's gem.
2) now, modifying a bit the wacav solution, in ApplicationController:
#controllers/application_controller.rb
protected
def ckeditor_pictures_scope(options = { :assetable_id => params[:column_id], :assetable_type => "Column"})
ckeditor_filebrowser_scope(options)
end
# to be modified: at this moment I'm interested in pictures only
def ckeditor_attachment_files_scope(options = { :assetable_id => "#{current_page.id}" ,:assetable_type => "Page" })
ckeditor_filebrowser_scope(options)
end
def ckeditor_before_create_asset(asset)
asset.assetable = Column.find(params[:column_id])
return true
end
I hope it can be helpful to others.
What you are searching for is using polymorphic Picture model, which will belong to many other models.
Please, read down here http://rails-bestpractices.com/posts/45-use-sti-and-polymorphic-model-for-multiple-uploads , as it's pretty well described.

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 to upload an image to S3 using paperclip gem

For the life of my I can't understand how the basic paperclip example works. There's only one line included in the controller, and that's
#user = User.create( params[:user] )
I simply don't understand how that's all that is needed to upload an image to s3. I've changed the example quite a bit because i wanted to use jquery file uploader rather than the default rails form helper, so I'm at the point where an image is being POSTed to my controller, but I can't figure out how I'm supposed to take the image from the params and assign it as an attachment. Here's what I'm seeing the logs:
Parameters: {"files"=>[#<ActionDispatch::Http::UploadedFile:0x132263b98 #tempfile=#<File:/var/folders/5d/6r3qnvmx0754lr5t13_y1vd80000gn/T/RackMultipart20120329-71039-1b1ewde-0>, #headers="Content-Disposition: form-data; name=\"files[]\"; filename=\"background.png\"\r\nContent-Type: image/png\r\n", #content_type="image/png", #original_filename="background.png">], "id"=>"385"}
My JS is very simple:
` $('#fileupload').fileupload({
dataType: 'json',
url: '/my_url',
done: function (e, data) {
console.log('done');
}
});`
What would be helpful for me to know is how I can strip the file data from the POSTed parameters given above and pass it to paperclip. I'm sure that I'll have to assign the attachment attribute a value of File.open(...), but I dont know what source of my file is.
I've spent a ridiculous amount of time trying to figure this out and I can't seem to get it. I've tried uploading directly to s3, but the chain of events was terribly confusing, so I want to get this simple pass-through example completed first. Thanks so much for any help you cna give!
You need a few more pieces and it will help if you can show the exact code you're using.
Paperclip can post to S3 by using:
http://rubydoc.info/gems/paperclip/Paperclip/Storage/S3
When your controller creates a User model, it is sending along all the params. This is called "mass assignment" (be sure to read about attr_accessible).
When your model receives the params, it uses the Paperclip AWS processor, which uploads it.
You need the AWS gem, a valid bucket on S3, and a config file.
Try this blog post and let us know if it helps you:
http://blog.trydionel.com/2009/11/08/using-paperclip-with-amazon-s3/
UPDATE 2013-04-03: Pleases see Chloe's comment below-- you may need an additional parameter, and the blog post may be outdated.
If you want to do it manually, approach it like this:
# In order to get contents of the POST request with the photo,
# you need to read contents of request
upload = params[:file].is_a(String)
file_name = upload ? params[:file] : params[:file].original_filename
extension = file_name.split('.').last
# We have to create a temp file which is going to be used by Paperclip for
# its upload
tmp_file = "#{Rails.root}/tmp/file.#{extension}"
file_id = 0
# Check if file with the name exists and generate unique path
while File.exists?(tmp_file) do
tmp_file_path = "#{Rails.root}/tmp/file#{file_id}.#{extension}"
id += 1
end
# Let's write the file from post request to unique location
File.open(tmp_file_path, 'wb') do |f|
if upload
f.write request.body.read
else
f.write params[:file].read
end
end
# Now that file is saved in temp location, we can use Paperclip to mimic one file
# upload
#photo = Photo.new :photo => File.open(tmp_file_path)
# We'll return javascript to say that the file is uploaded and put its thumbnail in
# HTML or whatever else you wanted to do with it
respond_to do |format|
if #photo.save
render :text => "Success"
else
render :text => #photo.errors
end
end
You can rewrite your create or whatever you use as the url to which you are POSTing the form.
This bit:
"files"=>[#<ActionDispatch::Http::UploadedFile:0x132263b98 #tempfile=# <File:/var/folders/5d/6r3qnvmx0754lr5t13_y1vd80000gn/T/RackMultipart20120329-71039-1b1ewde-0>
is the part (I think) that holds the file contents that are posted in the form.
In Rails, the User model will have a helper: has_attached_file
Passing the [:params] to the User.create method allows that helper to pick up the file contents, do any processing on them (eg resizing etc based on attributes supplied to the helper) and then push the image(s) to your storage (eg S3 or whatever - S3 credentials are passed to the helper).
Hopefully that explains the 'how does it do it?' question
re the jQuery bit.. not sure what the code should be there, but why not use the Rails form with :remote => true and handle the response in jquery?

rails how to render a file with correct filename

This is tough one to explain so i'll try my best, and hopefully edit the question if people need more information. I am not providing exact code, but merely an example of the issue.
I am using rails 2.3.8. I am on Unix.
I have a bunch of files under a directory not Apache accessible. (i.e. /data/files/file.rpk)
I have the following in my view.
link_to "RPK File", :controller => 'mycontroller', :action=> 'myaction', :file => '/data/files/file.rpk'
I have the following in my controller.
def myaction
if FileTest.exists?(params[:file])
render :file => params[:file]
end
end
When i select the link on the page i get a download prompt for my desired file, but the name of the file is "myaction" instead of the filename.
Thoughts on how i could get it named correctly?
Sounds like a job for send_file. The x_sendfile option prevents that your workers keep busy while transferring the actual file. You can read more about that in this blogpost.
send_file path_to_file_on_filesystem, :type => "application/zip", :x_sendfile => true
You want to use send_data with the :filename option. See the API documentation.
You want to be extremely careful with this, though. Never ever trust the client/user! They will send file=../../../../etc/group or something in order to read arbitrary files on your system, so be very sure to sanitize that value before passing it to any file-reading methods.

Saving XML files with Rails

im working on a Rails project that should create XMl files, or to be more specific
use existing XMl templates and put content from the database in it.
So i dont need to create the xml structure, basically just rendering a template with content.
What would be the smartest way to do that?
So far i have a file.xml.erb in my layout folder
and i have a custom route "/renderXML" that does
def renderXML
#reading_question = ReadingQuestion.find(params[:id])
render :file => 'layouts/question.xml'
end
This works, but i also want to save the file, not only show it (actually viewing it is not really needed).
For saving i found this
File.open('fixed.xml','w'){|f| f.write builder.to_xml}
How do i access the rendered file and save it with some method like above?
Perhaps something like:
s = render_to_string :file => 'layouts/question.xml'
File.open('fixed.xml','w'){|f| f.write s}
render :text => s
Another approach :
send_data fixed, :type => 'text/xml; charset=UTF-8;', :disposition =>
"attachment; filename=fixed.xml"

Resources