I have a Rails 3 app that defines a few non-ActiveRecord models. All of these are defined in app/models/module_name. I have the following in all_autoload_paths:
[4] pry(main)> app._all_autoload_paths
=> ["/Users/mandar/myapp/lib",
"/Users/mandar/myapp/app/assets",
"/Users/mandar/myapp/app/controllers",
"/Users/mandar/myapp/app/helpers",
"/Users/mandar/myapp/app/models"]
Is there a difference between how the following 2 class definitions are handled when loading models?
module A
class X
# some code
end
end
and
class A::Y
# some code
end
The reason I ask this is sometimes I've seen a uninitialized constant A::Y - NameError error.
Update: Following Sibevin's answer, I'd like to make this clearer. Currently, I have the following file structure:
app
- models
- a
- x.rb
- y.rb
I have seen the error for Y which uses the ModuleName::ClassName syntax, but almost never for X.
Thanks for the help!
They should be no difference, but I usually separate them into individual files.
You can create a folder app/models/a/ first and put your class A::X in app/models/a/x.rb.
Similarly, app/models/a/y.rb for class A::Y.
BTW, app/models/a.rb for your module A if needed.
UPDATE:
Actually, I never use the 2nd format to declare a class in a module. Maybe the following doc can answer your question:
Everything you ever wanted to know about constant lookup in Ruby
If you've ever tried to take a short-cut when re-opening a module, you
may have noticed that constants from skipped namespaces aren't
available. This is because the outer namespaces are not added to
Module.nesting.
Related
Say I have two standard rails resources defined as controllers, each with their own CRUD actions (index, show, etc...)
EmployeesController
Employees::AssignmentController
Directory looks like -
app/
controllers/
- employees_controller.rb
employees/
- assignment_controller.rb
I'm trying to figure out how to best define the second nested controller to work with Rails autoloader.
Option A - Namespaced Constant
If I do -
class Employees::AssignmentController < ApplicationController
...
end
I get the following error in my app log and when running integration tests:
LoadError: Unable to autoload constant Employees::AssignmentController, expected /Users/jeeves/git/my-project/app/controllers/employees/assignment_controller.rb to define it>
It doesn't like the constant defined as one single namespace - Employees::AssignmentController
Option B - Nested Constant inside a module
If I do -
module Employees
class AssignmentController < ApplicationController
end
end
I actually get the same LoadError error as above.
Option C - Nested Constant inside a class
Finally, if I do -
class Employees
class AssignmentController < ApplicationController
end
end
I get TypeError: Employees is not a class, which makes sense since the outer class isn't defined. I try and fix it by adding an explicit class definition with an empty file
app/
controllers/
- employees_controller.rb
+ - employees.rb
employees/
- assignment_controller.rb
class Employees
end
And this works. But it feels silly to have this extra dummy file hanging around in app/controllers, especially when it's not even a controller itself.
Question
Is there a correct way to approach the above situation? Is Option C my only option? Am I even correctly using namespaces or should I avoid them all together?
Thanks!
Define (and reopen) namespaced classes and modules using explicit
nesting. Using the scope resolution operator can lead to surprising
constant lookups due to Ruby’s lexical scoping, which depends on the
module nesting at the point of definition.
- The Ruby Style Guide
An example of this suprising behavior is:
class Employees::AssignmentController
def index
#rate = Rate.find(...)
end
end
You would expect Rate to be resolved as Employees::Rate but its actually resolved to ::Rate since the lexical scope is ::.
This is the preferred method:
module Employees
class AssignmentController
def index
#rate = Rate.find(...) # resolves to Employees::Rate
end
end
end
It reopens the module and defines a new constant in the correct lexical scope.
As to alternative C - its possible since classes in Ruby are modules (check Class.ancestors if you don't belive me) but its not considered good form since classes should be things that can be instantiated.
The rails generators do this the "wrong" way by using the scope resolution operator but thats really just due to limitations in the file templating system.
Then why am I getting a load error?
I don't actually know. This is the preferred method and there is no obvious reason why its not working in this case. Double check everything. Restart spring by running spring stop and restart the server.
in my Rails app I'm making liberal use of class namespacing like so:
class Person
class PrimaryEmailAddress
class Update < Trailblazer::Operation
def persist(options, **)
Shared::Property::HasOne::Update.(
{property_class: PrimaryEmailAddress, property_value: #params[:email_address]},
'current_user' => self['current_user'], parent_node: options[:person])
end
end
end
end
Unfortunately, the ruby interpreter keeps thinking that my namespaced, embedded functions are part of other class' namespace, and it throws uninitialized constant errors. i.e. at runtime I get an error like this
uninitialized constant Person::Shared::Property
Basically, the interpreter is looking at this function Shared::Property::HasOne::Update and treating it like it's in the Person namespace, when it's not, and then throwing an error.
I can get around the problem by explicitely stating that the function is in the Object namespace, like so Object::Shared::Property::HasOne::Update, but adding Object:: all over the place is annoying and ugly. Is there a better solution anyone knows of? Short of rewriting all of my class names / namespacing.
I imagine part of the problem is that Person::Shared is a valid namespace, and so the ruby interpreter starts treating the Shared::Property:: ... reference like I just forgot to add Person to the beginning.
I really appreciate any feedback!!
Found the answer: by adding :: in front of a class reference, I force the ruby interpreter to look at the top level namespace. i.e. instead of Object::Shared::Property::HasOne::Update I can do ::Shared::Property::HasOne::Update, which I find more readable.
While there are a fair number of questions about uninitialized constant problems, I had trouble finding this answer because all the questions I found were framed in specific cases, rather then being genericized. It seems likely that this is a duplicate question and I just haven't found the other one, but I'm going to post this QA here in case I'm wrong and this helps someone else running into this same problem.
This question ended up leading me to the correct answer.
I'm running Rails 3.2.7,
I have a folder '/app/jobs'
and the following in my 'config/application.rb' file
config.autoload_paths += %W(#{Rails.root}/app/jobs)
And everything is okay.
However if I want to namespace my classes eg
class Jobs::UpdateGameStatus
#methods etc
end
Rather than
class UpdateGameStatus
#methods etc
end
Then I get
uninitialized constant Jobs (NameError)
It's not the end of the world but I'd love to know why...
I fixed it in the end, wrapping all my classes with a Jobs module was what I needed to do.
my files were located in 'app/jobs'
and looked like this
module Jobs
class JobName
#methods etc
end
end
and are used like so
Jobs::JobName.method(args)
I know you have already sorted this out, and this is old, but in ruby, it is also possible to declare the namespaced class directly using class Jobs::JobName. It's a little less typing, and achieves the same result.
Edit: As #D-side pointed out, Jobs has to already be defined. My own code that uses this is based around STI, which presumes that the previous class/module I am extending already exists.
I am using Ruby v1.9.2 and Ruby on Rails v3.2.2. I have many model classes having constant statements. For instance:
# app/models/class_one.rb
class ClassOne < ActiveRecord::Base
CONSTANT_ONE = ClassTwo::CONSTANT_TWO
end
# app/models/class_two.rb
class ClassTwo < ActiveRecord::Base
CONSTANT_TWO = 1
end
When I restart the server, I get the following error:
Routing Error
uninitialized constant ClassTwo::CONSTANT_TWO
Try running rake routes for more information on available routes.
Is the error related to the loading order of files (and so of classes)? How should I solve the problem?
Note: Since Ruby on Rails, I heard that a "working" solution could be to state constants in initializer files (in the config/initializers/ directory). If so, how should that be made the proper way? What do you think about?
Constants in Rails are kind of a pain, as you are beginning to find out. The pain only increases as you really dig in. It is much easier and more maintainable to use an actual method on the class than to use a constant. For example, in testing, it is MUCH easier to modify a method than a constant when covering a variety of use cases. Also, when doing more complicated programming, you can begin to get into loading issues (like multiple loading errors, or unavailability, like you have) that just don't happen with methods. I've stopped using constants in my Rails apps altogether, and haven't missed them a bit. You may be interested in an article that Advi Grimm wrote to the same effect.
Edit:
If you really desire to use constants, in the way you described, check out Where's the best place to define a constant in a Ruby on Rails application? for more info.
Those 2 classes are defined in the same file? wow. Reorder the classes:
class ClassTwo < ActiveRecord::Base
CONSTANT_TWO = 1
end
class ClassOne < ActiveRecord::Base
CONSTANT_ONE = ClassTwo::CONSTANT_TWO
end
should fix it. CONSTANT_ONE = ClassTwo::CONSTANT_TWO is evaluated as soon as it's parsed.
When you create a namespaced model with rails scaffolding, you get two files. For example, this scaffold:
rails generate model Staff::Location name:string address:string
Generates these files:
/app/models/staff.rb
module Staff
def self.table_name_prefix
"staff_"
end
...
/app/models/staff/location.rb
class Staff::Location < ActiveRecord::Base
...
I am running into problems when in development mode where rails unloads the Staff module and never reloads it. This causes several annoying bugs such as Location not able to access it's table due to the missing table_name_prefix. The problem seems to crop up when I don't access the models directly, such as through a polymorphic relationship.
I can't seem to get the module loaded on a consistent basis. Is this the best practice way to do namespaced models? If it is, what am I missing?
Although I wasn't able to reproduce the problem in Rails 3.2.2, I've run into something like this before. The generic way to hack around this problem in development mode is through an ActionDispatch callback. Add this to config/environments/development.rb:
MyApp::Application.configure do
ActionDispatch::Callbacks.before do
load Rails.root.join('app', 'models', 'staff.rb')
end
end
Anything you do in that block will be executed before each request, so make sure you're only doing it in development mode.† Otherwise, you're going to suffer a performance hit in production.
I logged a message inside the staff.rb file and within the Staff module itself, and both messages appeared in the log for each request.
† I tried using the to_prepare callback, since that seems to be the documented way to execute code before each request only when cache_classes is false. But that only seemed to execute after restarting the application. There's at least one other open Stack Overflow question regarding this, although he's using a slightly different syntax than I used. If you can get to_prepare to work, I'd suggest that instead of before.
About a year later, I have finally found the answer to this question. This answer is specifically for rails 3.1. I am not sure if it is a problem in rails 3.2.
The problem occurs when setting up a model. If scaffolding is used, no helper file is generated. This would normally be in /app/helpers/staff/location_helper.rb. There are two ways to setup this file:
module Staff::LocationHelper
...
end
module Staff
module LocationHelper
...
end
end
In rails 3.1, specifically for helpers, you must use the first solution. You do not have to use it for other modules that use a namespace in other parts of the rails project. In fact, some structures in ruby require the second solution.
If you use the second solution when declaring a helper, in certain cases the Staff module in the helper file will override the module in /app/models/staff.rb. It will silently replace it with the empty Staff module in the file. This does not happen 100% of the time because helpers are not always loaded.