Ruby on Rails. Uninitialized constant

20,573

Solution 1

A general answer

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 problem specific answer

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 :)

Solution 2

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.

Solution 3

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]

Solution 4

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

Solution 5

Your ImageUploader class is being autoloaded before THUMB_WIDTH has been defined. Change the definition order:

class Image < ActiveRecord::Base
  include Rails.application.routes.url_helpers

  THUMB_WIDTH = 220
  PREVIEW_WIDTH = 460
  MAX_WIDTH = 960

  mount_uploader :image, ImageUploader

end
Share:
20,573
Nemoden
Author by

Nemoden

Interested in: Photography, but I have no camera. Investing, but I have no money. Inventing, but I have no thoughts. Cooking, but I have no ingredients. Singing, but I have no voice. Making music, but I have no notes. Flying, but I have no wings.@Nemoden

Updated on July 09, 2022

Comments

  • Nemoden
    Nemoden almost 2 years

    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.

  • Stoic
    Stoic over 10 years
    I do like what you have stated (as I was naive not to think of that :P ), but the only problem with this method is that we will have to do this for every model we want this uploader to handle, IMHO.
  • Nemoden
    Nemoden over 10 years
    it says: undefined local variable or method 'model'
  • Nemoden
    Nemoden over 10 years
    Can I exploit Rails::Application::Configuration somehow?
  • Nick Urban
    Nick Urban over 10 years
    @Nemoden Yes, see my answer.
  • Nemoden
    Nemoden over 10 years
    A very thorough answer, a lot of useful information, good references. Thanks!
  • Nemoden
    Nemoden over 10 years
    interpreters! right! lines evaluated one-by-one. And I'm a 8 years-experienced developer (with a PHP background, but it doesn't really matter). Shame, shame, shame on me, how I could not see this in a first place? thanks!