I have a Rails model: app/models/A.rb -> class A < ActiveRecord::Base
Then I have: app/services/a/b/c.rb -> class A::B::C, where both a and b are folders. AFAIK, app/<foldername> is autoloaded, so that is not an issue.
And, finally, I have an module: module B (it is an external gem with this name).
If I attempt to do this: A::B::C.new it throws an error message: toplevel constant ... referenced by ... (Don't remember exactly) - in other words, it does NOT work.
Other classes defined under a folder - work just fine. For example: app/services/a/x.rb (class A::X)
If I try A::B, I simply get to B (the gem), with a warning message (but it continues to work).
But, if I rename either a or b folder name (and class signatures of course) - it works just fine!
So,
1) Why is that? How does ruby handle all these classes, modules, namespaces, inclusions?
2) Is it possible to have everything working w/o renaming a single thing?
Thanks in advance!
You may need an intermediate A::B declared in a/b.rb to properly define the module A::B before you reference it, or you can re-style your declaration in your final file.
For example:
# a/b/c.rb
class A::B::C
end
This depends on module or class A and A::B being defined before that file is loaded.
You can side-step this:
class A
module B
class C
end
end
end
That will force-declare all the intermediates. As this introduces a lot of indentation, a slightly less messy approach is:
class A
module B
end
end
class A::B::C
end
Of course, having an a.rb and a/b.rb with the appropriate intermediate declarations often helps avoid all of this.
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.
I am creating a gem, here is my structure :
lib/
| myapp.rb
| myapp/
| version.rb
| configuration.rb
| use_cases/
| base_use_case.rb
| entity1/
| show_entity.rb
spec/
myapp.gemspec
I would like to make my class ShowEntity inherit from BaseUseCase.
module MyApp::UseCases::Entity1
class ShowEntity < BaseUseCase
end
end
But I get the following error
in `<module:Entity1>': uninitialized constant MyApp::UseCases::Entity1::BaseUseCase (NameError)
In myapp.rb I required each classes but I still get the error.
This is the first time I try to really understand how modules and classes work together and to require them inside a gem project, so any help or explanations on why I get that error will be great.
In your code:
module MyApp::UseCases::Entity1
class ShowEntity < BaseUseCase
end
end
Ruby will first look for BaseUseCase in the current module, MyApp::UseCases::Entity1. Assuming this class is defined under MyApp::UseCases to match your file structure, it won’t be found there. Ruby then looks in the enclosing scope to try to find the class. However, since you’ve used the double colon syntax, the enclosing scope is the top–level, not MyApp::UseCases as you may be expecting. Ruby “jumps over” all the namespaces in the statement module MyApp::UseCases::Entity1.
If instead you defined the class like this it should work:
module MyApp::UseCases
module Entity1
class ShowEntity < BaseUseCase
end
end
end
Here, when BaseUseCase isn’t found in the current scope the enclosing scope searched is MyApp::UseCases. Ruby steps back a single module statement at a time, and since there is now a separate declaration for Entity1, the next scope is MyApp::UseCases instead of the top level. If BaseUseCase were defined directly under MyApp instead then this code wouldn’t work either, since after failing to find it under MyApp::UseCases::Entity1 and MyApp::UseCases the next scope searched would be the top level.
To fix it you should break out your module declarations as needed as above, or explicitly define the fully namespaced name of BaseUseCase:
class ShowEntity < ::MyApp::UseCases::BaseUseCase
My application code is structured as shown below in Rails 3.2. If I go into the Rails console and enter Foo::Bar::FooBar it will return this warning:
warning: toplevel constant FooBar referenced by Foo::Bar::FooBar
Application code and files they are located in:
# app/models/foo/bar/foo_bar.rb
module Foo
class Bar
class FooBar
end
end
end
# app/models/foo/bar.rb
module Foo
class Bar
end
end
# app/models/foo_bar.rb
class FooBar
end
My autoload paths have not been changed from the Rails defaults.
One way I've been able to fix the issue has been to add the following code to Foo::Bar::FooBar. However, it feels dirty and I was wondering if there was a configuration option or some other thing I'm doing wrong that would fix the issue.
# app/models/foo/bar/foo_bar.rb
module Foo
# This line of code removes the warning and makes class methods execute
# on the Foo::Bar::FooBar class instead of the FooBar class.
class Bar; end
class Bar
class FooBar
end
end
end
The basic problem is that you're reusing the same name for different classes, in an overlapping scope. You might be able to do something clever to make Ruby resolve the constant name in the way you want, but this kind of "solution" is fundamentally brittle. (What if a new version of Ruby makes a tiny change to how constants are looked up, or you move to another Ruby implementation?)
Why don't you just namespace the top-level FooBar model class within a Module? (You'll have to move the file into a subdirectory with the same name as the Module.)
I am using a plugin in Rails, and I call its methods without problems:
plugin_module::class_inside_module.method_a(...)
I want to re-open the class_inside_module and add a new method, I tried in many different ways. I can't figure out why in this way doesn't work:
class plugin_module::class_inside_module
def new_method
puts 'new method'
end
end
I get the error: uninitialized constant plugin_module, but how is possible if I can call without problem plugin_module::class_inside_module.any_methods ?
Do you know why I get that error ? why "uninitialized constant" ? (it is a class declaration :-O )
Do you have any ideas how I can add a new methods in a class inside a module (that is part of a plugin) ?
Thank you,
Alessandro
If you have written your class and module-names like you did, so plugin_module instead of PluginModule this is against ruby/rails standards, and rails will not be able to automatically find the class and module.
If you write something like
module MyModule
class MyClass
end
end
Rails will expect this file to be located in lib\my_module\my_class.
But this can always easily be overwritten by explicitly doing a require.
So in your case, when you write
module plugin_module::class_inside_module
Rails will not know where to find the module plugin_module.
This way of writing only works if module plugin_module is previously defined (and loaded).
So either add the correct require, or rename your modules to standard rails naming, or write it as follows:
module plugin_module
class class_inside_module
This way will also work, because now the order no longer matters.
If the module is not known yet, this will define the module as well.
Either you are re-opening the class, or you define it first (and the actual definition will actually reopen it).
Hope this helps.
Have you tried reopening the module that's wrapping the class, rather than relying on ::?
module plugin_module
class class_inside_module
def new_method
puts 'new_method'
end
end
end
By the way, you know that the proper name for modules and classes is use CamelCase with a capital first letter?
module PluginModule
class ClassInsideModule
def new_method
puts 'new_method'
end
end
end
In my rails app I have a models/foo.rb and models/foo/exceptions.rb:
class Foo
include Foo::Exceptions
end
module Foo::Exceptions
class MySpecialException < Exception
end
end
At the rails console, I then see this:
>> Foo::MySpecialException
Foo::Exceptions::MySpecialException < Exception
>> Foo::MySpecialException == Foo::Exceptions::MySpecialException
true
>> Foo::MySpecialException === Foo::Exceptions::MySpecialException
false
What do you think of my file structure / namespacing?
Is my inclusion line in Foo necessary, or does Rails autoinclude those modules?
Why does MySpecialException exist in the top level Foo namespace and point to Foo::Exceptions::MySpecialException?
What does it mean that those two classes are == but not ===?
I explored this in irb but kept running into errors that were inconsistent with the behavior I (think) I've seen in Rails.
related: What is a conventional place to keep custom Exception definitions in a rails project?
What is the relationship between a ruby class and module with the same name?
A class and a module can't have the same fully qualified name in ruby. It's possible to have a class Foo::Bar and a module Baz::Bar, in which case there is no relation between the class and the module. However it is not possible to both have class Foo::Bar and a module Foo::Bar at the same time.
(I'm not sure what this has to do with the rest of your question though)
Is my inclusion line in Foo necessary, or does Rails autoinclude those modules?
Rails will not automatically include your modules. However that does not mean it's necessary to include yourself, you could just access it qualified. I.e. use Exceptions::MySpecialException instead of just MySpecialException inside the Foo class.
Why does MySpecialException exist in the top level Foo namespace and point to Foo::Exceptions::MySpecialException?
Because you included Foo::Exceptions into Foo. Because of that all instance methods of Foo::Exceptions are also instance methods of Foo and all constants of Foo::Exceptions are also constants of Foo - including MySpecialException.
What does it mean that those two classes are == but not ===?
== means that it's the same class. That it's not === means that the class is not an instance of itself (since x === y is the same as y.is_a?(x) if x is a class).
I suggest that you move exceptions to lib: lib/exceptions/exception_name.rb
Rails will auto load stuff in models dir, however for the lib folder, you gotta tell to do so by checking config/application.rb and adding this:
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]