Migrating Carrierwave to ActiveStorage - ruby-on-rails

I have an application using Carrierwave to handle file uploads but really love the simplicity of ActiveStorage. There are plenty of tutorials on migrating from Paperclip to ActiveStorage with the former sunsetting development, but I see nothing on migrating from Carrierwave to ActiveStorage. Has anyone successfully done the migration and could point me in the right direction?

The procedure is really simple actually.
step 1:
configure the active store bucket. try to use a different bucket than your carrierwave one
step 2:
configure your model in order to provide access to the ActiveStorage. example
class Photo < AR::Base
mount_uploader :file, FileUploader # this is the current carrierwave implementation. Don't remove it
has_one_attached :file_new # this will be your new file
end
Now you will have two implementations for the same model. carrierwave access at file and ActiveStorage at file_new
step 3:
download images from Carrierwave and save it to active storage
This can be implemented in a rake file, activeJob etc..
Photo.find_each do |photo|
begin
filename = File.basename(URI.parse(photo.fileurl))
photo.file_new.attach(io: open(photo.file.url), filename: d.file )
rescue => e
## log/handle your errors in order to retry later
end
end
At this point you will have one image on the carrierwave bucket and the newly created image on the active storage bucket!
(optional)
step 4
Once you are ready with the migration modify your model changing the active storage accessor and remove the carrierwave integration
class Photo < AR::Base
has_one_attached :file # we changed the atachment name from file_new to file
end
This is a convenience option so your integration in controllers and other places remain intact. hopefully!
 step 5
Update your records on active_storage_attachments table in order for the attachments be found as file and not file_new update column name from "file_new" to "file"
notes
Is possible to make some other tweaks to the application in order to handle things to consider
if your site will be running while you do the migration one way to fully operate would be implement active storage for new uploads then when you display images you can display active storage and carrierwave as a fallback
something like this in a helper:
photo.attached? ? url_for(photo.file_new) : photo.file.url
I hope this helps!

To begin with
You'll have to run this bundle exec rails active_storage:install
rails db:migrate
Replace mount_uploader :image, ImageUploader, to look like has_one_attached :image, in your model.
For rendering the image in the view, you should replace image_url with url_for(user.image).
You don't have to make any change to the controller code or in params, as the attribute image is already a strong parameter.
# user.rb
class User < ApplicationRecord
# mount_uploader :image, ImageUploader
has_one_attached :image
end
# show.html.erb
<%= image_tag url_for(user.image) %>
or
<%= image_tag user.image %>

Related

Carrierwave object.url VS object.image_url

In my Rails 5 app I am using Carrierwave to upload images.
I have to model that uses the same uploader:
account.rb:
mount_uploader :logo, ImageUploader
image.rb:
mount_uploader :image, ImageUploader
This uploads the file to:
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
The strange this now is that I can use:
#account.logo&.url(:thumb) // works!
#account.logo&.image_url(:thumb) // error!
But on the image model (one product has many images):
#product.images.first&.image_url(:thumb) // works!
#product.images.first&.url(:thumb) // error!
So in the first case I have to use .url and in the second one .image_url
An I have no idea why...any help?
The instance method image_url is defined dynamically based on the column that is passed to mount_uploader and simply calls url on the column. The definition looks like this...
def #{column}_url(*args)
#{column}.url(*args)
end
So, I would suspect that logo_url would work on #account (although I have not tested this)
source

Rails Active Storage: Get relative disk service path for attachment

I'm switching to Rails Active Storage to handle upload and storage of images locally (using the disk service) for a product catalog, and I'm having trouble getting a usable url to the image to feed into an <img> tag. I'm using React on the frontend, so I can't (easily) use Rails helpers to generate the tag.
ActiveStorage puts the files in /public/images. I can hardcode relative links to the files (i.e. http://localhost:3000/images/Ab/CD/AbCDEfGhIjkL) and it works fine.
Relevant snippet from Product.rb:
class Product < ApplicationRecord
attr_accessor :image_url
has_one_attached :image
def as_json(options)
h = super(options)
if self.image.attached?
h[:image_url] = ActiveStorage::Blob.service.service_url(self.image.key)
end
h
end
end
as_json produces a JSON object to feed to React that has an entry image_url that is used for the <img>'s src attribute. With the code above, image_url contains the full path to the file (i.e. http://localhost:3000/srv/www/rails_app/public/images/Ab/CD/AbCDEfGhIjkL). Using url_for in the view produces the same result. I want it to only contain the path relative to rails root.
I could manipulate the string to remove everything before the relative path, but I foresee this causing bugs in the future if anything ever changes, so I'd much rather find a way to get ActiveStorage to just generate an appropriate string for me.
Thanks!
You need to use the routes helper to build of a URL to your Rails app.
https://guides.rubyonrails.org/active_storage_overview.html#linking-to-files
class Product < ApplicationRecord
attr_accessor :image_url
has_one_attached :image
def as_json(options)
h = super(options)
if self.image.attached?
h[:image_url] = Rails.application.routes.url_helpers.rails_blob_path(self.image)
end
h
end
end

upload image to multiple models in rails

I am using carrierwave and Minimagick gem to upload an attachment to S3. Now I want to save the some.pdf in two models(ie, assignment, and message). I give the same parameters in attachment field to save in two tables. But the second table attachment saves blurry. First one gets clear view of attachment.
My controller codes like,
#assignment = Assignment.new(assignment_params)
#message = Message.new
begin
Message.transaction do
asign_att = params[:assignment][:attachment]
#assignment.save!
#message.attachment = asign_att
#message.save!
end
end
My model has,
(in attachment.rb) mount_uploader :attachment, AttachmentUploader
(in message.rb) mount_uploader :attachment, ImageUploader
I want to save same file into two models with clear view. What I want to do? Thanks in advance.
Check in your second table uploader file if you have specified any version or something like that.
With version you can create clones of attachment in different resolutions like this.
version :thumb do
process resize_to_fit 50, 50
end
I would use a callback to do this, something like:
after_commit :assign_to_models
def assign_to_models
...
end
IMHO, I would create an model that has all the carrierwave attachements, and have it belongs both to message and attachement.
I hope this helps

How can I make file validation if field does not exist on DB?

Ruby on Rails 4.0
I have item model.
Item new form has image field that does not exist on DB.
Because I want to save files on directory.
But how can I make validation for file field?
You could add an attr_accessor to your model.
class Item < ActiveRecord::Base
attr_accessor :file
validates_presence_of :file
end
Then in the form
<%= f.file_field :file %>
That is one option but I would recommend looking into using Carrier Wave or Paperclip for file uploads. The files are still saved on the file system and are only referenced in the database by their file names.

Rails carrierwave mounting on condition

I need to mount picture uploader after some verification function.
But if I call as usual mounting uploader in model:
mount_uploader :content, ContentUploader
carrierwave first download content, and then Rails start verification of model.
Specifically, I don't want to load big files at all! I want to check http header Content-length and Content-type and then, if it's OK, mount uploader.
Maybe something like that:
if condition
mount_uploader :content, ContentUploader
end
How can I do it?
P.S. Rails version 3.2.12
This is not the way to go if you just want avoid loading big files! That said, one can have conditional mount overriding content=.
As CarrierWave v1.1.0 there is still no conditional mount. But note that mount_uploader first includes a module in the class and then overrides the original content= to call the method content= defined in the included module. So, a workaround is just to redefine the accessors after you have called mount_uploader:
class YourModel < ActiveRecord::Base
mount_uploader :content, ContentUploader
def content=(arg)
if condition
super
else
# original behavior
write_attribute(:content, arg)
end
end
def content
# some logic here
end
end

Resources