My RoR app is on Heroku and active storage is configured correctly. However, when I'm in the app and fetch the uploaded document, the url is something like https://cremers.s3.eu-west-1.amazonaws.com/cx9xy0pmbieagvuw8a0vzcnfhvcc?response-content-disposition=inline%3B filename%3D"Digeste_9.1.pdf"%3B....
How to change this to a "normal" url, like https://www.cremers.fr/documents/digest_9.1.pdf ?
You can use a route/controller to act as a sort of proxy. I've done exactly this, below is roughly the code I used, edited for your specifics.
I did not test this with your settings, obviously, and I wasn't using ActiveStorage in my case, so you might need/want to adjust, but this should get you started:
# config/routes.rb
Rails.application.routes.draw do
get '/documents/:filename.:format.:compression', to: 'documents#show'
end
# app/controllers/documents_controller.rb
require 'open-uri'
class DocumentsController < ApplicationController
def show
bucket_name = 'cremers'
aws_region = 'eu-west-1'
filename = params[:filename]
s3_url = "https://s3-#{aws_region}.amazonaws.com/#{bucket_name}/#{filename}"
data = open(s3_url)
send_data data.read, type: data.content_type
end
end
Related
How can I get url of my has_one model attachment stored in active storage in my rails controller. So, that I would be able to send it as full link as api in json.
So far, I have tried following methods but each of them are giving various issues:
current_user.image.service_url ---- undefined method `service_url' for #<ActiveStorage::Attached::One:0x....
Rails.application.routes.url_helpers.rails_disk_blob_path(current_user.image, only_path: true), it gives me an output like:
"/rails/blobs/%23%3CActiveStorage::Attached::One:0x007f991c7b41b8%3E"
but this is not a url, right? I am not able to hit and get image on browser.
url_for ----
undefined method `active_storage_attachment_url' for #<Api::V1::UsersController:0x007f991c1eaa98
Use the method rails_blob_path for attachements in a controller and models
For example, if you need to assign a variable (e.g. cover_url) in a controller, first you should include url_helpers and after use method rails_blob_path with some parameters. You can do the same in any model, worker etc.
Complete example below:
class ApplicationController < ActionController::Base
include Rails.application.routes.url_helpers
def index
#event = Event.first
cover_url = rails_blob_path(#event.cover, disposition: "attachment", only_path: true)
end
end
Sometimes, e.g. an API needs to return the full url with host / protocol for the clients (e.g. mobile phones etc.). In this case, passing the host parameter to all of the rails_blob_url calls is repetitive and not DRY. Even, you might need different settings in dev/test/prod to make it work.
If you are using ActionMailer and have already configuring that host/protocol in the environments/*.rb you can reuse the setting with rails_blob_url or rails_representation_url.
# in your config/environments/*.rb you might be already configuring ActionMailer
config.action_mailer.default_url_options = { host: 'www.my-site.com', protocol: 'https' }
I would recommend just calling the full Rails.application.url_helpers.rails_blob_url instead of dumping at least 50 methods into your model class (depending on your routes.rb), when you only need 2.
class MyModel < ApplicationModel
has_one_attached :logo
# linking to a variant full url
def logo_medium_variant_url
variant = logo.variant(resize: "1600x200>")
Rails.application.routes.url_helpers.rails_representation_url(
variant,
Rails.application.config.action_mailer.default_url_options
)
end
# linking to a original blob full url
def logo_blob_url
Rails.application.routes.url_helpers.rails_blob_url(
logo.blob,
Rails.application.config.action_mailer.default_url_options
)
end
end
I didn't have used rails active storage but what i have read in documentation this might help you
Try rails_blob_url(model.image)
For more http://edgeguides.rubyonrails.org/active_storage_overview.html
I was able to view the image in the browser using the following:
<%= link_to image_tag(upload.variant(resize: "100x100")), upload %>
Where upload is an attached image.
Is there a way to change/set the filename on download?
Example: Jon Smith uploaded his headshot, and the filename is 4321431-small.jpg. On download, I'd like to rename the file to jon_smith__headshot.jpg.
View:
<%= url_for user.headshot_file %>
This url_for downloads the file from Amazon S3, but with the original filename.
What are my options here?
The built-in controller serves blobs with their stored filenames. You can implement a custom controller that serves them with a different filename:
class HeadshotsController < ApplicationController
before_action :set_user
def show
redirect_to #user.headshot.service_url(filename: filename)
end
private
def set_user
#user = User.find(params[:user_id])
end
def filename
ActiveStorage::Filename.new("#{user.name.parameterize(separator: "_")}__headshot#{user.headshot.filename.extension_with_delimiter}")
end
end
Starting with 5.2.0 RC2, you won’t need to pass an ActiveStorage::Filename; you can pass a String filename instead.
I know this was already answered, but I would like to add a second way of doing this. You could update your file name when the user object is saved. Using OP's example of the user model and the headshot_file field, this is how you could solve this:
# app/models/user.rb
after_save :set_filename
def set_filename
file.blob.update(filename: "ANYTHING_YOU_WANT.#{file.filename.extension}") if file.attached?
end
The approach of #GuilPejon will work. The problem with directly calling the service_url is:
It is short-lived (not recommended by rails team)
It will not work if the service is disk in development mode.
The reason it does not work for disk service is that disk service requires ActiveStorage::Current.host to be present for generating the URL. And ActiveStorage::Current.host gets set in app/controllers/active_storage/base_controller.rb, so it will be missing when service_url gets called.
ActiveStorage as of now gives one more way of accessing the URL of the attachments:
Using rails_blob_(url|path) (recommended way)
But if you use this, you can only provide content-disposition and not the filename.
If you see the config/routes.rb in the `ActiveStorage repo you will find the below code.
get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob
direct :rails_blob do |blob, options|
route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
end
and when you look into blobs_controller you will find the below code:
def show
expires_in ActiveStorage::Blob.service.url_expires_in
redirect_to #blob.service_url(disposition: params[:disposition])
end
So it is clear that in rails_blob_(url|path) you can only pass disposition and nothing more.
I haven't played with ActiveStorage yet, so this is kind of a shot in the dark.
Looking at the ActiveStorage source for the S3 service, it looks like you can specify the filename and disposition for the upload. From the guides it seems that you can use rails_blob_path to access the raw URL of the upload and pass these parameters. Therefor you might try:
rails_blob_url(user.headshot_file, filename: "jon_smith__headshot.jpg")
Using Rails 4.2.10
I would like to open image from URL thanks to mongoid papaerclip and open_uri
It perfectly works in 95% of use cases but some website send me a 404 when they see the user-agent of the request is Ruby.
The problem is with the lib paperclip=>
paperclip/io_adapters/uri_adapter.rb in download_content at line 48
def download_content
options = { read_timeout: Paperclip.options[:read_timeout] }.compact
open(#target, **options)
end
If I could add here an option it would be great but I don't think it's possible so I would like to add a default header with my user-agentto all request done by open_uri
Luckily for your use case there is no such thing as a class being closed against modification in ruby.
Add a patch to your rails app in an initializer. The structure is roughly as follows:
In config/initializers/some_arbitrary_name.rb
module UriAdapterPatch
def open(url, options)
# alter the objects however you want
super(altered_or_original_url, altered_or_original_options)
end
end
Paperclip::UriAdapter.prepend(UriAdapterPatch)
Solution for paperclip-3.5.4
module Paperclip
class UriAdapter < AbstractAdapter
def download_content
open(#target,"User-Agent" => "Your Custom User Agent")
end
end
end
# for example put it in config/initializers/paperclip_user_agent.rb
For other versions just write in project folder
gem which paperclip
and find in path from output file paperclip/io_adapters/uri_adapter.rb
There is function def download_content, it is your aim to rewrite
I want to store an image in my Rails app, hand out the direct URL for it and capture the requests for it.
I have a created a controller/action images_controller#show, a layout file with only yield and a view file with one single image_tag.
The rendered direct link to the image: https://example.com/assets/a-3883fc3a19a59c2cac8e30c827e81ec6f67538dd0213393d08a425bc800a06a3.jpg
How would I capture requests for this image as they don't pass the controller? My server logs only shows
Started GET "/assets/a-3883fc3a19a59c2cac8e30c827e81ec6f67538dd0213393d08a425bc800a06a3.jpg"
Since the "static" assets are placed in the web root of the web server they are served without Rails intervening in production.
In development config.serve_static_assets = true is used to get rails to serve assets so that the are compiled on demand. However that would be prohibitively slow in production.
There are two basic alternatives here and which to use depends on your use case:
1. Do it on the web server:
This is mostly applicable if uploading the images is a developer concern and you want metrics for technical or marketing reasons.
Both NGinx and Apache can generate metrics for the number of unique visits on a URI. Exactly how to do this depends on your web server / sass provider and the rest of infrastructure in place.
If you decide to go down this route I recommend you start a new question with the details of your setup.
https://www.digitalocean.com/community/tutorials/how-to-target-your-users-with-nginx-analytics-and-a-b-testing
https://devcenter.heroku.com/articles/shogun
2. Handle images as any other resource
If you intend have users upload images you and want tracking or any form of access control you would have the user upload images into a non public directory and serve them through a run of the mill rails controller.
Gems like paperclip or carrierwave can make this far easier.
# config/routes.rb
resources :images
# app/models/image.rb
class Image < ActiveRecord::Base
belongs_to :user
validates_presence_of :file
end
# app/controllers/images_controller.rb
class ImagesController < ApplicationController
def new
#image = Image.new
end
def create
#image = Image.new(image_params)
#image.save
respond_with(#image)
end
def index
#images = Image.all
end
def show
#images = Image.find(params[:id])
respond_to do |f|
format.html {}
format.jpg { send_file #image.file , type: 'image/jpeg', disposition: 'inline' }
end
end
# ...
end
I'm using jpegcam to allow a user to take a webcam photo to set as their profile photo. This library ends up posting the raw data to the sever which I get in my rails controller like so:
def ajax_photo_upload
# Rails.logger.info request.raw_post
#user = User.find(current_user.id)
#user.picture = File.new(request.raw_post)
This does not work and paperclip/rails fails when you try to save request.raw_post.
Errno::ENOENT (No such file or directory - ????JFIF???
I've seen solutions that make a temporary file but I'd be curious to know if there is a way to get Paperclip to automatically save the request.raw_post w/o having to make a tempfile. Any elegant ideas or solutions out there?
UGLY SOLUTION (Requires a temp file)
class ApiV1::UsersController < ApiV1::APIController
def create
File.open(upload_path, 'w:ASCII-8BIT') do |f|
f.write request.raw_post
end
current_user.photo = File.open(upload_path)
end
private
def upload_path # is used in upload and create
file_name = 'temp.jpg'
File.join(::Rails.root.to_s, 'public', 'temp', file_name)
end
end
This is ugly as it requires a temporary file to be saved on the server. Tips on how to make this happen w/o the temporary file needing to be saved? Can StringIO be used?
The problem with my previous solution was that the temp file was already closed and therefore could not be used by Paperclip anymore. The solution below works for me. It's IMO the cleanest way and (as per documentation) ensures your tempfiles are deleted after use.
Add the following method to your User model:
def set_picture(data)
temp_file = Tempfile.new(['temp', '.jpg'], :encoding => 'ascii-8bit')
begin
temp_file.write(data)
self.picture = temp_file # assumes has_attached_file :picture
ensure
temp_file.close
temp_file.unlink
end
end
Controller:
current_user.set_picture(request.raw_post)
current_user.save
Don't forget to add require 'tempfile' at the top of your User model file.