Carrierwave: Duplicating File In a Second Model - ruby-on-rails

I have two models, each with their own Carrierwave uploaders:
class User < ActiveRecord::Base
mount_uploader :avatar, AvatarUploader
end
and:
class Bookshelf < ActiveRecord::Base
mount_uploader :image, ImageUploader
end
I want the user's avatar to be the latest bookshelf image he's uploaded. I try to achieve this like so:
class BookcasesController < ApplicationController
def create
#bookcase = current_user.bookcases.build(params[:bookcase])
if #bookcase.save
current_user.avatar = #bookcase.image
current_user.avatar.recreate_versions!
end
end
end
Unfortunately, this has no effect on the avatar at all. How else might I achieve this?

current_user.avatar = #bookcase.image
current_user.avatar.recreate_versions!
Doesn't actually save --- you can either:
current_user.avatar.save
or as you put:
current_user.update_attribute(:avatar, #bookcase.image)

If your image file is stored locally and you don't mind opening a file descriptor, you could also do this:
current_user.avatar = File.open(#bookcase.image.path)
current_user.save

Related

Attach Cloudinary image to Rails model using ActiveStorage

I have a user model
class User < ApplicationRecord
has_one_attached :photo
end
I'm trying to:
upload an image to Cloudinary via URL (this works)
attach it to a user instance which uses ActiveStorage (this does not)
Here is what I think should work
user_img_response = Cloudinary::Uploader.upload("https://www.formula1.com/content/dam/fom-website/manual/Misc/2019-Races/Monaco2019/Monaco%20chicane%20HAM%20VER%20sized.jpg.transform/9col/image.jpg")
img_id = user_img_response["url"].match(/image\/upload.*/)[0]
signature = "#{img_id}##{user_img_response["signature"]}"
preloaded_file = Cloudinary::PreloadedFile.new(signature)
user = User.new(title: "Chris")
user.photo = preloaded_file
user.save
=> true
However, the photo is not being attached to the user instance
user.photo.attached?
=> false
Assuming your app/models/photo.rb looks similar to this:
class Photo < ActiveRecord::Base
attr_accessible :title, :bytes, :image, :image_cache
belongs_to :album
mount_uploader :image, ImageUploader
validates_presence_of :title, :image
end
What happens if you try:
...
user = User.new(title: "Chris")
user.photo.image = preloaded_file # <---- assign file to image attribute
user.save
You can also try to emulate this sample app for your case: https://github.com/cloudinary/cloudinary_gem/tree/master/samples/photo_album
EDIT: you can try something like this:
require 'uri'
file = URI.open(user_img_response["url"]) # use cloudinary url
photo.image.attach(io: file, filename: 'image.jpg')
See: https://blog.eq8.eu/til/upload-remote-file-from-url-with-activestorage-rails.html

Unable to autoload constant, expecting FILEPATH to define it - in other words, huh?

When I try to access my localhost, I get the following message.
**LoadError in ListingsController#index
Unable to autoload constant Listing, expected C:/Sites/redemo/app/models/listing.rb to define it**
This is the code it refers to in listings_controller.rb
# GET /listings.json
def index
#listing =Listing.all
end
My listing.rb is as follows:
class Listings < ActiveRecord::Base
mount_uploader :image, ImageUploader
end
Please, what else am I supposed to do to define it?
You check your model definition. While controller is Listings, model should be Listing
Therefore change to this;
class Listing < ActiveRecord::Base
mount_uploader :image, ImageUploader
end

Fallback Image with Carrierwave

My uploader is working well apart from one small thing. The setting of default images. I'm using carrierwave for users to upload profile images of themselves:
user model
class User < ActiveRecord::Base
has_one :avatar, class_name: 'Image', foreign_key: :user_id
before_create :create_fallback_image
def create_fallback_image
self.create_avatar
end
end
image model
class Image < ActiveRecord::Base
mount_uploader :file_name, AvatarUploader, auto_validate: false
belongs_to :user
end
avatar uploader
class AvatarUploader < BaseUploader
include CarrierWave::RMagick
storage :file
process resize_to_fit: [75, 75]
process convert: 'gif'
def default_url
'foobar'
end
def filename
random_string + '.gif'
end
end
def random_string
#random_string ||= User.random_string
end
end
When a user signs up without uploading an optional profile image, they are assigned an association to their profile image, but instead of the default_url working, they get a random string from the filename method.
I thought I could get around it like this:
user model
class User < ActiveRecord::Base
has_one :avatar, class_name: 'Image', foreign_key: :user_id
before_create :create_fallback_image
def create_fallback_image
# look here:
self.create_avatar.create_fallback
end
end
image model
class Image < ActiveRecord::Base
mount_uploader :file_name, AvatarUploader, auto_validate: false
belongs_to :user
def create_fallback
self.update_attributes(file_name: 'my_fallback.jpg')
end
end
and while it nearly, nearly works, when I update the attributes of the file_name column, the uploader kicks in and my_fallback.jpg is overridden by a random string from my random_string method!
Carrierwave has a built-in fallback mechanism for default image
Update your default_url method in AvatarUploader as below:
def default_url
ActionController::Base.helpers.asset_path("fallback/" + [version_name, "my_fallback.jpg"].compact.join('_'))
end
where change fallback/ to your desired folder path.
This way, when an avatar is not uploaded for a particular user then my_fallback.jpg would be used as fallback image.
Refer to section Providing a default URL in Carrierwave Documentation.
when I update the attributes of the file_name column, the uploader
kicks in and my_fallback.jpg is overridden by a random string from my
random_string method!
This happens because you have overridden filename method in AvatarUploader which gets called every time an image is uploaded. If you notice, its calling random_string method in it. Hence, you get a random string as your filename.
UPDATE
As per the chat session with OP, if an avatar is not uploaded for a user then a default image should be shown. I suggested the following helper :
module ApplicationHelper
def display_avatar(user)
unless user.avatar.nil?
image_tag(user.avatar.file_name)
else
image_tag("/path/to/fallback.jpg")
end
end
## ...
end
Use this helper method in views to display avatar image appropriately.
Also, you can do it within model:
class User < ApplicationRecord
has_one :avatar, class_name: 'User::Avatar', as: :parent, dependent: :destroy
accepts_nested_attributes_for :avatar, allow_destroy: true #, ...
def avatar
super || build_avatar
end
end

Dynamic uploader with Carrierwave

I am using a single Image model to store information about images used by different other models (via a polymorphic association).
I'd like to change the uploader on this model depending on the model associated to have different versions for different models.
For example, if the imageable is a Place, the mounted uploader would be a PlaceUploader. And if there is no PlaceUploader, it would be the default ImageUploader.
At the moment I have:
class Image < ActiveRecord::Base
belongs_to :imageable, polymorphic: true
mount_uploader :image, ImageUploader
end
Ideally I'd like to have:
# This is not supported by CarrierWave, just a proof of concept
mount_uploader :image, -> { |model| "#{model.imageable.class.to_s}Uploader".constantize || ImageUploader }
Is there a way to achieve that? Or a better way to have different versions depending on the associated model?
Edit
I found another solution using one single ImageUploader:
class ImageUploader < BaseUploader
version :thumb_place, if: :attached_to_place? do
process resize_to_fill: [200, 200]
end
version :thumb_user, if: :attached_to_user? do
process :bnw
process resize_to_fill: [100, 100]
end
def method_missing(method, *args)
# Define attached_to_#{model}?
if m = method.to_s.match(/attached_to_(.*)\?/)
model.imageable_type.underscore.downcase.to_sym == m[1].to_sym
else
super
end
end
end
As you can see my 2 versions are named thumb_place and thumb_user because if I name them both thumb only the first one will be considered (even if it doesn't fill the condition).
I need to implement same logic that i have a single Image model and base on polymorphic association to mount different uploaders.
finally i come up below solution in Rails 5:
class Image < ApplicationRecord
belongs_to :imageable, polymorphic: true
before_save :mount_uploader_base_on_imageable
def mount_uploader_base_on_imageable
if imageable.class == ImageableA
self.class.mount_uploader :file, ImageableAUploader
else
self.class.mount_uploader :file, ImageableBUploader
end
end
end

Conflict between Picture model and Ckeditor::Picture model

In my existing Rails project, I create Picture model.
class Picture < ActiveRecord::Base
belongs_to :user
end
And then, when adding Ckeditor to my project, I have to create another Picture model under ckeditor directory like this
class Ckeditor::Picture < Ckeditor::Asset
...
end
In my user model, I have this association
class User < ActiveRecord::Base
has_many :pictures
end
However, I cannot use user.pictures. Whenever I make this statement, the following error comes up:
Expected /home/xxx/app/models/ckeditor/picture.rb to define Picture
How can I solve this issue?
try:
class User < ActiveRecord::Base
has_many :pictures,:class_name=> "::Picture"
end
I'm not sure but maybe:
module Ckeditor
class Picture < Ckeditor::Asset
...
end
end
I managed to resolve my issue by renaming the Picture class into UserPicture and use table_name to set its corresponding table in database. And then in User model:
has_many :pictures, class_name: 'UserPicture'
Simple:
1) rename models/ckeditor/picture.rb to models/ckeditor/epicture.rb
2) in models/ckeditor/epicture.rb change to this:
class Ckeditor::Epicture < Ckeditor::Asset
has_attached_file :data,
url: "/ckeditor_assets/epictures/:id/:style_:basename.:extension",
path: ":rails_root/public/ckeditor_assets/epictures/:id/
3) in config/initializers/ckeditor.rb uncomment row and change to this:
config.picture_model { Ckeditor::Epicture }
4) for correct work in _asset.html.erb change bug:
polymorphic_path(asset, format: :json) to picture_path(asset)
in my case dir of this file
\usr\local\rvm\gems\ruby-1.9.3-p545\gems\ckeditor-4.1.2\app\views\ckeditor\shared\_asset.html.erb
Restart server.
Work's fine!
You can change Dafault Picture Model Name in config/initializers/ckeditor.rb like this:
Ckeditor.setup do |config|
...
config.picture_model { Ckeditor::EditorPicture }
...
end
remove autogenerated defined model from models/ckeditor/picture.rb and add new model /ckeditor/editor_picture.rb
insert to editor_picture.rb
class Ckeditor::EditorPicture < Ckeditor::Asset
mount_uploader :data, CkeditorPictureUploader, :mount_on => :data_file_name
def url_content
url(:content)
end
end

Resources