This is odd, but:
Uploader class (app/uploaders):
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
# ....
version :thumb, from_version: :preview do
process resize_to_limit: [Image::THUMB_WIDTH]
end
Image class (app/models):
class Image < ActiveRecord::Base
include Rails.application.routes.url_helpers
mount_uploader :image, ImageUploader
THUMB_WIDTH = 220
PREVIEW_WIDTH = 460
MAX_WIDTH = 960
The application says:
uninitialized constant Image::THUMB_WIDTH
version :thumb, from_version: :preview do
process resize_to_limit: [Image::THUMB_WIDTH] #<<<----
end
version :preview, from_version: :fullsize do
What's wrong?
UPDATE:
Agis pointed out the reason.
Bounty for the best solution to this problem will be applied in 2 days. I don't like code separation, e.g. making a new class holding all the constants for the Image class in initializers etc. This solutions is bad because it it brings inconsistency and code fragmentation.
Ruby NameError Uninitialized Constant Causes. The Uninitialized Constant error is a variation of a regular NameError exception class. It has several possible causes. You'll see this error when the code refers to a class or module that it can't find, often because the code doesn't include require, which instructs the Ruby file to load the class.
rails nameerror uninitialized constant class will occur if your rails console is not loaded with configuration of the class file containing method being called. Basically this can happen if you happened to call the function from the class which is not loaded with configuration when your rails console/server was started.
However, Ruby is not tolerant of reparsing the same class definition if it has constants declared in it. When those constants are re-initialized, they will generate the warning already initialized constant X, as shown below: Unlike load, require is idempotent in Ruby. It returns false and does nothing on subsequent invocations:
Doing so will wind up loading the file twice… making this the most likely cause of “already initialized constant X” warning. By default Rails is configured to autoload from any sub-folders of app/ Your project may be configured to auto-load other folders like lib/ since that was in the default auto-load list for early versions of Rails.
You have a chicken or egg case as far as rails autoloader is concerned, the "rails way" of solving this in a general sense would be to refactor the metacode so that the class names get passed as string values rather than classes, for example:
belongs_to :manager, class_name: "Employee"
belongs_to
will call constantize
on class_name
hopefully at a time when all classes have been loaded so the chicken and egg issue is circumvented "the rails way".
What @Stoic suggested is essentially a variation of this theme of circumventing the evaluation of image.rb
at image_uploader.rb
load time:
model.class.const_get("THUMB_WIDTH")
could also have been phrased as:
'Image'.constantize.const_get("THUMB_WIDTH")
and result is the same and the general lesson to take from this is: avoid using another class name literals in class load-time code or in other words belongs_to :manager, class_name: "Employee"
is good and belongs_to :manager, class_name: Employee
would be bad.
It's not pretty, but its probably the most elegant universal way to avoid these headaches
A different way of looking at the problem is that thumbnail icon width is actually a concern of the uploader and not of the model and that you're in fact seeing a fringe case of inappropriate intimacy code smell (http://www.codinghorror.com/blog/2006/05/code-smells.html).
Watch out for classes that spend too much time together, or classes that interface in inappropriate ways. Classes should know as little as possible about each other.
So, if you are of this school of thought (I'm leaning in this direction) the solution would be to make THUMB_WIDTH
a constant of the ImageUploader
class and the problem goes away.
It's generally a good idea to separate different domain concerns out of the models anyway as the models like to get bloated and unmanageable - you could view the uploader class as a service class of your model designed to extract a particular domain problem much in the same way value objects, form objects etc get handled in http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
Plan C would be to switch places of config.autoload_paths
in your application.rb
and cross fingers :)
This happens because the code for the ImageUploader
class is evaluated before the one for the Image
class. So at the point where Image::THUMB_WIDTH
is evaluated, the constant lookup happens and searches for the Image
constant but does not find it since the relevant file is not loaded yet.
You can fix this by adding this definition at the top of your ImageUploader
file:
class Image; end
You could also do the same but in an initializer instead (ex. config/initializers/image.rb
).
This would make sure that the Image
class is loaded at the beginning of the boot process, before everything else and may be considered a clearer solution to you in case you don't want to have definitions of different classes in the same file.
I think you should probably move the constants out of the class, and into the wider namespace in an initializer. Or you could create a method on the image class to access the constant instead. This may or may not alleviate your problems.
class Image
THUMB_WIDTH = 220
def self.thumb_width
THUMB_WIDTH
end
end
process resize_to_limit: [Image.thumb_width]
Rails provides a convenient place to store configuration data: Rails.application.config
. This will solve your dependency problem and nicely separate the configuration from the logic. I know you said you prefer not to separate them, but I think this is pretty clean (better than creating a constants module).
In config/application.rb:
config.image_sizes = {
thumb_width: 220,
preview_width: 460,
max_width: 960,
}.with_indifferent_access
In app/uploaders/image_uploader.rb:
version :thumb, from_version: :preview do
process resize_to_limit: [Rails.application.config.image_sizes[:thumb_width]]
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With