In ruby on rails project, I get a url from user and use below http request and then show the result to user. The code is show below:
problem.rb:
class Problem < ActiveRecord::Base
def content_length
uri = URI.parse("http://png-4.findicons.com/files/icons/1607/ruby_on_rails/256/ror_folder_256_v3.png")
response = Net::HTTP.start(uri.host, uri.port) { |http| http.request_head(uri.path)}
response["content-length"].to_i
end
end
I get the image of this url and show the size and the picture of url to user:
show.html.erb:
<p>
<strong>Image Size:</strong>
<%= number_to_human_size(#problem.content_length) %>
</p>
<p>
<strong>Image:</strong>
<%= image_tag(#problem.url) %>
</p>
Now, I save the url to the database, but I want save the image of this url in database too. How can save the image into database?
Problem model, have a paperclip method that is get the image from upload the image from below code:
<div class="field">
<%= f.label :photo %><br>
<%= f.file_field :photo %>
</div>
Can I save the image of URL to paperclip of problems? If yes, how can I do it?
As long as you are using Paperclip 3.1.4 or higher it should be as simple as setting up paperclip on your model:
class Problem
has_attached_file :image
end
And then assigning and saving:
def attach_picture_from_url(url)
#problem.image = URI.parse(url)
#problem.save!
end
With Paperclip 4 there are validations in place to ensure that someone doesn't spoof the content type. If the url that you are fetching is missing the correct extension (e.g. http://exmample.com/foo returns your jpeg) then you will receive an error about the extension not matching the detected content type. If this is a use-case for you then you can do something like this:
require 'open-uri'
def attach_picture_from_url(url)
image_handle = open url
raise "Not an image" unless image_handle.content_type.start_with? 'image/'
extension = image_handle.content_type.gsub 'image/', ''
temp_file = Tempfile.new ['image', extension]
temp_file.write image_handle.read
temp_file.close
#problem.image = temp_file
#problem_image.save!
ensure
temp_file.unlink if temp_file
end
Obviously this is more complicated, but it will ensure that the file will always have an extension that matches the content type.
Related
I'm using Rails 7 with Turbo. I'm trying to build a file upload mechanism - the idea is that user in the profile page could upload the file and after upload success, page should not be reloaded but it should show a green mark as a success confirmation.
My code:
# routes
kyc_document_upload POST /kyc_document_upload(.:format) profile#kyc_document_upload
# profile_controller.rb
class ProfileController < ApplicationController
def kyc_document_upload
#file = FileRead.read(params[:file])
body = Base64.encode64(#file)
ExternalApi.user.sent_missing_docs(body)
end
# views/profile/show.html.erb
<%= form_tag(kyc_document_upload_path, multipart: true) do %>
<%= file_field_tag "file" %>
<%= submit_tag %>
<% end %>
How to add turbo and green mark instead of redirection ?
I think it's not relevant but worth to mention that the file itself is not saved anywhere but is encoded into Base64 string and sends this file to an external service under the hood.
I need to get the path to the file on disk which is using ActiveStorage. The file is stored locally.
When I was using paperclip, I used the path method on the attachment which returned the full path.
Example:
user.avatar.path
While looking at the Active Storage Docs, it looked like rails_blob_path would do the trick. After looking at what it returned though, it does not provide the path to the document. Thus, it returns this error:
No such file or directory # rb_sysopen -
Background
I need the path to the document because I am using the combine_pdf gem in order to combine multiple pdfs into a single pdf.
For the paperclip implementation, I iterated through the full_paths of the selected pdf attachments and load them into the combined pdf:
attachment_paths.each {|att_path| report << CombinePDF.load(att_path)}
Use:
ActiveStorage::Blob.service.path_for(user.avatar.key)
You can do something like this on your model:
class User < ApplicationRecord
has_one_attached :avatar
def avatar_on_disk
ActiveStorage::Blob.service.path_for(avatar.key)
end
end
I'm not sure why all the other answers use send(:url_for, key). I'm using Rails 5.2.2 and path_for is a public method, therefore, it's way better to avoid send, or simply call path_for:
class User < ApplicationRecord
has_one_attached :avatar
def avatar_path
ActiveStorage::Blob.service.path_for(avatar.key)
end
end
Worth noting that in the view you can do things like this:
<p>
<%= image_tag url_for(#user.avatar) %>
<br>
<%= link_to 'View', polymorphic_url(#user.avatar) %>
<br>
Stored at <%= #user.image_path %>
<br>
<%= link_to 'Download', rails_blob_path(#user.avatar, disposition: :attachment) %>
<br>
<%= f.file_field :avatar %>
</p>
Thanks to the help of #muistooshort in the comments, after looking at the Active Storage Code, this works:
active_storage_disk_service = ActiveStorage::Service::DiskService.new(root: Rails.root.to_s + '/storage/')
active_storage_disk_service.send(:path_for, user.avatar.blob.key)
# => returns full path to the document stored locally on disk
This solution feels a bit hacky to me. I'd love to hear of other solutions. This does work for me though.
You can download the attachment to a local dir and then process it.
Supposing you have in your model:
has_one_attached :pdf_attachment
You can define:
def process_attachment
# Download the attached file in temp dir
pdf_attachment_path = "#{Dir.tmpdir}/#{pdf_attachment.filename}"
File.open(pdf_attachment_path, 'wb') do |file|
file.write(pdf_attachment.download)
end
# process the downloaded file
# ...
end
I have a form with multiple file uploads, The issue is when i am submitting the form and an validation error occurs, the file input field gets reset.
I basically wanted to persist those files inside the file input field for the complete process.
I have also gone through few links
How can I "keep" the uploaded image on a form validation error?
Please let me know what are the various options in such cases that one can follow.
Carrierwave is a great tool for handling file uploads and can handle this for you
https://github.com/jnicklas/carrierwave#making-uploads-work-across-form-redisplays
I took a completely different approach to the other solutions on offer here, as I didn't fancy switching to CarrierWave or using yet another gem to implement a hack to get around this.
Basically, I define placeholders for validation error messages and then make an AJAX call to the relevant controller. should it fail validation I simply populate the error message placeholders - this leaves everything in place client side including the file input ready for resubmission.
Example follows, demonstrating an organisation with nested address model and a nested logo model (that has a file attachment) - this has been cut for brevity :
organisations/_form.html.erb
<%= form_for #organisation, html: {class: 'form-horizontal', role: 'form', multipart: true}, remote: true do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<p class='name error_explanation'></p>
<%= f.fields_for :operational_address do |fa| %>
<%= fa.label :postcode %>
<%= fa.text_field :postcode %>
<p class='operational_address postcode error_explanation'></p>
<% end %>
<%= f.fields_for :logo do |fl| %>
<%= fl.file_field :image %>
<p class='logo image error_explanation'></p>
<% end %>
<% end %>
organisations_controller.rb
def create
if #organisation.save
render :js => "window.location = '#{organisations_path}'"
else
render :validation_errors
end
end
organisations/validation_errors.js.erb
$('.error_explanation').html('');
<% #organisation.errors.messages.each do |attribute, messages| %>
$('.<%= attribute %>.error_explanation').html("<%= messages.map{|message| "'#{message}'"}.join(', ') %>");
<% end %>
Created a repo with a example of using Paperclip on rails and mainting your files when validation error occurs
https://github.com/mariohmol/paperclip-keeponvalidation
I had to fix this on a recent project using the Paperclip Gem. It's a bit hacky but it works. I've tried calling cache_images() using after_validation and before_save in the model but it fails on create for some reason that I can't determine so I just call it from the controller instead. Hopefully this saves someone else some time!
model:
class Shop < ActiveRecord::Base
attr_accessor :logo_cache
has_attached_file :logo
def cache_images
if logo.staged?
if invalid?
FileUtils.cp(logo.queued_for_write[:original].path, logo.path(:original))
#logo_cache = encrypt(logo.path(:original))
end
else
if #logo_cache.present?
File.open(decrypt(#logo_cache)) {|f| assign_attributes(logo: f)}
end
end
end
private
def decrypt(data)
return '' unless data.present?
cipher = build_cipher(:decrypt, 'mypassword')
cipher.update(Base64.urlsafe_decode64(data).unpack('m')[0]) + cipher.final
end
def encrypt(data)
return '' unless data.present?
cipher = build_cipher(:encrypt, 'mypassword')
Base64.urlsafe_encode64([cipher.update(data) + cipher.final].pack('m'))
end
def build_cipher(type, password)
cipher = OpenSSL::Cipher::Cipher.new('DES-EDE3-CBC').send(type)
cipher.pkcs5_keyivgen(password)
cipher
end
end
controller:
def create
#shop = Shop.new(shop_params)
#shop.user = current_user
#shop.cache_images
if #shop.save
redirect_to account_path, notice: 'Shop created!'
else
render :new
end
end
def update
#shop = current_user.shop
#shop.assign_attributes(shop_params)
#shop.cache_images
if #shop.save
redirect_to account_path, notice: 'Shop updated.'
else
render :edit
end
end
view:
= f.file_field :logo
= f.hidden_field :logo_cache
- if #shop.logo.file?
%img{src: #shop.logo.url, alt: ''}
Well - I thought of taking a different approach to this; Instead of temporarily storing the file on the server, why not serve it back to the client to be resubmitted when the user fixes the validation issues.
This might still need a bit of refinement but it's the general concept:
# in the controller - save the file and its attributes to params
def create
# ...
if params[:doc] # a regular file uploaded through the file form element
# when the form re-renders, it will have those additional params available to it
params[:uploaded_file] = params[:doc].read # File contents
params[:uploaded_file_original_filename] = params[:doc].original_filename
params[:uploaded_file_headers] = params[:doc].headers
params[:uploaded_file_content_type] = params[:doc].content_type
elsif params[:uploaded_file] # a file coming through the form-resubmit
# generate an ActionDispatch::Http::UploadedFile
tempfile = Tempfile.new("#{params[:uploaded_file_original_filename]}-#{Time.now}")
tempfile.binmode
tempfile.write CGI.unescape(params[:uploaded_file]) #content of the file / unescaped
tempfile.close
# merge into the params
params.merge!(doc:
ActionDispatch::Http::UploadedFile.new(
:tempfile => tempfile,
:filename => params[:uploaded_file_original_filename],
:head => params[:uploaded_file_headers],
:type => params[:uploaded_file_content_type]
)
)
end
#...
# params (including the UploadedFile) can be used to generate and save the model object
end
# in the form (haml)
- if !params[:uploaded_file].blank?
# file contents in hidden textarea element
= text_area_tag(:uploaded_file, CGI.escape(params[:uploaded_file]), style: 'display: none;') #escape the file content
= hidden_field_tag :uploaded_file_headers, params[:uploaded_file_headers]
= hidden_field_tag :uploaded_file_content_type, params[:uploaded_file_content_type]
= hidden_field_tag :uploaded_file_original_filename, params[:uploaded_file_original_filename]
A workaround for this rather than an outright solution is to use client side validation so that the file isn't lost because the whole form persists.
The few users that don't have JavaScript enabled will lose the files between requests, but perhaps this % is so low for you as to make it an acceptable compromise. If this is the route you decide to go down I'd recommend this gem
https://github.com/bcardarella/client_side_validations
Which makes the whole process really simple and means you don't have to rewrite your validation in JavaScript
Browsers block against setting the value attribute on input of file
type for security reasons so that you can't upload a file without the
user's selected any file itself.
Pre-Populate HTML form file input
You can use carrierwave: https://github.com/carrierwaveuploader/carrierwave
Or validate the model via js request.
I found a way to keep files without using gems, it can probably be improved but I am still a young dev :)
I draw the whole thing from solution 2 contained in this article: https://medium.com/earthvectors/validations-and-file-caching-using-activestorage-e16418060f8f
First of all, you need to add an hidden_field within your form that contains the signed_id of the attachment:
<%= f.hidden_field :file_signed_id, value: #model.file.signed_id if #model.file.attached? %>
<%= f.input :file %>
The problem when validations fail (in my case), it keeps the file in memory but do not send anymore the ActionDispatch object as parameters. To override it, I did the following in my controller:
if file = params.dig(:model, :file)
blob = ActiveStorage::Blob.create_and_upload!(
io: File.open(file.tempfile),
filename: file.original_filename
)
#model.file.attach(blob)
elsif file_signed_id = params.dig(:model, file_signed_id)
blob = ActiveStorage::Blob.find_signed(file_signed_id)
#model.file.attach(blob)
end
You then can display your file when rendering your view again:
<%= link_to #model.file.filename, url_for(#model.file) if #model.file.attached? %>
The only problem I see with this workaround it is that it will create a blob object even if validations fail.
I hope it will help someone!
I am trying to upload a file to Box.com using its API REST call and the httmultiparty gem. The code is working and uploads to Box.com but does that after writing the uploaded file to the server file system as in f.write(data.read) then capturing the file path for the written file as the input parameter to the Box.com API REST call as in :filename => File.new(path). The app will be running on Heroku, so we can't save any files (read only) on Heroku's server so I would like to directly upload the file to Box.com while bypassing the writing of the file on the server but can't figure that out given that the Box.com REST call requires an object of type "File". Any help is appreciated. Thanks.
The model and view code is:
###
#The Model
###
class BoxUploader
require 'httmultiparty'
include HTTMultiParty
#base_uri 'https://api.box.com/2.0'
end
class File < ActiveRecord::Base
attr_accessible :file
attr_accessor :boxResponse
FILE_STORE = File.join Rails.root, 'public', 'files'
API_KEY = #myBoxApiKey
AUTH_TOKEN = #myBoxAuthToken
def file=(data) #uploaded file
filename = data.original_filename
path = File.join FILE_STORE, filename
#### would like to bypass the file writing step
File.open(path, "wb") do |f|
f.write(data.read)
end
#############
File.open(path, "wb") do |f|
boxResponse = BoxUploader.post('https://api.box.com/2.0/files/content',
:headers => { 'authorization' => 'BoxAuth api_key={API_KEY&auth_token=AUTH_TOKEN' },
:body => { :folder_id => '911', :filename => File.new(path)}
)
end
end
###
# The View
###
<!-- Invoke the Controller's "create" action -->
<h1>File Upload</h1>
<%= form_for #file, :html => {:multipart=>true} do |f| %>
<p>
<%= f.label :file %>
<%= f.file_field :file %>
</p>
<p>
<%= f.submit 'Create' %>
<% end %>
To upload a file from memory with HTTMultiParty, you need to supply it with an UploadIO object in place of the File object you'd normally give it. The UploadIO object can be populated using StringIO. It seems HTTMultiParty handles UploadIO objects in a special way, so you can't use the StringIO directly:
class Uploader
include HTTMultiParty
base_uri "http://foo.com"
end
string_io = StringIO.new('some stuff that pretends to be in a file')
upload_io = UploadIO.new(string_io, 'text/plain', 'bar.txt')
Uploader.post("/some/path", query: {file: upload_io})
You are aiming at a non-common use pattern, so your best shot could be to extend the existant gem, to provide the functionality you need.
There is a gem ruby-box to use with Box service at the 2.0 version of their API.
The gem is well supported and pretty easy to use.
You'll need to dig on the source code and create a new upload method.
(An Image belongs to a Post)
The image uploader works, but only when I upload the image on Post Edit. I want the image to upload as the Post is created.
The Image upload ("<%= f.file_field :image %>") is within the New Post Form, so I'm guessing Carrierwave is trying to upload the image to the designated path before the Post is created, causing it to not know where to upload.
Here is the ImageUploader file:
class TrackImageUploader < CarrierWave::Uploader::Base
def cache_dir
"#{Rails.root}/tmp/uploads"
end
include CarrierWave::RMagick
#storage :file
#storage :fog
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
version :featured do
process :resize_to_fill => [210, 145]
end
def extension_white_list
%w(jpg jpeg gif png)
end
end
I am guessing the store_dir from the code above is in charge of saving the image in the according path on Amazon S3.
How would I make it so that it uploads the image AFTER the Post is made, so that it can get the Post.id ?
I had exactly the same problem, but I've found a solution.
My ImageUploader.rb looks like this: https://gist.github.com/egbertp/7572501. On line 9 of this file you'll see include CarrierWaveDirect::Uploader. By using the direct uploader technique, your site-user will upload his/her file directly to the Rackspace cloud. However, at the time of uploading, the Image model is not yet created in you database, causing an error (as you guessed correctly).
By disabling the direct upload technique, the site-user sends the image to your webserver fist and afterwards the Image model is created and saved in the database. In my ImageUploader class this is also necessary because I would like my webserver to process the image, in order to create a thumbnail version.
I hope this will help you, or somebody else in the future.
Best regards,
Egbert
I ve also done the same but i ve used the cloudinary gem to maintain my images. Can you please show me ur controller where u ve written the edit action.
I m showing you my code u can tally it with urs
in the controller in edit action
def create
#img = Image.new(params[:img])
if #img.save
redirect_to(:action => 'show')
else
render('c_view')
end
end
in the view from where it is taking the image
<%= form_for :img, :url => {:action => "create"} do |f| %>
<%= f.label "Upload an image"%>
<%= f.hidden_field(:image_cache)%>
<%= f.file_field :image %>
<%= f.submit "Submit", class: "btn btn-success"%>
<% end %>
To show the image in some view page ive used this
<% #img.each_with_index do |i|%>
<%= image_tag (i.image.url) %>
<%end%>