I'm experiencing some issues when caching some data in my Rails API, when the model has a Carrierwave uploader attached. The issue started when I added versions in my ImageUploader.
The model looks like this:
class Article < ApplicationRecord
validates_presence_of :title, :intro, :body, :image, :release_date, :slug
mount_uploader :image, ImageUploader
The ImageUploader now looks like this:
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
# Choose what kind of storage to use for this uploader:
storage :fog
# Override the directory where uploaded files will be stored.
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
process resize_to_fit: [1920,1080]
version :thumb do
process resize_to_fit: [400,400]
end
version :big do
process resize_to_fill: [1080,1080]
end
Before the change I had no versions, and did no processing the original image.
I have tried caching this endpoint, which looks like this:
today = Date.today
data = Rails.cache.fetch("articles_#{params[:slug]}", :expires_in => 12.hours) do
Article
.includes(:related_articles, :users, :external_authors, :categories)
.where('release_date <= ?', today)
.where(slug: params[:slug])
.first
.as_json(include: [{ related_articles: { except: [:body, :intro, :preview] } }, { categories: { } }, { users: { except: [:password_digest, :password_reset_token, :password_reset_sent_at] } }, { external_authors: { } }])
end
render json: data, status: :ok
The problem is that it initally works, then it suddenly does not work. Especially it does not work after restarting the server (but it has sometimes stopped working randomly after multiple fetches where it worked being cached), giving me error like the following: uninitialized constant ImageUploader::Uploader2293312720
If I then change the cacheKey, it works again, but suddenly the error comes back, either randomly (it works multiple times before it randomly gives me the error), or when restarting server.
For now, I've just removed the caching, and it works fine with no issues.
I'm using: Rails v5.2.5, and Carrierwave v2.2.2.
Related
I've got a Lead model which is splited by lead_status param on products and offers. Only leads with product status should contain images and offers should not. I've migrated attached product_image table to schema and tried to set a default image only for products. Like this:
class Lead < ApplicationRecord
has_attached_file :product_image, styles: { small: "150x150>", default: "350x350"}
validates_attachment_content_type :product_image, content_type: /\Aimage\/.*\z/
before_save :product_image_default_url
def product_image_default_url
if self.lead_status == "product" && self.product_image.url.nil?
self.product_image.url = "/images/:style/default_user_avatar.png"
end
end
Every time when I save a new lead without uploaded image I get "/product_images/original/missing.png" as a default url. No matter which status it has.
Model doesn't recognize new leads by it's status
How can I change that? Force my Lead model to save a default image url according to a "product" status and ignore all those with "offer" status?
My rails version is 5.2.1 and paperclip 6.0.0
Try with following,
has_attached_file
:product_image,
styles: { small: "150x150>", default: "350x350"},
default_url: ":style/default_user_avatar.png"
# app/assets/images/medium/default_user_avatar.png
# app/assets/images/thumbs/default_user_avatar.png
Existing method is,
def default_url
if #attachment_options[:default_url].respond_to?(:call)
#attachment_options[:default_url].call(#attachment)
elsif #attachment_options[:default_url].is_a?(Symbol)
#attachment.instance.send(#attachment_options[:default_url])
else
#attachment_options[:default_url]
end
end
In initializer, Provide monkey patch for following,
require 'uri'
require 'active_support/core_ext/module/delegation'
module Paperclip
class UrlGenerator
def default_url
if #attachment.instance.lead_status == 'product'
default_url = attachment_options[:default_url]
else
default_url = # provide another missing default_url
end
if default_url.respond_to?(:call)
default_url.call(#attachment)
elsif default_url.is_a?(Symbol)
#attachment.instance.send(default_url)
else
default_url
end
end
end
end
Update as per cases
I am using carrierwave to upload my image but failed. Please help me.
Versions used:
rails (4.0.1)
carrierwave (0.10.0)
carrierwave-mongoid (0.7.1)
carrierwave_backgrounder (0.4.1)
My uploader:
#<EventImageUploader:0x007fc6a948ede8
#model=#<Event::EventImage
_id: 5876f63b6c616cea34630000,
c_at(created_at): 2017-01-12 03:21:31 UTC,
image: nil,
image_tmp: "1484191291-59956-9524/2016-12-10_23-45.jpg">,
#mounted_as=:image>
My image is nil, but the image under image_tmp exists. My sidekiq is running fine too.
Here is my worker:
class ImageWorker < ::CarrierWave::Workers::StoreAsset
def perform(*args)
super(*args)
record = ::CarrierWave::Workers::Base.perform(*args)
if record
p "success"
else
raise "record #{args} not found, failed"
end
end
end
How to make the upload works? Thank you.
After viewing the source of CarrierWave::Workers::StoreAsset#perform,
the reason why the image cannot be uploaded is because of embedded documents.
The line from source record = resource.find id will return nil because resource is an embedded class and you cannot find id by an embedded class.
To solve the problem, see here. My code should work if I add the following:
class Event
include Mongoid::Document
embeds_many :images
end
class EventImage
include Mongoid::Document
embedded_in :Event
mount_uploader :image, ImageUploader
process_in_background :image
def self.find(id)
bson_id = Moped::BSON::ObjectId.from_string(id) # needed for Mongoid 3
root = Event.where('images._id' => bson_id).first
root.images.find(id)
end
end
If you get the error uninitialized constant Moped::BSON, make sure to require it in the first place.
I have an Event model that has many photographs. I have an image uploader mounted to the Photographs attribute, and regular uploads and everything is working fine.
However, when I try and duplicate an event, recreating a new photograph object for the new event, the new image is darker than the original, and if I duplicate the duplicate event, it gets darker still.
I have played around with it, but have no solution.
My Uploader code:
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
include CarrierWave::Processing::RMagick
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def cache_dir
"#{Rails.root}/tmp/carrierwave"
end
process :colorspace => :rgb
# Remove EXIF data
process :strip
# Create different versions of your uploaded files:
version :thumb do
process :resize_to_limit => [640, 640]
end
version :preview_thumb do
process :resize_to_limit => [600, 600]
end
version :wine_thumb do
process :resize_to_limit => [160, 440]
end
version :logo_thumb do
process :resize_to_limit => [90, 90]
end
end
And my duplcation code (in Active Admin):
member_action :create_duplicate_event, method: :post do
old_event = Event.find(params[:id])
photograph_urls = old_event.photographs.map(&:image_url)
attributes = old_event.attributes.except("photographs", "id")
new_photos = []
photograph_urls.each do |photo|
new_photo = Photograph.new({
remote_image_url: photo
})
if new_photo.save
new_photos << new_photo
end
end
#event = Event.new(attributes)
#event.photograph_ids = new_photos.map(&:id)
render "/admin/events/_events_form/"
end
The :rgb tag was an attempt to fix. But no luck.
Ruby 2.1 and Rails 4.0
Ok, after a lot of playing around and searching I managed to fix this problem.
First, you need to download the .icc color profiles, which can be found here. It says for windows but they seemed to work for me on my Mac.
Once you have put the .icc files into a /lib/color_profiles directory, add the following code to your uploader:
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
process :convert_image_from_cmyk_to_rgb
#versions, and any other uploader code go here
def convert_image_from_cmyk_to_rgb
manipulate! do |image|
if image.colorspace == Magick::CMYKColorspace
image.strip!
image.add_profile("#{Rails.root}/lib/USWebCoatedSWOP.icc")
image.colorspace == Magick::SRGBColorspace
image.add_profile("#{Rails.root}/lib/sRGB.icc")
end
image
end
end
end
This converts CMYK images to RGB, and keeps the profiles keeping nice, while keeping RGB images as they were, and not ruining them.
I hope this helps someone in the future, and saves them the hours I spent working this out.
Though paperclip gem is generally used for easy file uploads, I have a similar requirement at server side. I have generated an image at server and want to save it in various styles as configured in paperclip options.
I have generated the migration and my model looks like this:
attr_accessible :genimg
has_attached_file :genimg
styles: { mini: '48x48>', small: '100x100>',
product: '240x240>', large: '600x600>' },
default_style: :product
url: '/images/:basename.:extension',
path: ':rails_root/app/assets/images/:basename.:extension'
convert_options: { all: '-strip -auto-orient' }
Is there a way I can save my local image in various formats using paperclip. How can I do so? If that's not possible, what are the other possible way to try out?
class YourController < ActionController::Base
def your_action
#image = Image.find 1 # (might be other way how you get the asset)
file_path = '/path/to/your/file'
file = File.open(file_path, 'r')
#image.asset = file
#image.save
end
end
in console you might do:
Image.new(:data => File.new(path_to_your_file, "r"))
I am essentially writing the project in Railscast 383 - the second part, when the photo is uploaded directly to AWS S3, and the photo is then processed in the background by Sidekiq to create a thumbnail version of the photo. I'm on Rails 4.
My issue is that the Sidekiq job, after completing successfully, keeps repeating over and over, instead of just stopping.
Where am I going wrong? I can't see any difference between my code and that of the Railscast, other than I'm on Rails 4 (so strong parameters instead of attr_accessible)
Photo class:
class Photo < ActiveRecord::Base
mount_uploader :image, ImageUploader
default_scope order('updated_at DESC')
after_save :enqueue_image
def image_name
File.basename(image.path || image.filename) if image
end
def enqueue_image
ImageWorker.perform_async(id, key) if key.present?
end
end
ImageWorker:
class ImageWorker
include Sidekiq::Worker
sidekiq_options retry: false
# sidekiq_options retry: 3
def perform(id, key)
photo = Photo.find(id)
photo.key = key
photo.remote_image_url = photo.image.direct_fog_url(with_path: true)
photo.save!
photo.update_column(:image_processed, true)
end
end
Uploader:
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWaveDirect::Uploader
include CarrierWave::RMagick
# storage :fog
#### storage defaults to fog when CarrierWaveDirect::Uploader is included ####
include CarrierWave::MimeTypes
process :set_content_type
version :thumb do
process :resize_to_limit => [200, 200]
end
version :medium do
process :resize_to_limit => [400, 400]
end
end
One reason for the sidekiq worker to be called again and again is because the perform_async is called every time you save your photo object, which occurs within the sidekiq worker itself.
So every time the ImageWorker is called, it saves the photo, calling the ImageWorker again, creating the loop you are experiencing.
Are you sure you aren't missing a check for the :image_processed tag to be true before calling the ImageWorker again.
Try this:
def enqueue_image
ImageWorker.perform_async(id, key) if key.present? && !image_processed
end
This will check if the image was processed once before. I think this was probably meant to be set in the rails cast but the author forgot about it, otherwise the image_processed flag is obsolete.