Rails extending Date class - ruby-on-rails

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

Related

Accessing modules in Rails lib folder

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

Auto require lib files in Rails 4

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

Rails constants unloaded from initializer

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.

uninitialized constant in My::Engine after file change

I'm developing a gem/engine. The way I do this is by bundling it in a test RailsApp from source:
# Gemfile
gem 'my-engine', path: '../local/path/to/gem'
This works fine so far.
But, after I change a file in my gem (add a space or a break for example) the Engine is unloaded. Causing the following error:
uninitialized constant My::Engine
This error is thrown by the file that does the first call to My::Engine. ( I need to call that to get the root: My::Engine.root ) If I delete that line, there are no error thrown, but just an empty page is rendered, and this is happening because all my SQL's change and no content is loaded from the database. I think this is because the files in the lib dir are unloaded, because in these files I dynamically create active-record models..
I already checked out the autoload_paths and watchable_dirs:
# engine.rb
module My
class Engine < Rails::Engine
engine_name 'my-engine'
initializer "my-engine.load_config" do |app|
app.config.autoload_paths += %W(#{Engine.root}/lib)
app.config.watchable_dirs["#{Engine.root}/lib"] = [:rb]
end
end
end
I'm not sure if I'm implementing these the right way, but they don't seem to solve my problems the way I'm using them.
I think you may need to require 'my/engine' before calling My::Engine.root, or change the order of your requires so that 'my/engine' is required prior to the file that makes a call to My::Engine.

Rails Module/Folder Naming Convention

I'm having a problem with a module name and the folder structure.
I have a model defined as
module API
module RESTv2
class User
end
end
end
The folder structure looks like
models/api/restv2/user.rb
When trying to access the class, I get an uninitialized constant error. However, if I change the module name to REST and the folder to /rest, I don't get the error.
I assume the problem has to do with the naming of the folder, and I've tried all different combos of /rest_v_2, /rest_v2, /restv_2, etc.
Any suggestions?
Rails uses the 'underscore' method on a module or class name to try and figure out what file to load when it comes across a constant it doesn't know yet. When you run your module through this method, it doesn't seem to give the most intuitive result:
"RESTv2".underscore
# => "res_tv2"
I'm not sure why underscore makes this choice, but I bet renaming your module dir to the above would fix your issue (though I think I'd prefer just renaming it to "RestV2 or RESTV2 so the directory name is sane).
You'll need to configure Rails to autoload in the subdirectories of the app/model directory. Put this in your config/application.rb:
config.autoload_paths += Dir["#{config.root}/app/models/**/"]
Then you should be able to autoload those files.
Also, your likely filename will have to be app/model/api/res_tv2/user.rb, as Rails uses String.underscore to determine the filename. I'd just call it API::V2::User to avoid headaches, unless you have more than one type of API.

Resources