I have a very odd error I cannot wrap my head around.
Basically, I have this class in my lib folder:
# lib/api/amazon.rb
module API
class Amazon
...
end
end
When I want to use it somewhere, I require it:
require 'api/amazon'
API::Amazon.do_stuff
This works initially but after a while it breaks and raises NameError: uninitialized constant API::Amazon. When I debug this and try to require the file again when the error is raised, it returns false, indicating that the file was already loaded. I can also see it in $" (this list of loaded files). Why can I then not access API::Amazon?
Note: I added "API" as an acronym to ActiveSupport::Inflector which is why I don't have to use "Api":
# config/initializers/inflections.rb
ActiveSupport::Inflector.inflections do |inflect|
inflect.acronym 'API'
end
EDIT:
I tried ::API::Amazon.do_stuff as well, same result.
I write some code aimed to get same result as yours, maybe it can give some clue.
trytemp.rb:
module API
class Amazon
def hello
puts "API::Amazon initially works well"
$stdout.flush
end
end
end
s = API::Amazon.new
s.hello
p API.constants
API = Module.new
p API.constants # Here you can see constant Amazon disappers from module API
s = API::Amazon.new
s.hello
It initially works well, then get same error,"uninitialized constant API::Amazon (NameError)":
$ ruby trytemp.rb
API::Amazon initially works well
[:Amazon]
trytemp.rb:15: warning: already initialized constant API
[]
trytemp.rb:19:in `<main>': uninitialized constant API::Amazon (NameError)
EDIT:
I though I had found the answer but the same error just occurred again... :(
END EDIT
It seems that I found the answer, with the help of #uncutstone.
Turns out I had not only used the API namespace for API::Amazon but also for some controllers, like so:
# app/controllers/api/v1/accounts_controller.rb
class API::V1::AccountsController < APIController
...
end
My theory is that one of these controllers was reloaded automatically at some point in time and re-initialized (and therefore cleared out) the API module/namespace.
Thus API::Amazon wasn't available after that, but rerequireing lib/api/amazon.rb didn't help because it had already been required and therefore wasn't loaded again.
I changed the controllers to look like this:
# app/controllers/api/v1/accounts_controller.rb
module API
class V1::AccountsController < APIController
...
end
end
and now it seems to work fine.
Related
The error is something like NameError: uninitialized constant Foo::Bar (when Bar is not a descendant of Foo).
I get that it has something to do with loading (autoloading?) constants, and I feel that anything inside of lib/ is safe to not prefix with :: because it's autoloaded (or something).
An example that just happened to me is something like this:
app/assets/classes/base_class.rb
class BaseClass
end
app/assets/some_module/some_class.rb
module SomeModule
class SomeClass < BaseClass
end
end
I was running a spec and got "An error occurred while loading [file]": NameError: uninitialized constant SomeModule::SomeClass::BaseClass.
Now, I get that it's trying to look for BaseClass within SomeModule::SomeClass. But this was working a few hours before and then stopped after no changes to these files.
So, I could just tack on a :: and go with class SomeClass < ::BaseClass, but without understanding why it feels bad and then I'm like, do I need to pepper all my code with :: all the time?
When do you need to prefix Ruby constants with "::"?
When you refer to a constant Ruby will look for it in the current module nesting.
module SomeModule
puts Module.nesting.inspect # the current module nesting is [SomeModule]
end
The module nesting is primarily set by using the module and class keywords to open a class/module.
If its not found there it will keep going upwards in the module nesting until it reaches main and if the constant still isn't found by then you'll get a missing constant error.
This error can be somewhat confusing since the message contains the module nesting where it started looking for the constant.
By prefix a constant with :: you're explicitly telling Ruby to resolve the constant from the top level namespace:
module Bar
def self.hello
puts "Hello from the top level Bar"
end
end
module Foo
module Bar
def self.hello
puts "Hello from Foo::Bar"
end
end
def self.test
Bar.hello # the current module nesting is Foo.
::Bar.hello
end
end
Foo.test
# Will output:
# Hello from Foo::Bar
# Hello from the top level Bar
In most cases its not strictly necissary but it is a good practice though to explicitly refer to constants outside your own namespace (dependencies).
So why did my code stop working?
Without an actual reproducable example its near impossible to tell. It can either be "programmer related issues" (you screwed up and moved/deleted/renamed the file) or it can be tied to issues with the autoloader in use. The classic autoloader was far more prone to buggy behavior due to the way it monkeypatched Object#constant_missing.
I'm having an issue in a Rails codebase that uses the official SendGrid gem. There are two separate instances of the same behavior occurring.
First Example
I've got an Engine class:
module API
class Engine < ::Rails::Engine
isolate_namespace API
end
...
end
I used to refer to that class in my routes like this:
mount API::Engine, at: :api, as: :api
...but I occasionally got this error: uninitialized constant SendGrid::API:Engine. (I mean "occasionally" literally; it seemed to occur randomly during development and a refresh would fix it.) So, I changed the line to this:
mount ::API::Engine, at: :api, as: :api
I thought that should explicitly target my API and not SendGrid's, but I still get the error occasionally during development when I'm working on SendGrid-related code.
Second Example
This one seems even stranger. This issue just arose this week, and made me realize that the above example isn't an isolated incident.
I've got a Response class:
class Survey::Response < Sequel::Model Sequel[:survey][:responses]
...
end
And our monitoring just revealed this error:
NoMethodError
undefined method `[]' for SendGrid::Response:Class
... on this line:
results = results.pluck(:response_id).uniq.map { |r| Survey::Response[r] }
This example seems even more extreme, since we're explicitly targeting Survey::Response and clearly not targeting SendGrid::Response.
Question
I'd love to know what's causing this, but mostly I'd like to know how to stop it. How can I make 100% sure that a line targets my class and not the sendgrid-ruby gem's class?
I did find this question, which came to the same conclusion as I did in my first question, though this question's issue was resolved whereas mine was not.
My issue was that a module was being included in the global namespace, and so its constants were polluting the entire codebase.
For example:
require 'sendgrid-ruby'
include SendGrid
module MyModule
...
end
Including SendGrid in this way can cause the issue described in my question. Including any gem in this way has the potential to cause a similar issue.
The solution is to only include modules where you must, for instance:
require 'sendgrid-ruby'
module MyModule
include SendGrid
...
# I can refer to Email here, and it will target SendGrid::Email
...
end
... or even better, don't include the module at all, and instead explicitly refer to the module's classes:
require 'sendgrid-ruby'
module MyModule
...
# Instead of referring to Email, I must explicitly refer to SendGrid::Email
...
end
This question helped me recognize the issue. But since that question didn't come up for me in my searches, I'll leave this question and answer here for any future travelers.
I have question about naming conventions and autoloading.
I want to have a presenter ItemPresenter in app/presenters/items/item_presenter.rb
My understanding was that I can just create that file like this:
module Items
class ItemPresenter
end
end
But when I do this and try to call the presenter as Items::ItemPresenter I get uninitialized constant error:
uninitialized constant Items::ItemPresenter
def show
#presenter = Items::ItemPresenter.new # this is the highlighted line of my Controller
EDIT: Rails, Custom Folders and Namespaces is not duplicate because it's about different dir structure jobs/smth.rb while I am trying to implement presenters/items/item_presenter.rb (1 more level)
EDIT2: neither it works from rails console: NameError: uninitialized constant Items::ItemPresenter
EDIT2: I tried doing this as suggested:
module Presenters
module Items
class ItemPresenter
def test
"hello"
end
end
end
end
And #presenter = Presenters::Items::ItemPresenter.new in my controller:
uninitialized constant TrialsController::Presenters
It seems like Rails do not see that directory at all.
EDIT3: Created a sample app https://github.com/dontlookforme/test_app
EDIT4: Figured it out. I screwed up the file name (see the answer I've posted)
I found the answer but it's necessary to see #user1556912's sample app (link in the original question) to see what happened.
The problem is that the filename is items_presenter.rb (plural) but the class name is ItemPresenter (singular).
As I pointed out in a comment on #Anthony E's answer, Rails will autoload everything in the /app dir, so it's not necessary explicitly to tell Rails about these files. However, along with namespaces matching dir hierarchies, the names of the classes must also match the names of the files exactly. In this case, I was able to get the class to load in the rails console by renaming items_presenter.rb to item_presenter.rb.
Going back to #Anthony E's answer, though, I do agree that the Items:: namespace seems superfluous here. I would just do app/presenters/item_presenter.rb.
app/presenters/ is the conventional path to store presenters. In fact, you can probably go without folder nesting for items:
app/presenters/item_presenter.rb
You'll need to update the module path accordingly:
module Presenters
class ItemPresenter
def test
"hello"
end
end
end
Then, you can tell Rails to automatically load this file in your application.rb:
config.autoload_paths << '#{config.root}/app/presenters'
Ugh. The thing I was doing wrong is the file name.
I named the preseter file items_presenter.rb but the class had singular Item in the name ItemPresenter.
Fixed that and everything started working.
Thanks for the help guys!
i am writing gem for my Rails app which calculates some stuff and uses class and modules.
Here is file structure.
root
->lib
-->finances
--->version.rb
--->finances.rb
--->calculator
----->formulas.rb
--->finalize
---->schedule.rb
-->finances.rb
Now root/lib/finances.rb
require "finances/version"
require "finances/finances"
require "finances/finalize/schedule"
require "finances/calculator/formulas"
root/lib/finances/calculator/formulas.rb
module Calculator
module Formulas
def method
end
end
end
root/lib/finances/finalize/schedule.rb
module Finalize
class Schedule
include ::Calculator::Formulas
end
end
but I get uninitialized constant Calculator (NameError)
if i try to just use
::Calculator::Formulas.method
it throws NoMethodError (undefined methodmethod' for Calculator::Formulas:Model):`
What exactly i am doing wrong. I cant seem to work around this. Could anyone help.
You try to use method as Formulas 'module method', while you defined it as regular instance method. So it should be called on RepaymentSchedule instance:
rs = RepaymentSchedule.new
rs.method
Also, you need to make sure your loading order is correct. Here, you should require file containing Formulas module before you load Schedule class, otherwise you get uninitialized constant error.
I have a file containing a helper class something like this:
app/classes/myfile.rb
Module mymodule
class myclass
# blah blah
end
end
I want to use this class in a controller, so I wrote something like this:
require 'myfile'
class MyController < ApplicationController
include mymodule
def index
mymodule::myclass.new
end
end
The route for the controller is defined like this:
match 'mycontroller', :to => 'mycontroller#index'
Now for the strange behaviour I'm facing. It works perfectly fine on the first run after the server starts. But when I refresh the page or hit the URL again, I get the following error.
Routing Error
uninitialized constant MyController::mymodule
I cannot make out anything out of the error, nor can I understand why it does not work from the second hit onward only. What's happening?
Generally speaking, Rails likes to see files containing:
module MyModule
named my_module.rb
Modules are generally capitalized
Also, it thinks that MyModule is scoped under the MyController class, which it is not. You could try
include ::MyModule
to access it from the top-level scope.
I also don't know if your load paths include your classes directory, so it is probably not autoloading the myfile.rb file in the first place.
I changed require 'myfile' to load 'myfile.rb' and it now works fine. I don't know if I solved the problem though. I don't know what is happening. Can someone enlighten me?