I have an attachment
class User < ApplicationRecord
has_one_attached :avatar
when I create the user and I perform on my seed
user = User.create({"name":"pedro})
file = open("#{Rails.root}/app/assets/img/user.png")
user.avatar.attach(io: file, filename: "user.png")
the avatar gets attached
However, when I try to replicate/update it on my controller:
user = User.find(params["id"])
user.avatar.purge
file = open("#{Rails.root}/app/assets/img/user.png")
user.avatar.attach(io: file, filename: "user.png")
It somehow gets attatched (if I perform a user.avatar.attached? before it returns false and true after I attach it) but the blob doesn't persist/get saved into storage. It somehow only persists with newly created objects.
I've tried looking for questions with a similar issue with no success.
I don't see anywhere that you are saving it to the database. Try user.new instead of user.create. After attaching the file save it.
user = User.new({"name":"pedro})
file = open("#{Rails.root}/app/assets/img/user.png")
user.avatar.attach(io: file, filename: "user.png")
user.save
it is good practice to add the content type
user.avatar.attach(io: file, filename: "user.png" , content_type:
'image/png')
Edit (Op comment update): you can use user.save(validate: false) to skip validations
Related
Fairly new to using ActiveStorage (love it so far!), but I am running into an issue with trying to figure out how to update a previously attached item.
I have a front-end form that allows a user to upload a PDF, my backend then takes the PDF and scrapes it for data, then exports the data to an excel spreadsheet. My code (when I call to re-attach the export), I see my item in my specified s3 bucket, but its a hashed key name.
class Export < ApplicationRecord
has_one_attached :item
def process_pdf!
pdf_file_name = "#{item.blob.key}_#{item.blob.filename.to_s}"
xls_file_name = "#{item.blob.key}_#{item.blob.filename.to_s.sub('pdf', '')}"
file = "#{Rails.root}/tmp/#{pdf_file_name}"
File.open(file, 'wb') do |f|
f.write(pdf.download)
end
PdfScraper.call(pdf_file: file, output_name: xls_file_name)
self.item.attach(
io: File.open("#{Rails.root}/tmp/#{xls_file_name}"),
filename: "#{xls_file_name}.xlsx",
content_type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
File.delete(file)
true
end
end
How do I attach the generated excel spreadsheet?
I'm looking to save a file (in this case an image) located on another http web server using rails 5.2 active storage.
I have an object with a string parameter for source url. Then on a before_save I want to grab the remote image and save it.
Example: URL of an image http://www.example.com/image.jpg.
require 'open-uri'
class User < ApplicationRecord
has_one_attached :avatar
before_save :grab_image
def grab_image
#this indicates what I want to do but doesn't work
downloaded_image = open("http://www.example.com/image.jpg")
self.avatar.attach(downloaded_image)
end
end
Thanks in advance for any suggestions.
Just found the answer to my own question. My first instinct was pretty close...
require 'open-uri'
class User < ApplicationRecord
has_one_attached :avatar
before_save :grab_image
def grab_image
downloaded_image = open("http://www.example.com/image.jpg")
self.avatar.attach(io: downloaded_image , filename: "foo.jpg")
end
end
Update: please note comment below, "you have to be careful not to pass user input to open, it can execute arbitrary code, e.g. open("|date")"
Like said in the comments, the use of open or URI.open is very dangerous, since it can not only access files but also process invocation by prefixing a pipe symbol (e.g. open("| ls")).
Kernel#open and URI.open enable not only file access but also process invocation by prefixing a pipe symbol (e.g., open("| ls")). So, it may lead to a serious security risk by using variable input to the argument of Kernel#open and URI.open. It would be better to use File.open, IO.popen or URI.parse#open explicitly.
Extracted from the Rubocop documentation: https://docs.rubocop.org/rubocop/1.8/cops_security.html#securityopen
So, a safer solution would be:
class User < ApplicationRecord
has_one_attached :avatar
before_save :grab_image
def grab_image
downloaded_image = URI.parse("http://www.example.com/image.jpg").open
avatar.attach(io: downloaded_image, filename: "foo.jpg")
end
end
using the down gem to avoid the security issues of using open-uri:
image = Down.download(image_url)
user.image.attach(io: image, filename: "image.jpg")
The simplest way to do this without having to enter filename explicitly is:
url = URI.parse("https://your-url.com/abc.mp3")
filename = File.basename(url.path)
file = URI.open(url)
user = User.first
user.avatar.attach(io: file, filename: filename)
This automatically saves the avatar against that particular user object.
In case you are using a remote service like S3 the URL can be retrieved by:
user.avatar.service_url
I have two models: Organization and Drawing. They have a 1:1 relationship. Drawing contains two variables: organization_id and file_path.
At signup of a new organization, I have my Rails application automatically copy a standard file for that organization to my S3 bucket. File_path in the Drawing model should include the string to the path where the file is stored.
To the organization's controller I have added a reference to the method upload_file, which I have included in the Drawing model.
Controller:
def create
#organization = Organization.new(organizationnew_params)
if #organization.save
Drawing.upload_file(#organization.id)
redirect_to root_url
end
end
Model:
def self.upload_file(id)
s3 = Aws::S3::Resource.new(
credentials: Aws::Credentials.new(ENV['S3_ACCESS_KEY'], ENV['S3_SECRET_KEY']),
region: ENV['AWS_REGION']
)
xmlfile = 'app/assets/other/graph.xml'
key = "uploads/#{id}/xml-#{id}.xml"
obj = s3.bucket('mybucketname').object(key)
obj.upload_file(xmlfile)
Conceptmap.create!(organization_id: id, xml_file: obj.public_url)
end
Question: My question concerns the correctness of xml_file: obj.public_url on the last line. Here I want to save the bucket-path of the saved file. But I wonder if this is the secure way to do this (or should this path be a hash)? How does this work with a carrierwave uploader; is it then automatically hashed? I don't want just anyone to be able to browse to the file and open it.
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.
I am using the paperclip gem to attach files to models. When you upload a file using Paperclip, the file is saved ONLY when the model is saved. Thus, if the model is invalid, the uploaded file is not saved. Is there a way to temporarily save the uploaded file, so that the user doesn't have to upload the same file if the model is invalid?
Define a before_save method that checks if the object is valid,
if not save the file to disk, give it a unique name (create some hash)
Put that in the form you send back in a hidden field
Delete the Upload field in the form
Now in the else branch of the before_save method check if there was a hidden_field previous_upload or however you name it
If there is load the picture and assign it to the paperclip attribute, it can figure out the rest
attr_accessor :previous_upload
def before_save
if valid?
if previous_upload
paperclip_file = #Load paperclip_file from /tmp
else
previous_upload = nil
end
else
previous_upload = "Some unique key for each upload like ip and time or such"
# Save paperclip_file with name previous_upload to /tmp
end
end