Rails 4.1.16
Ruby 2.2.7
I have this ActiveRecord model:
class Something::Type < ActiveRecord::Base
When referencing the model like this:
Something::Type.find_by_whatever("test")
I get the following error:
NoMethodError: undefined method `find_by_whatever' for ActiveRecord::AttributeMethods::Serialization::Type:Class
I understand that Ruby constants are broken up, so that Type exists as its own constant, and the autoloader is finding ActiveRecord::AttributeMethods::Serialization::Type first.
However, referencing the namespace in an "absolute" way (prefixing with colons) is supposed to solve the issue, but the result is the same. Any ideas why?
::Something::Type.find_by_whatever("test")
NoMethodError: undefined method `find_by_whatever' for ActiveRecord::AttributeMethods::Serialization::Type:Class
The problem when you define a class with the scope resultion operator is that the module nesting is resolved to the point of definition (the point where you use the module keyword). If you look at the module nesting:
class Something::Type < ActiveRecord::Base
puts Module.nesting.inpsect # [Something::Type]
end
The class not even really nested in the Something module. Which will give you an extremely surprising constant lookup:
module Something
FOO = "test"
end
class Something::Type
puts Foo # gives a uninitialized constant error since its not looking in the Something module
end
Instead you should ALWAYS declare namespaced classes with explicit nesting:
module Something
class Type
puts Module.nesting.inspect # [Something::Type, Something]
puts Foo # test
end
end
Which gives the module nesting [Something::Type, Something] which means that it will properly look up constants in the same Something namespace.
This is something that the old classic autoloader tended to gloss over as it relied on monkeypatching Module#const_missing. Zeitwork does not so do it right.
Related
I've worked with a couple of Ruby gems and also Rails. One thing I've never fully understood is why Rails required explicit class constant references for code defined in the /lib folder. In a ruby gem, I could create something like this:
lib/my_gem/custom_error.rb
module MyGem
class CustomError < StandardError
end
end
lib/my_gem/some_class.rb
module MyGem
class SomeClass
def initialize
raise CustomError
end
end
end
Whether it's an error, another class or whatever, as long as the calling class is in the same namespace as the referenced class, ruby would initialize the correct class CustomError in the case above. Moving to Rails, this is a different story and this code would result in an uninitialized constant error. In Rails I would have to raise MyGem::CustomError instead. Why is this the case? I assume it has something to do with autoloading. Is there a way around this or is this standard?
Disclaimer : I know that it's a very bad pattern, I just want to understand why I have the following behaviour and the mechanics behind it.
Lets have one class with some constants / methods
one concern
one monkey patch that include that concern into my first class
I put all of the following in one rails model (under app/modela/a.rb)
class A
MY_CONST = "const"
def self.hello
"world"
end
end
module MyConcern
extend ActiveSupport::Concern
included do
byebug # Here I want to try many things
end
end
class A
include MyConcern
end
Then I open my console and run the following to load my class :
[1] pry(main) > A
First I'm my A Class is loaded, then the MyConcern module, then the monkey patch.
When I enter my byebug I have some weird behaviour
(byebug) self
A # Important : I'm in the scope of my A class
(byebug) hello
"world" # so far so good, my A class is loaded and have a world class method
(byebug) A::MY_CONST
"const" # Obiously this is working
(byebug) self::MY_CONST
"const" # When I Explicitly access it through `self`, it's also working
(byebug) MY_CONST
*** NameError Exception: uninitialized constant MyConcern::MY_CONST
nil # Here for some reason, and that's what I want to understand, I can't access my const, even if `self == A`
I want' to know why constant are not working the same way. Why ruby isn't able to find this const. My scope is my A class, self == A, so why can't I access my constant?
ps : Rails.version => "6.0.0"
I have the following standard Rails ActiveRecord Foo defined:
# app/models/foo.rb
class Foo < ApplicationRecord
end
And I'm trying to call Foo.find(..) from within a hierarchy that contains a module also named Foo..
# lib/commands/bar.rb
module Commands
module Bar
module Create
class Command
def initialize(params)
...
Foo.find(params[:foo_id]
...
end
end
end
end
end
# lib/commands/foo.rb
module Commands
module Foo
module Create
class Command
...
end
end
end
end
Ruby/Rails is finding Commands::Foo instead of my Foo Model and throwing undefined method 'find' for Commands::Foo:Module.. how can I point at the correct ActiveModel implementation?
The obvious answer is to rename Commands::Foo.. to Commands::Foos.. but I'm curious to know if there's another way :o)
If you want to avoid the clash then you should rename the modules. The existing structure is unwieldy and will present similar problems to all future maintainers.
The best solution that I find in your code is to ensure you call the appropriate module and method via its full path:
2.3.3 :007 > ::Commands::Foo::Create::Command.new
"Commands::Foo::Command reached"
=> #<Commands::Foo::Create::Command:0x007ffa1b05e2f0>
2.3.3 :008 > ::Commands::Bar::Create::Command.new
"Commands::Bar::Command reached"
=> #<Commands::Bar::Create::Command:0x007ffa1b04f110>
You shouldn't try to override or modify internal Rails calls, because then you've modified the framework to fit code, which leads to unpredictable side effects.
You can try to call::Foo in Commands::Foo, it should go with your Foo model
In Rails you can create a model under app/foo/bar.rb, with bar.rb containing:
class Foo::Bar
def some_method
puts "I work fine"
end
end
If you try to do this in a pure ruby app you'd get a NameError: uninitialized constant Foo unless you've already initialized a module Foo.
What is Rails doing that allows it to create classes without first initializing their containing module? Is it possible to import this behavior through something like activesupport, or are we left to implement on our own?
Rails modifies the Class class to include a const_missing method which gets called when an undefined class is used. It then loads things to try and load the requested class.
The implementation of this in ActiveSupport is in lib/active_support/dependencies.rb.
actually model class created is extend to < ActiveRecord::Base
I have some inherited code that I am modifying. However, I am seeing something strange(to me).
I see some code like this:
::User.find_by_email(params[:user][:email]).update_attributes(:mag => 1)
I have never seen something like this(I am new to Ruby on Rails). What does this do and why doesn't my User.find_by_email(params[:user][:email]).update_attributes(:mag => 1) work? The error says something about the User constant.
I am using Rails 2.3.5 if that helps.
:: is a scope resolution operator, it effectively means "in the namespace", so ActiveRecord::Base means "Base, in the namespace of ActiveRecord"
A constant being resolved outside of any namespace means exactly what it sounds like - a constant not in any namespace at all.
It's used in places where code may be ambiguous without it:
module Document
class Table # Represents a data table
def setup
Table # Refers to the Document::Table class
::Table # Refers to the furniture class
end
end
end
class Table # Represents furniture
end
It makes sure to load the User model in the global namespace.
Imagine you have a global User model and another User model in your current module (Foo::User). By Calling ::User you make sure to get the global one.
Ruby uses (among other things) lexical scoping to find constant names. For example, if you have this code:
module Foo
class Bar
end
def self.get_bar
Bar.new
end
end
class Bar
end
The Foo.get_bar returns an instance of Foo::Bar. But if we put :: in front of a constant name, it forces Ruby to only look in the top level for the constant. So ::Bar always refers the top-level Bar class.
You will run into situations in Ruby where the way your code is being run will force you to use these 'absolute' constant references to get to the class you want.
You might find a lead here: What is Ruby's double-colon `::`?
The "::" operator is used to access Classes inside modules. That way you can also indirectly access methods. Example:
module Mathematics
class Adder
def Adder.add(operand_one, operand_two)
return operand_one + operand_two
end
end
end
You access this way:
puts “2 + 3 = “ + Mathematics::Adder.add(2, 3).to_s