I have a bunch of custom classes in my Rails 3.2 app in lib folder: i.e. extending ActiveRecord, etc. It all works fine.
However I'm trying to add a couple of custom methods to FileUtils, i.e.
module FileUtils
def last_modified_file(path='.')
# blah ...
end
end
I put it in lib/file_utils.rb
In my application.rb I have
config.autoload_paths += %W(#{config.root}/lib)
My other custom classed are loaded but not the module.
I read (Best way to load module/class from lib folder in Rails 3? ) that I'm supposed to define a class inside module in order for Rails to pick it up and according to FileUtils.class - it should be Object < BasicObject.
So I tried
module FileUtils
class Object
def last_modified_file(path='.')
# blah ...
end
end
end
But that doesn't work either.
However when I fire up irb and just paste my code which effectivly puts my new code inside object and reinclude my module - it works fine.
Whaat amd I missing here?
Your patch is never going to be loaded because autoload is only invoked when Rails can't find a constant. Since the FileUtils constant already exists, the autoloader is never called, and your file is never loaded.
Simply require it from an initializer.
require File.join(Rails.root, "lib/file_utils.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 the following structure in my /lib folder
/lib
/example
/foo
bar.rb
foo.rb
foo.rb
module Example
module Foo
def self.some_methods
end
end
end
bar.rb
module Example
module Foo
class Bar
...
end
end
end
I then have in application.rb
config.autoload_paths << Rails.root.join('lib')
and in an initializer
...
Example::Foo.some_methods
...
I'm running into problems when trying to run my test suite in that it seems to be failing when setting up the Rails environment. It's complaining that some_methods does not exist on Example::Foo. I can boot a local development server up without any errors, however.
Digging into the problem using a breakpoint, the Example::Foo module is defined at the point of failure, but it's empty. I am assuming that the autoloader is loading bar.rb first (I can access that class in the initializer no problems)? It seems to be the case that Rails doesn't try to load foo.rb as Example::Foo has already been defined by bar.rb?
What's happening? If I require foo.rb in the initializer it works, but it feels like a bad solution. Also, any idea why this problem would only surface when running rake spec?
The whole point of the autoloader is that it loads missing constants by turning a module/class hierarchy into a path. As you've defined your constant in two files, it will never automatically load the second file.
You need to manually require your foo.rb.
I have a rails application where I need to extend the Hash module to add a method -
class Hash
def delete_blank
delete_if{|k, v| v.nil? or (v.instance_of?(Hash) and v.delete_blank.empty?)}
end
end
I have created a filed named, hash_extensions.rb and placed this in my lib folder and of course configured autoloading paths with the following line in config/application.rb
config.autoload_paths += %W(#{config.root}/lib)
When I call the delete blank method on a Hash however, I get the below error -
undefined method `delete_blank' for #<Hash:0x000000081ceed8>\nDid you mean? delete_if
In addition to this, I have also tried placing require "hash_extensions" at the top of the file I am calling the delete_blank method from.
What am I doing wrong here or can I avoid extending Hash to have the same functionality?
You could resolve this issue in a few different ways:
Assuming that hash_extensions.rb resides under your_app/lib/extensions. (It's a good idea to store all extensions in a separate folder), require all extensions in config/application.rb as below:
Dir[File.join(Rails.root, "lib", "extensions", "*.rb")].each {|l| require l }
Move hash_extensions.rb under config/initializers and it should be automoagically loaded.
Create a folder say lib or extensions under your_app/app and move hash_extensions.rb to it and Rails would take care of loading it.
In my Rails project, I want to add services directory in app folder and include some service objects.
So let's say I want to add app/services/foo/test.rb which looks like:
module Services
module Foo
class Test
end
end
end
In my config/application.rb I added:
config.paths.add File.join('app', 'services'), glob: File.join('**', '*.rb')
config.autoload_paths += Dir[Rails.root.join('app', 'services', '*')]
However when I try to load the files in console it doesn't work:
⇒ rails c
Loading development environment (Rails 4.1.4)
[1] pry(main)> Services::Foo::Test
NameError: uninitialized constant Services
Any help how can I solve this issue?
After add new dir, reload spring
spring stop
First of all, the code under app folder will be loaded without any config.
I think the problem was the folder structure doesn't match with your class definition.
So this config will work:
app/services/foo/test.rb
module Foo
class Test
end
end
My clue is, for example we have app/controllers/api/v1/users_controllers.rb and the class constant will be Api::V1::UsersController, not Controllers::Api::V1::UsersController
Update
Conventionally, we usually use FooServices instead of Foo, it is clearer, for example:
app/services/foo_services/bar_parser.rb
module FooServices
class BarParser
# Do stuff
end
end
So we understand that every class inside foo_services folder is a service which related to Foo
My problem was because of Rails naming conventions, I suppose. I just renamed class to not use module Services and it worked.
I use the following line in an initializer to autoload code in my /lib directory during development:
config/initializers/custom.rb:
RELOAD_LIBS = Dir[Rails.root + 'lib/**/*.rb'] if Rails.env.development?
(from Rails 3 Quicktip: Auto reload lib folders in development mode)
It works great, but it's too inefficient to use in production- Instead of loading libs on each request, I just want to load them on start up. The same blog has another article describing how to do this:
config/application.rb:
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]
However, when I switch to that, even in development, I get NoMethodErrors when trying to use the lib functions.
Example of one of my lib files:
lib/extensions.rb:
Time.class_eval do
def self.milli_stamp
Time.now.strftime('%Y%m%d%H%M%S%L').to_i
end
end
Calling Time.milli_stamp will throw NoMethodError
I realize others have answered similar questions on SO but they all seem to deal with naming conventions and other issues that I didn't to have to worry about before- My lib classes already worked for per-request loading, I just want to change it to per-startup loading. What's the right way to do this?
I think this may solve your problem:
in config/application.rb:
config.autoload_paths << Rails.root.join('lib')
and keep the right naming convention in lib.
in lib/foo.rb:
class Foo
end
in lib/foo/bar.rb:
class Foo::Bar
end
if you really wanna do some monkey patches in file like lib/extensions.rb, you may manually require it:
in config/initializers/require.rb:
require "#{Rails.root}/lib/extensions"
P.S.
Rails 3 Autoload Modules/Classes by Bill Harding.
And to understand what does Rails exactly do about auto-loading?
read Rails autoloading — how it works, and when it doesn't by Simon Coffey.
Though this does not directly answer the question, but I think it is a good alternative to avoid the question altogether.
To avoid all the autoload_paths or eager_load_paths hassle, create a "lib" or a "misc" directory under "app" directory. Place codes as you would normally do in there, and Rails will load files just like how it will load (and reload) model files.
This might help someone like me that finds this answer when searching for solutions to how Rails handles the class loading ... I found that I had to define a module whose name matched my filename appropriately, rather than just defining a class:
In file lib/development_mail_interceptor.rb (Yes, I'm using code from a Railscast :))
module DevelopmentMailInterceptor
class DevelopmentMailInterceptor
def self.delivering_email(message)
message.subject = "intercepted for: #{message.to} #{message.subject}"
message.to = "myemail#mydomain.org"
end
end
end
works, but it doesn't load if I hadn't put the class inside a module.
Use config.to_prepare to load you monkey patches/extensions for every request in development mode.
config.to_prepare do |action_dispatcher|
# More importantly, will run upon every request in development, but only once (during boot-up) in production and test.
Rails.logger.info "\n--- Loading extensions for #{self.class} "
Dir.glob("#{Rails.root}/lib/extensions/**/*.rb").sort.each do |entry|
Rails.logger.info "Loading extension(s): #{entry}"
require_dependency "#{entry}"
end
Rails.logger.info "--- Loaded extensions for #{self.class}\n"
end