Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby on Rails. Uninitialized constant

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.

like image 480
Nemoden Avatar asked Jan 10 '14 10:01

Nemoden


People also ask

What does uninitialized constant mean in Ruby?

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.

What is uninitialized constant class error in rails?

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.

Is it possible to use constants in the same class in Ruby?

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:

Why does my rails app say already initialized constant x?

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.


4 Answers

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

like image 159
bbozo Avatar answered Oct 02 '22 08:10

bbozo


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.

like image 26
Agis Avatar answered Oct 02 '22 08:10

Agis


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]
like image 24
S.Spencer Avatar answered Oct 02 '22 07:10

S.Spencer


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
like image 37
Nick Urban Avatar answered Oct 02 '22 08:10

Nick Urban