Dynamic link to download file from S3 in Rails - ruby-on-rails

I have a message controller/model/view, where user can send message to each other. I have added an attachment column for user to upload files when creating their message. I would like the recipient of the message to be able to download the attached file from S3 to his/her default download folder.
I have read several post, but none of them seem to address the dynamic angle.
Below is where I am now, in my controller:
def download_url(attachment_file_name)
s3 = AWS::S3.new.buckets[Rails.application.secrets.s3_bucket_name] # This can be done elsewhere as well,
# e.g config/environments/development.rb
url_options = {
expires_in: 60.minutes,
use_ssl: true,
response_content_disposition: "attachment; filename=\"#{attachment_file_name}\""
}
s3.objects[ self.path ].url_for( :read, url_options ).to_s
end
and in the view:
<%= link_to 'Download attachment', download_url(#message.attachment) %>
But this returns the error:
ActionView::Template::Error (undefined method `download_url' for #<#<Class:0x00007fee04a12648>:0x00007fedf5c1d4d8>):
Which kind of makes sense. Does anyone knows how to do this please, i am surprise something that simple gets complex to me. help please - 4 hours in :(

Related

Upload file to S3 from rails server

I would like to upload a file to an Amazon S3 bucket from a rails app. The file comes via a user uploading it. I am having difficulty finding the proper documentation for this because of the different versions of the aws-sdk. Additionally, the acl: :public_read is intentional. I only want authorized users uploading, but anyone should be able to access it.
My current problem is that I am unable to index into a lazy loaded collection. Google provides no useful information for how to not load it lazily.
Here is my relevant code. Note that the controller actions for new and show are empty for now.
messages_controller.rb
def create
# Creates the file object
obj = $S3.bucket(TEST_BUCKET).objects[params[:file].original_filename]
# Uploads the file
obj.write(
file: params[:file],
acl: :public_read
)
end
new.html.erb
<h1>Upload a file</h1>
<%= form_tag messages_path, enctype: 'multipart/form-data' do %>
<%= file_field_tag :file %>
<%= submit_tag 'Upload file' %>
<% end %>
I am using restful routes so the only relevant routing info is resources :messages
I am running rails 6.0.0beta, not that it should matter. I am using version 3 of the aws-sdk gem.
I ended up finding the solution sort of. I do not know how to solve the lazy loading problem but there is a better way.
In the create controller action
def create
obj = $S3.bucket(TEST_BUCKET).object('key_name')
obj.put(body: params[:file].to_io)
end
It'll take some more investigation to apply the attributes to it that I want but this should help anyone else scouring the 3! different versions of the sdk documentation.
The .to_io method on the file is required to get access to the actual file that is in the params[:file] object.

Rails 4 Mailer, Amazon S3, and inline images

I have a Rails 4 application hosted on Heroku that serves assets from an S3 bucket. I am trying to customize my mailer (in this case, a customization of the Devise mailer) so that I can embed inline images into my emails.
Per the Rails documentation, the mailer should include code such as the following:
def confirmation_instructions(record, token, opts={})
# Prepare image for embedding
attachments.inline['logo'] = File.read("#{Rails.root}/app/assets/images/logo.jpg")
# Allow Devise to do its thing
super
end
And the view should contain the following:
<%= image_tag attachments['logo'].url, :style => "my styling here" %>
On Heroku, this fails with the following log:
ActionView::Template::Error (undefined method `url' for nil:NilClass):
"my styling here" %>
In other words, it looks like attachments.inline['logo'] is returning nil, and the view is then calling .url on nil.
I've tried numerous fixes and at this point am pretty exasperated. I know it must be something simple I'm overlooking and I hope somebody out there can point out where I'm going wrong.
Thanks in advance.
Try this:
File.read(Rails.root.join("app/assets/images/logo.jpg")
That's how I got it to work.

Allowing client to download files from ftp rails

I'm developping a web application in rails 4 and I'm currenty faced with a tiny issue.
I want to make the users of the website to be able to download files from a ftp by clicking on a link. I've decided to go on this:
def download
#item=Item.find(params[:id])
#item.dl_count += 1
#item.save
url = #item.file_url.to_s
redirect_to url and return
end
And, very basically, this in my view:
<%= link_to 'DL', controller: "items", action: "download"%>
However, I'm not quite satisfied by this, as it generates a few mistake like the fact that clicking the link create two GET methods, one responding by 403 Forbidden and the next with a 302 found...
Do you have any idea about how I could improve this?
In Rails you should do:
def download
#item=Item.find(params[:id])
#item.dl_count += 1
#item.save
url = #item.file_url.to_s
send_file url, type: 'image/jpeg', disposition: 'inline'
end
Take a look for more information http://apidock.com/rails/ActionController/DataStreaming/send_file
Note that send_file can send only from local file system.
If you need get file from remote source (should be secure location) like http://example.com/apps/uploads/tfm.zip and avoid store this file in server memory, you can first save file in #{RAILS_ROOT}/tmp/ or system /tmp and then send_file
data = open(url)
filename = "#{RAILS_ROOT}/tmp/my_temp_file"
File.open(filename, 'w') do |f|
f.write data.read
end
send_file filename, ...options...
If Rails can`t read file, you should check file permission

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?

How do I get the base URL (e.g. http://localhost:3000) of my Rails app?

I'm using Paperclip to allow users to attach things, and then I'm sending an email and wanting to attach the file to the email. I'm trying to read the file in and add it as an attachment, like so:
# models/touchpoint_mailer.rb
class TouchpointMailer < ActionMailer::Base
def notification_email(touchpoint)
recipients "me#myemail.com"
from "Touchpoint Customer Portal <portal#touchpointclients.com>"
content_type "multipart/alternative"
subject "New Touchpoint Request"
sent_on Time.now
body :touchpoint => touchpoint
# Add any attachments the user has included
touchpoint.assets.each do |asset|
attachment :content_type => asset.file_content_type,
:body => File.read(asset.url)
end
end
end
This gives me the following error No such file or directory - /system/files/7/original/image.png?1254497688 with the stack trace saying it's the call to File.read. When I visit the show.html.erb page, and click on the link to the image, which is something like http://localhost:3000/system/files/7/original/image.png?1254497688, the image is displayed fine.
How can I fix this problem?
Typically root_url should provide this.
File.read is expecting a file path, not a url though. If you are generating the images, you should call the image generating code and return the bytes of the generated image instead of calling File.read(…)
asset.url returns the URL to the file. This is usually /system/classname/xx/xx/style/filename.ext. You'd put this in an image_tag.
You want asset.path. It returns the full path to the file, which will usually be something like /home/username/railsapp/public/system/classname/xx/xx/style/filename.ext
HTH.
request.env["HTTP_HOST"]
I don't know why this one line of code is so elusive on the web. Seems like it should be up front and center.
as ZiggyTheHamster is saying: the asset.url is the generated url that would be used on webpages (which is why you're getting the unix-style directory slashes, as pointed out in the comments.)
asset.path should give you the OS-aware path to the file, but even that isn't needed with paperclip.
Paperclip::Attachment is already an IOStream.
You just need :body => asset like so:
touchpoint.assets.each do |asset|
attachment :content_type => asset.file_content_type,
:body => asset
end

Resources