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?
Related
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.
I'm trying to make a Ruby on Rails engine, and I want the initializer to be able to have access to the helpers and models.
I'll write below an example, part of the code, and the error that I have. It may not be the recommended way, because I can see that in some cases I'm repeating myself, but it's the first engine I make.
file lib/my_engine/engine.rb
module MyEngine
require 'my_engine/functions'
class Engine < ::Rails::Engine
isolate_namespace MyEngine
config.autoload_paths += %W( #{config.root}/lib )
end
class GlobalVars
attr_accessor :foo
def initialize
#foo = MyEngine::Functions.new
end
end
class << self
mattr_accessor :GLOBAL
mattr_accessor :USER_CONFIG
self.GLOBAL = MyEngine::GlobalVars.new
# add default values of more config vars here
self.USER_CONFIG = 'default config'
end
def self.setup(&block)
yield self
end
end
file lib/my_engine/functions.rb
module MyEngine
require '../../app/helpers/my_engine/options_helper'
class Functions
include MyEngine::OptionsHelper
attr_accessor :some_link
def initialize
#some_link = get_option('dummy')
end
end
end
There is also a controller named OptionsController in app/controllers/my_engine, and OptionsHelper in app/helpers/my_engine/options_helper.rb:
module MyEngine
module OptionsHelper
def get_option(name)
MyEngine::Option.new
end
end
end
When I try to run the dummy application, this error occurs:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant MyEngine::Option (NameError)
If I change to just Option.new, I have this error:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant MyEngine::OptionsHelper::Option (NameError)
For ::MyEngine::Option.new, I have:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant MyEngine::Option (NameError)
For ::Option.new, I have:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant Option (NameError)
The dummy application has nothing in it. All helpers and models defined above are in the engine.
Before this, I had other errors because it couldn't access the helper, or the Functions class. I had to add require and include to make it work even if they are placed in the same directory. Also, to work, I had to move GlobalVars from its own file inside engine.rb.
Can somebody show me what I'm doing wrong?
After I used required for every class, I ended with ActiveRecord::ConnectionNotEstablished, and it seems that not everything is loaded and available at that point when the GLOBAL object is created.
So I moved the code that was using the models in a separate init method. Then, I added an after initialize event:
config.after_initialize do
MyEngine.GLOBAL.init
end
I see a possible problem: because you are inside module MyEngine it might be possible that actually rails is looking for MyEngine::MyEngine::Option, so I see two approaches:
just write Option: this will look for MyEngine::Option
write ::MyEngine::Option this will look in the global namespace and find MyEngine::Option
Secondly, if that does not help, even though your path seems correct, but you can always explicitly require "my_engine/option" at the top of the file. I am not entirely sure the autoloading in an engine works in quite the same way, and I tend to, in my engine file, require almost everything (to make sure it works).
In my engine.rb I do
require_relative '../../app/models/my_engine/option'
maybe this will help, but it is not a nice solution.
I have rails 4.1.16 API application that is tested using RSpec 3.4.0, and I experience problems with testing classes called the same name in a different module.
The structure is:
app/controllers/bar/notifications_controller.rb
class Bar::NotificationsController < ApiController
...
end
and controller with the same name in a different module:
app/controllers/foo/bar/notifications_controller.rb
module Foo
class Bar::NotificationsController < ApiController
...
end
end
The Foo is a new module and does not have tests yet.
After adding it, all the corresponding controller tests for the old Bar::NotificationsController started to fail.
The spec file:
spec/controllers/bar/notifications_controller_spec.rb
require 'spec_helper'
describe Bar::NotificationsController, type: :controller do
...
end
All the tests in that spec file fail with the same error:
RuntimeError:
#controller is nil: make sure you set it in your test's setup method.
The problem does not exist when I change the controller name in the Foo module:
app/controllers/foo/bar/foo_notifications_controller.rb
module Foo
class Bar::FooNotificationsController < ApiController
...
end
end
I already tried adding on top of the spec file require 'bar/notifications_controller' and using the class name as a string describe "Bar::NotificationsController, type: :controller but it did not solve the issue (the same error).
Why is this happening? What is the solution?
I want to believe there is a tiny thing I did not try yet and I don't have to pollute my code and the structure with nonsense names just to make the specs pass.
Many thanks in advance for your help!
In general, I've take to including all namespacing in the class definition. Something like:
app/controllers/foo/bar/notifications_controller.rb
class Foo::Bar::NotificationsController < ApiController
...
end
While, at first glance, this might look the same as:
app/controllers/foo/bar/notifications_controller.rb
module Foo
class Bar::NotificationsController < ApiController
...
end
end
These are, in fact, different. The difference is in how Rails handles autoloading of constants. I won't go into the details here because it's a longer topic and there are good articles/posts out in the web-o-sphere.
You can find good articles on how Rails handles autoloading like this one (or try Googling rails constant loading)
Also, as the article notes, Ruby constant loading operates differently than Rails loading. Good information on Ruby constant loading can be found here (or try Googling ruby constant loading).
I'm following instructions at http://ariejan.net/2011/10/14/rails-3-customized-exception-handling/ and have hit a road block.
I am relatively new to rails, so I'm not sure what I've done right/not-so-right.
The first step was to create class
MyApp::ProfileNotFoundError < StandardError
end
So I went to app/models and created profile_not_found.rb which contains the following, where (APP) is the name of my app as defined by Rails.application.class.parent_name, but I have hidden from this post for security/privacy.
(APP)::ProfileNotFoundError < StandardError
end
In app/controllers/application_controller.rb I added
rescue_from (APP)::ProfileNotFoundError, :with => :profile_not_found
and in my login controller I added
raise (APP)::ProfileNotFoundError if #profile.nil?
However, when I try to test the code, I get a Routing Error stating
uninitialized constant (APP)::BlankUsernameError
In my opinion, this suggests that I did something wrong pertaining to the class creation, but the tutorial is so vague I can't figure it out. Any pointers?
I'm running Rails 3.0.20 & Ruby 1.8.7 on Ubuntu 12.04.2 x86_64
Do you have the class keyword in your class definition?
class MyApp::ProfileNotFoundError < StandardError
end
Secondly, you'll have to require your exceptions where you're using it. This is probably the problem you're encountering with the uninitialized constant error. To do this you will probably have to wrap it in a module:
module Exceptions
class MyApp::ProfileNotFoundError < StandardError
end
end
Also, you should put your error classes in a different directory than /models. This directory should be explicitly for your models. Maybe make one like /errors.
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