My model:
class Mainphoto < ApplicationRecord
has_one_attached :main_photo
end
Try to attache file like this in after_create for User model:
Mainphoto.create!(:profile_id => self.profile.id)
file = open("#{Rails.root}/public" + self.profile.avatar_photo_url)
photo_url = self.profile.avatar_photo_url
self.profile.mainphoto.main_photo.attach(io:file, filename:photo_url.slice(8..))
It generate url for photo like this:
http://localhost:3000/rails/active_storage/representations/redirect/eyJfcmFpbHMi
OnsibWVzc2FnZSI6IkJBaHBBa2FyaWF0aW9uIn19-
-021933e1f5b2afffc1fd3a3674847cee15e2754d/3184836_b37_2.jpg
and I get error like this:
ActiveStorage::FileNotFoundError in
ActiveStorage::Representations::RedirectController#show
ActiveStorage::FileNotFoundError
I check all post in here with keywords "activestorage upload image from url" but still I get this error.
I check database and there is correct Blob, correct attachment entry ... what's going on?
I DONT WANT TO DO IT LIKE THIS - BUT THIS WORK ... why?
blob = ActiveStorage::Blob.create_and_upload!(
io: File.open("#{Rails.root}/public" + photo_url),
filename: photo_url.slice(8..),
content_type: 'image'
)
ActiveStorage::Attachment.create(
name: 'main_photo',
record_type: 'Mainphoto',
record_id: self.profile.mainphoto.id,
blob_id: blob.id
)
Related
Been trying to setup an ActiveStorage fixture to test my Rails 7 model using Minitest. I'm following steps in File attachment fixtures and Adding attachments to fixtures.
I can tell that the fixture is being loaded into ActiveStorage::Attachment correctly:
(ruby) ActiveStorage::Attachment.all
[#<ActiveStorage::Attachment:0x00007ff16aa74f28 id: 93947105, name: "file", record_type: "Trades File", record_id: 824316784, blob_id: 821228354, created_at: Sat, 31 Dec 2022 21:39:50.514686000 UTC +00:00>]
(ruby) ActiveStorage::Attachment.first.filename
#<ActiveStorage::Filename:0x00007ff16656a0e0 #filename="tw_trades.csv">
However, when I try to access the attachment from the model, I receive nil values:
(ruby) TradesFile.first.file.filename
nil
(ruby) TradesFile.first.file.attached?
false
I can also confirm that the attachment refers to the correct model instance:
Here is my config:
test/fixtures/active_storage/attachments.yml:
tw_file:
name: file
record: tw (Trades File)
blob: tw_file_blob
test/fixtures/active_storage/blobs.yml:
tw_file_blob: <%= ActiveStorage::FixtureSet.blob filename: "tw_trades.csv", service_name: 'test_fixtures' %>
test/fixtures/trades_files.yml
tw:
account: tw_account
imported_at: 2022-12-22 11:32:49
app/models/tw.rb
class TradesFile < ApplicationRecord
belongs_to :account
has_one_attached :file
...
end
Update
One issue may be that my model is named TradesFile but the record field for tw_file in the attachments.yml is set to Trades File (with a space).
I removed the space so the record type matches the model name. However, I noticed that the record_id in the attachment no longer matches the id of the TradesFile:
Also calling ActiveStorage::Attachment.first.record returns nil, so it seems the attachment is linking to some record that does not exist.
Success! Yes turned out the issue was the space in the record field in the attachments.yml.
For future readers, the record field in attachments.yml must exactly match the model name with the attachment.
So if you have:
# app/models/trades_file.rb
class TradesFile < ApplicationRecord
belongs_to :account
has_one_attached :file
...
end
Then the record field must be:
# test/fixtures/active_storage/attachments.yml
tw_file:
name: file
record: tw (TradesFile)
blob: tw_file_blob
How to seed images to a model that has many attached images?
#place.rb
....
name
has_many_attached :photos
.....
#seed.rd
p = Place.create!(name: "New york")
p.photo.attach(io: File.open('app/assets/images/tower.png'),
filename: 'tower.png', content_type: 'image/png')
This does not work
i have the tower.png image in my image assets
Any suggestions?
Since the relationship between places and photos (files) is one-to-many, you have to use place.photos.attach to attach images to the record. You can join the image path with Rails.root to generate the file path.
place.photos.attach(
io: File.open(Rails.root.join('app/assets/images/tower.png')),
filename: 'tower.png',
content_type: 'image/png'
)
I have a Photo model with an image attribute. The image contains a base64 string obtained from an api. I need to run an after_create callback and I was thinking I could use Paperclip for saving the image to the disk in the callback as it would save me some work implementing the folder structure in the public folder and generating thumbnails. Is there an easy way to do that?
To answer my own question, here is what I've come up with:
class Photo < ActiveRecord::Base
before_validation :set_image
has_attached_file :image, styles: { thumb: "x100>" }
validates_attachment :image, presence: true, content_type: { content_type: ["image/jpeg", "image/jpg"] }, size: { in: 0..10.megabytes }
def set_image
StringIO.open(Base64.decode64(image_json)) do |data|
data.class.class_eval { attr_accessor :original_filename, :content_type }
data.original_filename = "file.jpg"
data.content_type = "image/jpeg"
self.image = data
end
end
end
image_json is a text field containing the actual base64 encoded image (just the data part, eg "/9j/4AAQSkZJRg...")
your set_image should look something like this
def set_image
self.update({image_attr: "data:image/jpeg;base64," + image_json[PATH_TO_BASE64_DATA]})
end
At least with Paperclip 5 it works out of the box you need to provide base64 string with format data:image/jpeg;base64,#{base64_encoded_file}
For you model it will be
Photo.new(
image: "data:image/jpeg;base64,#{image_json}",
image_file_name: 'file.jpg' # this way you can provide file_name
)
Additionally in your controller you do not need to change anything:-) (maybe you would like to accept :image_file_name in params)
As of Paperclip 5.2 you need to register the DataUriAdapter for Paperclip to handle base64 images for you.
In config/initializers/paperclip put:
Paperclip::DataUriAdapter.register
Then as #eldi says you can just do:
Photo.new(
image: "data:image/jpeg;base64,#{image_json}",
image_file_name: 'file.jpg' # this way you can provide file_name
)
(See Paperclip release notes here)
require 'RMagick'
data = params[:image_text]# code like this data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABPUAAAI9CAYAAABSTE0XAAAgAElEQVR4Xuy9SXPjytKm6ZwnUbNyHs7Jc7/VV9bW1WXWi9q
image_data = Base64.decode64(data['data:image/png;base64,'.length .. -1])
new_file=File.new("somefilename.png", 'wb')
new_file.write(image_data)
After you kan use image as file
Photo.new(image: image)#save useng paperclip in Photo model
I am trying to use fancybox to display images. When I use static images, as the existing in the assets folder, all works fine. However, when I try to use uploaded images which were saved using carrierwave, it always puts the next message: "The requested content cannot be loaded.".
The code that I am using to display the picture is the next:
app/models/upload.rb
class Upload
include Mongoid::Document
include Mongoid::Timestamps
mount_uploader :file, UploadUploader
# Fields
field :file, type: String
field :filename, type: String
field :file64, type: String
field :size_file, type: Integer
field :file_extension, type: String
# Validations
validates_presence_of :file
# Hooks
before_validation do
if not self.file.url and self.filename and self.file64
sio = CarrierwaveStringIO.new( Base64.decode64( self.file64 ) )
sio.original_filename = self.filename
self.file = sio
self.filename = nil
self.file64 = nil
end
end
before_save do
self.size_file = self.size
self.file_extension = File.extname( self.file.to_s )[1..-1]
end
def base64
MIME::Types.type_of( self.file.url ).first.content_type
end
def size
self.file.size
end
end
app/views...
...
= link_to upload_path( upload.id ), class: 'fancybox' do
= image_tag upload_path( upload.id ), height: 174, width: 256
...
app/assets/javascript...
...
$( ".fancybox" ).fancybox()
...
I have tried lots of things to make the code works. For example, in the view, I change the "href" of the link_tag and the "src" of the image_tag, to the show path of the upload but it still does not work. Also, I tried to load the picture using AJAX and using the "content" property of fancybox but I obtained the same result. Finally, I created a show action for the upload controller in which I only display the picture and used the "content" and "type" properties of fancybox to use html, but I can not see the picture (no routes matches with the url of the picture, not the show action).
I have spent a lot of hours trying to make it works but I can not find anything. Thanks in advance.
EDIT:
app/controllers/upload_controller.rb
class UploadsController < ApplicationController
load_and_authorize_resource
respond_to :html, :json
def show
if request.format.html?
content_type = MIME::Types.type_for( #upload.file.url ).first.content_type
send_file #upload.file.url, content_type: content_type, disposition: 'inline', :x_sendfile => true
else
respond_with #upload, api_template: :general
end
end
...
app/uploaders/upload_...
class UploadUploader < CarrierWave::Uploader::Base
storage :file
def store_dir
"#{ Rails.root.to_s }/uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def cache_dir
"#{ Rails.root.to_s }/tmp/uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
def extension_white_list
%w(jpg jpeg png bmp tif)
end
end
app/views/uploads/show.html.haml
= image_tag #upload.file_url.to_s
Specifying type option, forces content type. it can be set to 'image', 'ajax', 'iframe', 'swf' or 'inline'
$( ".fancybox11" ).fancybox({
type: 'iframe', width: 380, height: 280
})
Refer this link fancybox api options.
How do I rename a file after is has been uploaded and saved?
My problem is that I need to parse information about the files automatically in order to come up with the file name the file should be saved as with my application, but I can't access the information required to generate the file name till the record for the model has been saved.
If, for example, your model has attribute image:
has_attached_file :image, :styles => { ...... }
By default papepclip files are stored in /system/:attachment/:id/:style/:filename.
So, You can accomplish it by renaming every style and then changing image_file_name column in database.
(record.image.styles.keys+[:original]).each do |style|
path = record.image.path(style)
FileUtils.move(path, File.join(File.dirname(path), new_file_name))
end
record.image_file_name = new_file_name
record.save
Have you checked out paperclip interpolations?
If it is something that you can figure out in the controller (before it gets saved), you can use a combination of the controller, model, and interpolation to solve your problem.
I have this example where I want to name a file based on it's MD5 hash.
In my controller I have:
params[:upload][:md5] = Digest::MD5.file(file.path).hexdigest
I then have a config/initializers/paperclip.rb with:
Paperclip.interpolates :md5 do|attachment,style|
attachment.instance.md5
end
Finally, in my model I have:
validates_attachment_presence :upload
has_attached_file :upload,
:path => ':rails_root/public/files/:md5.:extension',
:url => '/files/:md5.:extension'
To add to #Voyta's answer, if you're using S3 with paperclip:
(record.image.styles.keys+[:original]).each do |style|
AWS::S3::S3Object.move_to record.image.path(style), new_file_path, record.image.bucket_name
end
record.update_attribute(:image_file_name, new_file_name)
My avatar images are named with the user slug, if they change their names I have to rename images too.
That's how I rename my avatar images using S3 and paperclip.
class User < ActiveRecord::Base
after_update :rename_attached_files_if_needed
has_attached_file :avatar_image,
:storage => :s3,
:s3_credentials => "#{Rails.root}/config/s3.yml",
:path => "/users/:id/:style/:slug.:extension",
:default_url => "/images/users_default.gif",
:styles => { mini: "50x50>", normal: "100x100>", bigger: "150x150>" }
def slug
return name.parameterize if name
"unknown"
end
def rename_attached_files_if_needed
return if !name_changed? || avatar_image_updated_at_changed?
(avatar_image.styles.keys+[:original]).each do |style|
extension = Paperclip::Interpolations.extension(self.avatar_image, style)
old_path = "users/#{id}/#{style}/#{name_was.parameterize}#{extension}"
new_path = "users/#{id}/#{style}/#{name.parameterize}#{extension}"
avatar_image.s3_bucket.objects[old_path].move_to new_path, acl: :public_read
end
end
end
And to add yet another answer, here is the full method I'm using for S3 renaming :
def rename(key, new_name)
file_name = (key.to_s+"_file_name").to_sym
old_name = self.send(file_name)
(self.send(key).styles.keys+[:original]).each do |style|
path = self.send(key).path(style)
self[file_name] = new_name
new_path = self.send(key).path(style)
new_path[0] = ""
self[file_name] = old_name
old_obj = self.send(key).s3_object(style.to_sym)
new_obj = old_obj.move_to(new_path)
end
self.update_attribute(file_name, new_name)
end
To use : Model.find(#).rename(:avatar, "test.jpg")
I'd like to donate my "safe move" solution that doesn't rely on any private API and protects against data loss due to network failure:
First, we get the old and new paths for every style:
styles = file.styles.keys+[:original]
old_style2key = Hash[ styles.collect{|s| [s,file.path(s).sub(%r{\A/},'')]} ]
self.file_file_name = new_filename
new_style2key = Hash[ styles.collect{|s| [s,file.path(s).sub(%r{\A/},'')]} ]
Then, we copy every file to it's new path. Since the default path includes both object ID and filename, this can never collide with the path for a different file. But this will fail if we try to rename without changing the name:
styles.each do |style|
raise "same key" if old_style2key[style] == new_style2key[style]
file.s3_bucket.objects[old_style2key[style]].copy_to(new_style2key[style])
end
Now we apply the updated model to the DB:
save!
It is important to do this after we create the new S3 objects but before we delete the old S3 objects. Most of the other solutions in this thread can lead to a loss of data if the database update fails (e.g. network split with bad timing), because then the file would be at a new S3 location but the DB still points to the old location. That's why my solution doesn't delete the old S3 objects until after the DB update succeeded:
styles.each do |style|
file.s3_bucket.objects[old_style2key[style]].delete
end
Just like with the copy, there's no chance that we accidentally delete another database object's data, because the object ID is included in the path. So unless you rename the same database object A->B and B->A at the same time (e.g. 2 threads), this delete will always be safe.
To add to #Fotios's answer:
its the best way I think to make custom file name, but in case you want file name based on md5 you can use fingerprint which is already available in Paperclip.
All you have to do is to put this to config/initializers/paperclip_defaults.rb
Paperclip::Attachment.default_options.update({
# :url=>"/system/:class/:attachment/:id_partition/:style/:filename"
:url=>"/system/:class/:attachment/:style/:fingerprint.:extension"
})
There's no need to set :path here as by default it's made that way:
:path=>":rails_root/public:url"
I didn't check if it's necessary but in case it doesn't work for you make sure your model is able to save fingerprints in the database -> here
One more tip which I find handy is to use rails console to check how it works:
$ rails c --sandbox
> Paperclip::Attachment.default_options
..
> s = User.create(:avatar => File.open('/foo/bar.jpg', 'rb'))
..
> s.avatar.path
=> "/home/groovy_user/rails_projectes/funky_app/public/system/users/avatars/original/49332b697a83d53d3f3b5bebce7548ea.jpg"
> s.avatar.url
=> "/system/users/avatars/original/49332b697a83d53d3f3b5bebce7548ea.jpg?1387099146"
The following migration solved the problem to me.
Renaming avatar to photo:
class RenamePhotoColumnFromUsers < ActiveRecord::Migration
def up
add_attachment :users, :photo
# Add `avatar` method (from Paperclip) temporarily, because it has been deleted from the model
User.has_attached_file :avatar, styles: { medium: '300x300#', thumb: '100x100#' }
User.validates_attachment_content_type :avatar, content_type: %r{\Aimage\/.*\Z}
# Copy `avatar` attachment to `photo` in S3, then delete `avatar`
User.where.not(avatar_file_name: nil).each do |user|
say "Updating #{user.email}..."
user.update photo: user.avatar
user.update avatar: nil
end
remove_attachment :users, :avatar
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
Hope it helps :)
Another option is set to default, work for all upload.
This example change name file to 'name default' for web, example: test áé.jpg to test_ae.jpg
helper/application_helper.rb
def sanitize_filename(filename)
fn = filename.split /(?<=.)\.(?=[^.])(?!.*\.[^.])/m
fn[0] = fn[0].parameterize
return fn.join '.'
end
Create config/initializers/paperclip_defaults.rb
include ApplicationHelper
Paperclip::Attachment.default_options.update({
:path => ":rails_root/public/system/:class/:attachment/:id/:style/:parameterize_file_name",
:url => "/system/:class/:attachment/:id/:style/:parameterize_file_name",
})
Paperclip.interpolates :parameterize_file_name do |attachment, style|
sanitize_filename(attachment.original_filename)
end
Need restart, after put this code