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.
Related
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.
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.
The first line of text is the LoadError
the second line of text in the image is the folder where the file, shared.rb resides in my rails project.
And the sytax colored stuff is the top part of shared.rb
as you can see, shared.rb defines NamedScope::Shared, so why is RoR saying that it isn't?
using rails 2.3.8
UPDATE:
tried this:
module NamedScope
module Shared
in the same file, shared.rb in {AppRoot}/lib/named_scope/
which also didn't work (same error)
UPDATE 2: This error was caused my a model class not having a Constant defined.
I just had
CONSTANT_NAME
instead of
CONSTANT_NAME = value
this is upsetting, as I feel lied to o.o
Did you define the NamedScope module before? You might need to do:
module NamedScope
module Shared
# do your thing here
end
end
I would need to see more of the code to follow.
I've had that issue when the code uses a Class that is defined somewhere else and the magic name resolution is not strong enough. Like if I have a XyzLoan class that extends the Loan class but I did not require it or use it before. It shouldn't fail but it does.
If that is the case you can do a binary search for the problem by removing halves of the code and try again until you find what is causing it.
So what I'd like to do is to override the default date_select method (I'd like to make an 'optional / unspecified' date input). What I've tried so far is this:
lib/overrides.rb
ActionView::Helpers::DateHelper::DateTimeSelector.class_eval do
def build_selects_from_types(order)
select = ''
order.reverse.each do |type|
separator = separator(type) unless type == order.first # don't add on last field
select.insert(0, separator.to_s + send("select_#{type}").to_s)
end
select.insert(0, '<p>HI!!</p>') # or whatever...
select.html_safe
end
end
I then required 'overrides' at the bottom of environment.rb but when starting WEBrick I get this error:
~/.rvm/gems/ruby-1.9.2-p0/gems/activesupport-3.0.0/lib/active_support/dependencies.rb:479:in
`load_missing_constant':
ActionView::Helpers is not missing
constant DateTimeSelector!
(ArgumentError)
So I obviously don't really know what I'm doing but this seems like a reasonable thing to attempt at least.
The error above seems to imply that it can't find the DateTimeSelector class but I've peered at the code in ~/.rvm/gems/ruby-1.9.2-p0/gems/actionpack-3.0.0/lib/action_view/helpers/date_helper.rb and I think I've got the module hierarchy right. Is it because it's a private Rails class?
Any thoughts are most welcome :)
In Ruby doesn't exist the concept of private class. Classes are never private.
The reason for the error is because the path is invalid. It should be
ActionView::Helpers::DateTimeSelector
not
ActionView::Helpers::DateHelper::DateTimeSelector
BTW, what you are trying to do is absolutely a bad idea. The fact that Ruby gives you the power of reopening classes and "patch" methods, doesn't mean you should do this for such this kind of customizations.
You should never make these chances to the Rails codebase unless you really know what you are doing. The risk is to break things that depends on this method.
The right way to go is do define a new helper and build your own logic.