I have a custom initializer config/initializers/api/v1.rb, in which I set some global constants for use accross the application:
module Api
module V1
DATE_FORMAT = '%Y-%m-%d'
end
end
The first time I hit a controller that users Api::V1::DATE_FORMAT, everything works as expected. If I hit that path again, I get a NameError uninitialized constant Api::V1::DATE_FORMAT.
The Rails documentation for configuration explicitly says it should handle this case:
You can use subfolders to organize your initializers if you like,
because Rails will look into the whole file hierarchy from the
initializers folder on down.
Related
There are probably hundreds of these questions on here but I haven't been able to get one to work yet. I'm using Rails 6 and I always have trouble writing custom modules.
I have a file for creating email tokens: lib/account_management/email_token.rb
module AccountManagement
module EmailToken
def create_email_token(user)
....
end
end
end
Then in my controller I have tried all kinds of things:
require 'account_management/email_token'
include AccountManagement::EmailToken
Calling it with: create_and_send_email_token(user)
At one point I added config.eager_load_paths << Rails.root.join('lib/account_management/') but I didn't think you still had to do that.
Whenever I try to call the controller action I get one of a few error messages:
*** NameError Exception: uninitialized constant Accounts::SessionsController::EmailToken
#this happens if I try to manually send through the rails console
(although inside of a byebug I can call require 'account_management/email_token' and it returns
true.
Most commonly I get:
NoMethodError (undefined method `create_email_token' for #<Accounts::SessionsController:0x00007ffe8dea21d8>
Did you mean? create_email):
# this is the name of the controller method and is unrleated.
The simplest way to solve this is by placing your files in app/lib/account_management/email_token.rb. Rails already autoloads any subdirectory of the app folder*.
/lib has not been on the autoload paths since Rails 3. If you want to add it to the autoload paths you need to add /lib not /lib/account_management to the autoload/eager loading paths. In Zeitwerk terms this adds a root where Zeitwerk will index and resolve constants from.
config.autoload_paths += config.root.join('lib')
config.eager_load_paths += config.root.join('lib')
Note that eager_load_paths is only used when eager loading is turned on. Its turned off by default in development to enable code reloading.
/lib is added to $LOAD_PATHso you can also manually require the file with:
require 'account_management/email_token'
See:
Autoloading and Reloading Constants (Zeitwerk Mode)
Rails #37835
I'm trying to extend the String class in my Rails 4.2 app. I created a lib/string.rb file to hold the extensions.
class String
def testing
"testing"
end
end
I added lib to autoload_paths in application.rb.
config.autoload_paths << Rails.root.join('lib')
When I startup the rails console and execute "".testing, I get NoMethodError: undefined method 'testing' for "":String
Can anyone explain why this method isn't getting picked up? I have a hunch that it's because the String constant is already loaded, so Rails doesn't need to autoload the constant. As a result, it never tries to load the lib/string.rb file and my method never gets added to String.
When I explicitly require the file in an initializer, I can get the method loaded, but if I change the method, I have to restart the server to get rails to see the change. It feels like I'm missing something. It seems like there should be a way to get Rails to automatically read core extension classes and reload them when the file changes.
Yep, you're right. It will not autoload string since it's already defined. I usually put core class extensions inside an initializer. So config/initializers/string.rb
I'm trying to add a method to the DateTime class like so:
class DateTime
def ymd(sep = "/")
strftime("%Y#{sep}%m#{sep}%d")
end
end
I put this in #{config.root}/lib/datetime.rb and updated the autoload_path to include #{config.root}/lib (since that seems to go in and out of the conventional autoload path). That didn't work, so I also tried putting it in a random directory (#{config.root}/blah and added that path to the autoload_paths line in the config).
In all of the above cases, I'm only able to use the new method in the rails console if I require 'datetime' first, and I'm not able to use it in controllers or view templates no matter what I do.
So,
Should the file be called datetime.rb or date_time.rb? (I've tried both so far and neither are currently working)
Where should I be putting this file so I can use the new method in models, controllers and views?
Any idea why I can require it in the console, but it doesn't autoload there?
The app is currently running rails 3.2.21, but I'll switch to rails 4 at some point so answers for either version are appreciated.
This is a tricky one to explain. I have a module in another module namespace like so:
# app/models/points/calculator.rb
module Points
module Calculator
def self.included(base)
base.send(:include, CommonMethods)
base.send(:include, "Points::Calculator::#{base}Methods".constantize)
end
end
end
So then in other classes all I need to do is:
class User
include Points::Calculator
end
I've specified this directory in application.rb to be autoloadable...(even though i think rails recurses through models...)
config.autoload_paths += Dir[ Rails.root.join('app', 'models', "points") ]
In development env, everything works fine. When running tests(and production env), I get the following error:
Unable to autoload constant Points::Calculator, expected /Users/pete/work/recognize/app/models/points/calculator.rb to define it (LoadError)
I actually followed the advice here to fix the problem: Stop Rails from unloading a module in development mode by explicitly requiring calculator.rb in application.rb.
However, why is this happening??
I stuck some debug output in ActiveSupport's dependencies.rb file and noticed that this file is being required twice. The first time its required I can see that the constant is indeed loaded.
But the 2nd time its required the constant has been unloaded as far as Rails can tell, but when the actual require is called, ruby returns false because ruby knows its already required it. Then Rails throws the "unable to autoload constant" error because the constant still isn't present and ruby didn't "re-require" the file.
Can anyone shed light on why this might be happening?
Rails augments the constant lookup mechanism of ruby.
Constant lookup in Ruby:
Similar to method missing, a Module#constant-missing is invoked when a reference to a constant fails to be resolved. When we refer to a constant in a given lexical scope, that constant is searched for in:
Each entry in Module.nesting
Each entry in Module.nesting.first.ancestors
Each entry in Object.ancestors if Module.nesting.first is nil or a module.
When we refer to a constant, Ruby first attempts to find it according to this built-in lookup rules.
When ruby fails to find... rails kicks in, and using its own lookup convention and its knowledge about which constants have already been loaded (by ruby), Rails overrides Module#const_missing to load missing constants without the need for explicit require calls by the programmer.
Its own lookup convention?
Contrasting Ruby’s autoload (which requires the location of each autoloaded constant to be specified in advance) rails following a convention that maps constants to file names.
Points::Calculator # =>points/calculator.rb
Now for the constant Points::Calculator, rails searches this file path (ie 'points/calculator.rb') within the autoload paths, defined by the autoload_paths configuration.
In this case, rails searched for file path points/calculator in its autoloaded paths, but fails to find file and hence this error/warning is shown.
This answer is an abstract from this Urbanautomation blog.
Edit:
I wrote a blog about Zeitwerk, the new code reloader in Rails. Check it out at -> https://blog.bigbinary.com/2019/10/08/rails-6-introduces-new-code-loader-called-zeitwerk.html
If someone is having this issue in rails 6 which has zeitwerk autoloader,
Change ruby constant lookup back to classic in your application.rb
# config/application.rb
#...
config.autoloader = :classic
#...
Read more details here Rails Official Guides
Calculator should be a class to be autoloaded correctly
module Points
class Calculator
...
end
end
I created a constant that includes my API key for an app I'm working on. It's working in the rails console, but I'm having a hard time getting this to work in my tests. I have to use Bing so I added the API like so:
# config/bing.yml
development:
secret: 1234
test:
secret: 5678
production:
secret: abcd
# config/initializers/bing.rb
BING_CONFIG = YAML.load_file("#{::Rails.root}/config/bing.yml")[::Rails.env]
How would I ensure that BING_CONFIG loads into rspec? Currently, it's working and loading fine in the my rails console, but when I run my specs, I get the following error:
uninitialized constant SearchEngine::BING_CONFIG
Is the BING_CONFIG = … string resides in your class/module SearchEngine? If yes, try to
require 'search_engine'
(or how the file containing that class is called) in top of your rspec file. If no, fix the problem with namespaces, e.g.:
SearchEngine::BING_CONFIG = …
In my understanding, as far as you configure such a initialization in config/initializers, it should be loaded even in rspec run. But it is still not working...
Since your error output showing SearchEngine name, I guess you've wrapped the initializer inside a module.
If that is the case, the SearchEngine module name should be introduced before the above assignment. But when you introduce the module name, to make Rails's autoloader happy, you must follow Rails' way of naming files (and class/module name) according to the namespace hierarchy. And it's depends on how SearchEngine is defined. How do you want to define the module name?