Rails + carrierwave not creating conditional versions - ruby-on-rails

I'm trying to set up conditional versions with carrierwave in my Rails app. I've implemented what seems to be an exact duplicate of the examples provided here.
The version is never created though unless my is_ipod? simply returns true. The code below is what I currently have and is not working. Notice the commented sections I've used to verify the image_type attribute is actually set correctly.
version :ipod_portrait_thumb, :if => :is_ipod? do
process resize_to_fit: [150,200]
end
def is_ipod? image
model.image_type == 'iPod Screenshot'
#if (model.image_type == "iPod Screenshot")
#if (model.image_type!=nil)
#puts "+++++"+model.image_type
# if (model.image_type=="iPod Screenshot")
#puts "+++++++ I AM HERE"
# return true
# end
#end
end
If is_ipod? looks like this:
def is_ipod? image
true
end
the version is created as expected. What am I missing? Thanks!
UPDATE:
I've edited the is_ipod? method to look like this:
def is_ipod? image
puts (image.path || "") + ': ' + ((model.image_type||"") == 'iPod Screenshot').to_s
model.image_type == 'iPod Screenshot'
end
Which outputs this to the console:
/public/uploads/tmp/20130325-1024-15906-5363/drawing.png: false
/public/uploads/tmp/20130325-1024-15906-5363/drawing.png: false
/public/uploads/app_image/image/59/drawing.png: true
So the version is trying to be created three times, twice for temp files and once for the final file. The model attribute is only set for the final file. Is this related? Can anyone tell me how this is different than this example?
class MyUploader < CarrierWave::Uploader::Base
version :monkey, :if => :is_monkey?
protected
def is_monkey? picture
model.favorite_food == 'banana'
end
end
Here is my model class in case that helps:
class AppImage < ActiveRecord::Base
attr_accessible :app_id, :image, :image_type, :image_cache
belongs_to :app
mount_uploader :image, AppImageUploader
validates :image_type, presence: true
validates :image, presence: true
end
Thanks!

I had a similar problem^ and I tried to create different sizes depending on model attribute. My solution is simple: just recreate versions after save.
app/uploaders/project_item_picture_uploader.rb
class ProjectItemPictureUploader < CarrierWave::Uploader::Base
...
version :preview do
process dynamic_process: true
end
protected
def dynamic_process(*args)
resize_to_fill *(model.get_size) if model.persisted?
end
end
app/model/project_item.rb
class ProjectItem < ActiveRecord::Base
mount_uploader :picture, ProjectItemPictureUploader
validates_presence_of :picture
def get_size
double ? [168, 57] : [76, 57]
end
after_save :recreate_delayed_versions!
def recreate_delayed_versions!
picture.recreate_versions!
end
end

It turns out this is happening because the AppImage model is a child of another model and is being added here on a nested form. For whatever reason child models don't have their attributes set by the time carrierwave processes versions.
I've verified this by adding carrierwave attachments to my parent model App. When the versions are processed for an App attachment, the attributes are set.
Maybe later I'll dig deeper to try and understand better (I'm pretty new to Rails), but for now I'm working around the issue by not having conditional versions.

I have the same issue this night and i'm doing some experimentation with it, finally i figured out, it seem that when your create a version for the first time (when you post your form), if you have this line :
model.image_type == 'iPod Screenshot'
this means that you should obligatory have a field in your form named image_type and the value for this field should be "iPod Screenshot"
CarrierWave verify the same thing when you show your image like article.image_url(:ipod_portrait_thumb) which means that you should have in your database a field named image_type with value "iPod Screenshot"
so if you have this line :
version :ipod_portrait_thumb, :if => :is_ipod?
you are telling CarrierWave to execute ipod_portrait_thumb function each time you Create or Show a record
in Case that your form don't contains the field image_type with value 'iPod Screenshot', for example because you set it in your model/controller, the way is to check another field to permit CarrierWave create your conditional version, so it's simple you can do something like this :
model.image_type == 'iPod Screenshot' || another_field_in_my_form_help_you_to_know_type_is_ipode == "something" # or just .present? rather than == "something" switch your case
here when CarrierWave try to create version the first time it will check this another_field_...... , and when you have a page that show your record (product, article....) it verify model.image_type == 'iPod Screenshot' that is stored already in your database
Hope this help you :)

Related

File does not get stored (mounted) if processing of versions fails

I upgraded carrierwave from 0.11.0 to 1.2.3 and realised that a, for me crucial, behaviour has changed and broke my logic. Here is the example of my uploader.
class FileUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :fog
version :thumb do
process :convert => :jpg
def default_url
'/assets/document_thumb.png'
end
end
end
And the model that it's mounted too:
class Material < ActiveRecord::Base
attr_accessible :name, :file
mount_uploader :file, FileUploader, validate_processing: false
before_create :create_file_hash
def create_file_hash
self.hash_digest = Digest::MD5.hexdigest(file.read)
end
end
In the old carrierwave, even if the version processing (e.g. in this case the convert) failed the main version of the file would still be uploaded and stored. However, now in cases when processing fails (not always, but I can't do conditional processing as my case is more complex then here illustrated) nothing gets stored. The file attribute remains an empty (blank) uploader and nothing is uploaded to the fog storage.
Any idea on how to get back the old behaviour?
In other words, how to ignore any errors with processing of versions. Or not trigger processing of versions in the after_cache callback but rather some later time down the line?
I think I've tracked down this issue to the following change in Mounter#cache method:
def cache(new_files)
return if not new_files or new_files == ""
#uploaders = new_files.map do |new_file|
uploader = blank_uploader
uploader.cache!(new_file)
uploader
end
#integrity_error = nil
#processing_error = nil
rescue CarrierWave::IntegrityError => e
#integrity_error = e
raise e unless option(:ignore_integrity_errors)
rescue CarrierWave::ProcessingError => e
#processing_error = e
raise e unless option(:ignore_processing_errors)
end
Which used to just do the uploader.cache!(new_file) directly (not in map) and then uploader got updated along the way and returned to the model when needed. However, now the processing error causes the map block to exit and #uploaders array never gets updated with the uploader that worked (i.e. for the original file).
One possible solution would be overriding the cache! method in you uploader instead:
class FileUploader < CarrierWave::Uploader::Base
def cache!(*)
super
rescue CarrierWave::ProcessingError => e
Rails.logger.debug "FileUploader: Error creating thumbnail: #{e}"
nil
end
...
end
That way, it works for every model
Half a day of effort later here is the solution I've come up with that doesn't involve monkey patching carrierwave.
class Material < ActiveRecord::Base
attr_accessible :name, :file
mount_uploader :file, FileUploader, validate_processing: false
#----> This is to manually trigger thumbnail creation <----
before_create :create_thumbnail
def create_thumbnail
file.thumb.cache!(file.file)
rescue CarrierWave::ProcessingError => e
Rails.logger.debug "FileUploader: Error creating thumbnail: #{e}"
end
# rest of the model code
end
So here we have create_thumbnail method triggered in before_create callback that manually calls the cache! method on the thumb uploader. The file.file is at this moment (i.e. before create, so before the file has been uploaded to the storage) pointing to the temporary cached file. Which is exactly what we want (we don't want to re-download the file from the storage just to create thumbnails.
class FileUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :fog
#----> Add the if condition to versions <----
version :thumb, if: :has_versions? do
process :convert => :jpg
#----> This is needed to trigger processing later <----
after :cache, :process!
def default_url
'/assets/document_thumb.png'
end
end
#---> This is to avoid versions before the main version is fully processed and cached <---
def has_versions?
!(model.new_record? && model[:file].nil?)
end
end
Now this is the tricky party. We need to initially disable the version creations and for that reason we have the has_versions? method that checks if the file is a new record. Now that check is not enough, because in our before_create callback the model is still new record (i.e. it hasn't yet been persisted).
However, what's the difference between the first time the uploader tries to create the versions (and which, if it fails, prevents original file from caching as described in the question) and the moment we call it in our before_create callback is that in the second case the file attribute of the model will be set.
Be careful, however, because you cannot do model.file since that points to the uploader (and if called here where I'm calling it it would actually cause a stack overflow). You need to access it as model[:file].
The final trick is that for some reason just calling cache! in the model would not actually trigger the processing. The processing was supposed to be triggered during the initial run (which we prevented for other versions) and since the original file is cached, carrierwave expects the versions are as well, so they don't need processing. But adding the after :cache, :process! ensures that it's triggered.
Don't know if anybody will find this useful or if I've gone about the problem the wrong way.
Either way, I'd love to hear comments.
Happy I made it work for my case and that I can continue using latest gem version.

Paperclip, before_save, and deleting attachments

I can't get this before_save filter to work. My methods are pretty standard, I think. Images are uploaded via Paperclip.
before_save :remove_checked_attachments
def attachments
%w(banner footer logo accreditation)
end
private
def remove_checked_attachments
attachments.each do |a|
if "remove_#{a}".to_sym && !"#{a}_updated_at_changed?".to_sym
"#{a}".to_sym.destroy
end
end
end
The remove_... params are passed, nothing's deleted though:
... "remove_banner"=>"1" ...
Any thoughts? Thanks.
Update
Even simplifying it to this doesn't work:
after_validation { banner.clear if remove_banner == '1' }
And "remove_banner"=>"1" comes through in the params. The u.banner.clear then u.banner.save works fine in the console.
I've solved this by making a concern like so:
# must be included after attachment declarations in model
module RemoveAttachment
extend ActiveSupport::Concern
included do
attachment_definitions.keys.each do |name|
attr_accessible :"remove_#{name}"
attr_accessor :"remove_#{name}"
before_validation { send(name).destroy if send("remove_#{name}") == '1' }
define_method :"remove_#{name}=" do |value|
instance_variable_set :"#remove_#{name}", value
send("#{name}_file_name_will_change!")
end
end
end
end
And just including the concern wherever need be. Thanks to this answer for a huge clue.

Upload apk using carrierwave and store both apk and extracted icon

I have a App model which has_many Versions
The versions model has an apk and an icon field (among others).
I currently have a form that allows users to upload an .apk file using carrierwave (class VersionUploader < CarrierWave::Uploader::Base).
Once the apk file is uploaded I've used the ruby_apk gem to extract the icon (from within versions_uploader.rb).
For the icon, the ruby_apk gem returns an array of hashes in the following format
{ "res/drawable-hdpi/ic_launcher.png" => "\x89PNG\x0D\x0A...", ... }
I would like to save the icon to disk and have it accessible the same way I would the apk file (By this i mean be able to call things like version.icon.identifier on it)
I've been stuck on this for a while now. Any help regarding how it should be done or what approach would be best would be greatly appreciated.
Thanks in advance!
This gets a lot easier if you use two different uploaders, and set the icon data from the model layer. Model code could look something like this:
mount_uploader :apk, ApkUploader
mount_uploader :icon, IconUploader
before_save :assign_icon
def assign_icon
if apk_changed?
icon_data = Android::Apk.new(apk.path).icons.values.first
self.icon = StringIO.new(icon_data)
end
end
Note that before_save has to come after mount_uploader :apk, because mount_uploader creates its own callbacks on the before_save event, and you want to fire after them.
I finally managed to get this working. Taavo's answer set me in the right direction but it didn't work as expected.
The main problem was that StringIO.new(icon_data) kept giving me a no implicit conversion of nil into string error. After quite some digging I found that I needed to add original_filename as an attribute to StringIO, but it has long since stopped accepting this without monkey patching the class. I found the solution to this problem from the Carrierwave wiki psge: How to: Upload from a string in Rails 3.
This now let me save the icon to the file-system but mysteriously, was not populating the Version.icon field with the reference (Version.apk was populated without any problems).
In the end, to get it working I had to get rid of the before_Save :assign_icon callback and moved my code into the ApkUploader file.
My code looks something like this:
1) Created a new initializer that inherits from StringIO
config/initializers/stringiohax.rb
class AppSpecificStringIO < StringIO
attr_accessor :filepath
def initialize(*args)
super(*args[1..-1])
#filepath = args[0]
end
def original_filename
File.basename(filepath)
end
end
2) Mount Uploaders
app/models/version.rb
class Version < ActiveRecord::Base
mount_uploader :apk, ApkUploader
mount_uploader :icon, IconUploader
3) Extract data from APK and save icon attribute
app/models/version.rb
class ApkUploader < CarrierWave::Uploader::Base
include Sprockets::Rails::Helper
include CarrierWave::MimeTypes
require 'ruby_apk'
..
...
process :extract_apk_info
def store_dir
app = App.find(model.app_id)
studio_name = Studio.find(app.studio_id).slug
"uploads/#{studio_name}/#{app.id}/version_#{model.version_code}"
end
def extract_apk_info
apk = Android::Apk.new(model.apk.path.to_s)
manifest = apk.manifest
icons = apk.icon
icon_name = icons.keys.last
icon_data = icons.values.last
model.package_name = manifest.package_name
model.version_code = manifest.version_code
model.version_name = manifest.version_name
model.min_sdk_ver = manifest.min_sdk_ver
model.icon = AppSpecificStringIO.new(icon_name, icon_data)
end

Displaying a Carrierwave filename in the view

I am trying to display the filename of a Carrierwave attachment in a Rails erb template. The following does not work:
<%= #page.form.filename %>
This seems in line with the documentation. Is some additional step needed?
My page model looks like this:
class Page < ActiveRecord::Base
mount_uploader :form, FormUploader
end
The form uploader looks like this:
class FormUploader < CarrierWave::Uploader::Base
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def extension_white_list
%w(pdf)
end
end
I have been able to get the filename via the file internal parameter:
<%= #page.form.file.filename %>
The documentation you're looking at is the sanitized file, it's what it uses for actually storing a file. The part you're looking for is FormUploader, which is an Uploader, and part of http://rubydoc.info/gems/carrierwave/0.5.2/CarrierWave/Uploader
If you want to get the file name, you could either read it from the database column directly, or use File.basename(#page.form.path) to extract it easily.
The Carrierwave docs might be a bit off, but recommended way seems to be:
#page.form.file.identifier
#adamonduty's solution is great. Another solution I used before, just create a method on the model:
def name
file.path.split("/").last
end
You're right #epylinkn. Documentation points towards using:
#page.form.file.identifier
But when I use that, I always get nil (just as #Cheng commented).
I then inspected my objects methods (#page.form.file.methods.inspect), and found the following to work:
#page.form.file_identifier
In your model's associated uploader class, define a filename method.
def filename
File.basename(path)
end
You can then call
model_instance.file.filename
Works as of CarrierWave 1.1.0. This is a succinct restatement/amalgamation of kikito and Chris Alley's responses above.
If you're using ActiveRecord, you can directly access the field named form in two ways:
def my_method
self[:form]
end
or
def my_method
form_before_type_cast
end
The second method is read-only.
CarrierWave::SanitizedFile has a private original_filename method containing the filename of the uploaded file. (docs: http://rdoc.info/github/jnicklas/carrierwave/master/CarrierWave/SanitizedFile:original_filename)
After reading through this thread from the CarrierWave mailing list, none seemed to fit my needs. With something like
class Upload < ActiveRecord::Base
mount_uploader :file, FileUploader
# ...
I heavily modify the :file column value from the original filename. Due to this I decided to track the original filename in a separate column from the one bound to CarrierWave. In my FileUploader I simply added a reader that wraps the private original_filename method:
def original_file
original_filename
end
I then added a before_create event to the Upload class (my Upload records are never modified, so a before_create is acceptable for my needs)
before_create do
self.original_file = self.file.original_file
end
I'm assuming you've got models like this?
class Page
mount_uploader :form, FormUploader
end
If so you should be able to call:
#page.form.url
#page.form.filename
Are you sure you've uploaded/attached the file correctly? What do you see when you inspect #page.form? Remember, the attachment will not be saved until you've fully processed the upload.
This is my solution:
before_save :update_file_attributes
def update_file_attributes
if file.present? && file_changed?
self.content_type = file.file.content_type
self.file_size = file.file.size
self.file_name = read_attribute(:file)
end
end

Rails 3 Observer -- looking to learn how to implement an Observer for multiple models

I have the following Observer:
class NewsFeedObserver < ActiveRecord::Observer
observe :photo, :comment
def after_create(record)
end
end
What'd I'd like to learn how to do is add a SWITCH/IF statement in the after_create, so I know which model was created
Something like:
after_create(record)
switch model_type
case "photo"
do some stuff
case "comment"
do some other stuff
end
Or easier to visualize:
if record == 'Photo'
How can I take record, and determine the model name?
In a comment, I notice you found this works using record.class.name but that's not very idiomatic Ruby. The Ruby case statement uses === for comparison which will work perfectly for you if you implement it properly.
class NewsFeedObserver < ActiveRecord::Observer
observe :photo, :comment
def after_create(record)
case record
when Photo
# do photo stuff
when Comment
# do comment stuff
else
# do default stuff
end
end
end
This essentially converts to:
if Photo === record
# do photo stuff
elsif Comment === record
# do comment stuff
else
# do default stuff
end
I advise you to note the following:
class Sample
end
s = Sample.new
Foo === s # true uses Class#===
s === Foo # false uses Object#===
=== is implemented differently in Class and Object
You need to setup separate observers for separate models
So for User => UserObserver , Photo => PhotoObserver
You need to tell the rails app what observers to use, that you specify in config/environment.rb
Atleast this is the standard way. For more details
http://guides.rubyonrails.org/active_record_validations_callbacks.html#observers

Resources