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
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 have created a file lib/ext/date.rb which extends the Date class with a method.
class Date
def self.next_(weekday_string)
// code here
end
end
In my application.rb file I autoload everything in this ext folder
config.autoload_paths << "#{Rails.root}/lib/ext"
But I keep getting the error undefined method next_ for Date:Class
Calling this method from within the console works fine
load 'lib/ext/date.rb'; Date.next_('wednesday')
=> Wed, 07 Oct 2015
And yes, the server has been restarted before trying to use this extended method.
I guess, your understanding of Rails autoload mechanism is fuzzy.
autoload_paths is used when rails tries to resolve undefined constant. Say, you access a User for the first time. No such class is loaded, so rails will look in its autoload paths and try find User there.
Your case is different. Date is certain to be present (as it is a system class). And thus rails will have no reason to access files in autoload paths.
Solution:
load the files explicitly. For example, in an initializer
# 00_monkey_patching.rb
Dir["#{Rails.root}/lib/monkey_patching/*.rb"].each do |file|
require file
end
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 store a value in a class variable inside of a module, such as:
module TranslationEnhancer
def self.install! klass
#dictionaries ||= [] << klass
end
...
end
I call this from an initializer in config/initializers:
require Rails.root + "lib" + "translation_enhancer.rb"
TranslationEnhancer::install! TranslationDictionary
Now, if I start the server in development environment, everything is ok during the first request. However, after that request, #dictionaries are suddenly nil. I have commented all other code in TranslationEnhancer, so I am absolutely sure the whole module must get reloaded every time I do a request.
I tried to move the module outside of the lib directory (moved it to lib_unloadable), then I tried:
ActiveSupport::Dependencies.explicitly_unloadable_constants << "TranslationEnhancer"
but failed again. I have no idea how to solve this, please help.
Got Ruby 1.9.2 # Rails 3.1.rc4.
EDIT: I know I could set the dictionaries as a constant. But I would like to use TranslationEnhancer as a library - so I could use it unchanged in a different project and install different Directories, such as:
TranslationEnhancer.install! EnglishDirectory, FrenchDirectory
These values won'd change during the runtime, they will just change project to project.
Solved!
I realized that the whole application.rb and environment.rb files are reloaded along with all other files. The only thing that does not get reloaded are initializers (config/initializers/*). The solution was to move the initialization to application.rb.
#dictionaries is not a "class variable". It is a "class-level instance variable".
Look here for a better explanation: Class and instance variables
Try using ##dictionaries instead.