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.
Related
I am working on a Ruby on Rails 6 project, and I am trying to use a class instance variable on an ActiveRecord model. Here is a basic example:
class Model << ApplicationRecord
#var = AnotherClass.new
class << self
attr_reader :var
end
# ...
end
I would then like to be able to use Model.var to access Model's instance of AnotherClass. There are multiple such models, each of them referring to a different AnotherClass, with all the AnotherClasses being subclasses of some BaseClass.
However, I am encountering the following error:
uninitialized constant Model::AnotherClass
Because of the class << self, Ruby seems to be looking for a nested class.
Is there a way to access AnotherClass directly, or is there a better way in general to set this up?
Edit: I solved this with a completely different approach, however I'm still interested to see how you would get around this issue.
The error you receive:
uninitialized constant Model::AnotherClass
Tells you that AnotherClass is not initialized (not loaded/found). Let me use the following context as an example:
class Model
AnotherClass
end
Ruby will start a constant lookup. This will start from the current namespace (Model) and and if nothing is found move up into the namespace tree. In the above example it will first look for Model::AnotherClass if that cannot be found it will look for AnotherClass, if that cannot be found it will throw the exception you receive.
This error simply tells you that AnotherClass is not loaded.
Anything in th app/ directory is loaded by the autoloader of Rails, however if you use the lib/ directory you have to manually require 'another_class' or add the relevant path to the autoload paths.
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 have created a model named "RandomStuff" inside namespace "Module".. but when i try Module::RandomStuff in rails console. it gives the following error
Module::RandomStuff(Table doesn't exist)
and cant access it anywhere in my rails app. when i run ActiveRecord::Base.connection.tables in irb the table "module_random_stuffs" is listed there..
Here are the steps i followed , Let me know if i am missing something
rails generate model Module::RandomStuff
then added one column in the migration file
class CreateModuleRandomStuffs < ActiveRecord::Migration
def change
create_table :module_random_stuffs do |t|
t.string :test_column
t.timestamps null: false
end
end
end
and then ran the following command
rake db:migrate
Still i am unable to access the model any where..
Thanks #DVG for pointing it out.
The following line was missing in my model Modul::RandomStuff
self.table_name = 'module_random_stuffs'
I just love these situations.
Sometimes, when Rails fails to load something it acts as if that something never existed. You can run into this issue if, for some reason, some constant (class or module at least, I've seen with both) had a mistake in its definition, but since it was optional, no error was thrown.
I actually ran into the same issue when using active_model_serializers. It tries to infer a serializer class from model's name. If it fails to find a matching one, it falls back to "serializerless" rendering. That confused me for a second, I was pretty sure I defined it, even in the proper file.
In your specific case Rails has to define a module called Module in order to specify a common table prefix there. Like so:
module Module
def self.table_name_prefix
'module_'
end
end
But try defining a module Module in irb and Ruby will refuse:
module Module
end
# TypeError: Module is not a module
It's not a module? Huh. Then what is it?
Module.class
# => Class
A class? That's right, moreover, it's a Ruby core class. So just choose a different module name. What you've chosen collides with something really essential.
Hacky mode (never actually do this, otherwise... wat)
You can fix that by altering the generated module.rb, replacing module with class. That will reopen the Module class and define your model inside it, making things work as expected. But technically, this is an absolutely unnecessary monkeypatch.
We're in the process of making a major database change to our Rails application. In order to be able to interop with the existing code, my plan is to do all the work in module namespaces to keep them separate from the existing models. However, I'm running into Rails autoload problems.
My file structure is like:
app/
models/
entity/
new_thing.rb
old_thing.rb
Where new_think.rb contains something like
module Entity
class NewThing
end
end
and old_thing.rb contains something like
class OldThing
end
OldThing gets autoloaded fine, but I keep getting errors like this:
Expected app/models/entity/new_thing.rb to define NewThing
Is there a way I can get it to correctly expect entity/new_thing.rb to define Entity::NewThing?
Try:
In your old_thing.rb
class OldThing
Extend Entity
end
or
class OldThing
require "entity/new_thing"
end
I added a helper class to my rails project, Foo, at app/helpers/foo.rb. It looks like this.
class Foo
#....stuff
end
I use it in some models, and everything works fine. However, in spec/helpers/foo_helper_spec.rb, I have
require 'spec_helper'
describe Foo do
end
This causes rspec to crash (not report any failed tests, but actually crash), saying
/actionpack-3.2.12/lib/abstract_controller/helpers.rb:153:in `include': wrong argument type Class (expected Module) (TypeError)
If i remove the describe line and just have the file empty, everything works, but I'd like to add some tests soon.
Anyone know how I can fix this?
Thanks.
rails follows the paradigm of convention over configuration. one of those conventions is, that you put modules into the helpers folder, because they get included into your controllers and views.
that's why rspec fails when it tries to include your module, which is actually a class.
if you need to have a real class in there, i think you should put it in a different directory to make it obvious, that this is not a usual rails helper!
for example, if you are implementing some kind of decorator for your views, put it in a decorators folder. if you are implementing some kind of adapter for your model, put it in the models folder or some model subfolder.