How to require a class when building a ruby gem? - ruby-on-rails

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

Related

How to define a nested controller to work with Rails autoloader?

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.

Rails modules as strict namespaces

I'm quite new to rails and I'm a bit confused of how modules work here. I have a project structure like this:
# app/models/foo.rb
class Foo < ActiveRecord
# lib/external_service/foo.rb
module ExternalService
class Foo
# lib/external_service/bar.rb
module ExternalService
class Bar
attribute :foo, Foo # not the model
I have worked with many coding languages before and I expected it to be easily possible to use 'Foo' inside Bar and ExternalService just like that but
LoadError: Unable to autoload constant Foo, expected lib/external_service/foo.rb to define it
The ExternalService::Foo should normally not even be visible outside of ExternalService but the whole project dies on this thing
Am I just missing a kinda 'strict mode'-notation or anything to make sure that I obviously mean ExternalService::Foo inside the service and prevent the service from killing my model?
I know I can just prepend the module but i wanna keep the code readable.
so you are using rails 4
if you want to create a module, first you need to import or autoload your lib folder
for example in application.rb you can add lib folder to autoload:
config.autoload_paths << Rails.root.join('lib')
after that because you are using rails you should create a folder hierarchy with snake cased name of your module hierarchy
for example if you have:
module ExternalService
class Foo
...
end
end
your foo.rb file should be in a folder with name 'external_service'
{{project_root}}/lib/external_service/foo.rb
folder hierarchy is convention of rails.
Ruby behaves just like this and it's totally ok.
In this case the Foo-Model is already loaded, so ruby prefers this instead of the local one. Also alphabetically app/ is before lib/
A not so beautiful but quick fix is just to call it like this:
attribute :foo, ExternalService::Foo

How to namespace in ruby

I've got a rails model called LeadSource
class LeadSource < ActiveRecord::Base
end
I also have in the lib directory, the following structure:
-lib
-reports
base.rb
lead_source.rb
in the lead_source.rb, i've got this defined:
module Reports
class LeadSource < Reports::Base
end
end
When I call LeadSource.new in the console, I get the following error:
LoadError: Unable to autoload constant LeadSource, expected ..../lib/reports/lead_source.rb to define it
I am under the assumption that the module Reports should be namespacing my LeadSource class. (allowing me to have two classes named the same thing). But why in this case, when I try to instantiate a new LeadSource object (active record model) is rails looking in lib and throwing a fit about my class defined there?
I would expect to call the active record model like this:
LeadSource.new
and the class in my lib like this:
Reports::LeadSource.new
without them colliding and causing issues.
EDIT:
I feel like this defeats the purpose of namespacing if the solution is to just change the class name as pointed out here as well as the similar question marked as duplicate of this one.
Am I wrong in thinking that a namespace should silo off the resource and not just make it so i have to type more to call it? seems silly to me.

Rails, Ruby classes / modules / namespaces confusion

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.

Why does structuring my code in this manner cause a toplevel constant warning and how can I fix it?

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

Resources