In my rails mountable engine:
config.to_prepare do
# works fine, and reload automatically in development
ApplicationController.helper :application
# works fine, but doesn't reload. After restart server, it works.
ApplicationController.helper Rails.application.helpers
It looks like fine when arg is symbol or string. But it doesn't work when arg is a module like Rails.application.helpers.
Or is there a good way to get all helpers like [:application, :users] from Rails.application.helpers.
Rails: 4.2.3
You can configure autoload_paths of the engine.
lib/my_engine/engine.rb
module MyEngine
class Engine < ::Rails::Engine
...
config.autoload_paths += Dir[Engine.root.join('app', 'helpers')]
end
end
http://api.rubyonrails.org/classes/Rails/Engine.html
for people using rails 6, you might want to try using the classic autoloader instead of zeitwerk
config.autoloader = :classic
Related
I'm trying to update my app from Rails 5.2 to 6.1, and use Zeitwerk autoload, and I am having some issues for autoloading /lib classes.
I included this in config/application.rb:
config.load_defaults 5.2
config.autoloader = :zeitwerk
config.enable_dependency_loading = true
config.autoload_paths += Dir[Rails.root.join('lib/**/*')]
config.eager_load_paths += Dir[Rails.root.join('lib/**/*')]
And, when I run zeitwerk:check, it alerts me that:
Hold on, I am eager loading the application.
expected file lib/facades/coconut/v2/foo.rb to define constant Foo
It is declared as:
# /lib/facades/coconut/v2/foo.rb
module Coconut
module V2
class Foo
# ...
end
end
end
When I try to debug it (putting a binding.pry in some test file), Zeitwerk says it do not defined, until I call it without modules (namespaces):
[1] pry(#<#filename>)> Coconut::V2::Foo
NameError: uninitialized constant Coconut::V2::Foo
from (pry):1:in `setup`
[2] pry(#<#filename>)> Foo
Zeitwerk::NameError: expected file /my-app/lib/api/coconut/v2/foo.rb to define constant Foo, but didn't
from /usr/local/bundle/gems/zeitwerk-2.5.4/lib/zeitwerk/loader/callbacks.rb:25:in `on_file_autoloaded'
[3] pry(#<#filename>)> Coconut::V2::Foo
=> Coconut::V2::Foo
It sounds really strange, because there are another classes in /lib that follows the same structure, but it works well, example
# /lib/api/services/youtube/client.rb
module Services
module Youtube
class Client
# ...
end
end
end
Inspecting it with binding.pry in some test:
pry(#<#filename>)> Services::Youtube::Client
=> Services::Youtube::Client
Has anyone had this problem or know what could be happening?
System:
ruby 2.7.6p219
Rails 6.1.6
Zeitwerk (2.5.4)
An autoload path is a root directory, not its contents.
You need to remove the wildcards as documented here.
trying to get my gem working in Rails 7. I have confirmed it works in Rails 6.1.4.1 (latest).
In my engine's engine.rb file I have this...
module MyEngine
class Engine < ::Rails::Engine
isolate_namespace MyEngine
initializer "my_engine.include_controller" do |app|
ActionController::Base.send :include, MyEngine::MyController
end
end
end
Upon server start or running a console I get...
uninitialized constant MyEngine::MyController (NameError)
I have the gem controllers in their namespaced directory and to reiterate this works in Rails 6.1.
I have also tried these variations with the same error...
ActionController::Base.include MyEngine::MyController
ActiveSupport.on_load :action_controller_base do
include MyEngine::MyController
end
If I put the following in the main app's ApplicationController instead then it works...
include MyEngine::MyController
Does anyone have any insight to how these hooks need to be called or should I report this as a bug to the rails team?
I was able to fix the same issue by pushing the required files to the autoload_once_paths configuration. Here an example of a possible fix.
module MyEngine
class Engine < ::Rails::Engine
isolate_namespace MyEngine
config.autoload_once_paths << "#{root}/app/controllers"
initializer "my_engine.include_controller" do |app|
ActionController::Base.send :include, MyEngine::MyController
end
end
end
Sources:
https://edgeguides.rubyonrails.org/autoloading_and_reloading_constants.html#config-autoload-once-paths
https://github.com/charlotte-ruby/impressionist/issues/305
https://github.com/hotwired/turbo-rails/blob/main/lib/turbo/engine.rb
I have a app/extensions folder where my custom exceptions reside and where I extend some of the Ruby/Rails classes. Currently there are two files: exceptions.rb and float.rb.
The folder is specified in the ActiveSupport::Dependencies.autoload_paths:
/Users/mityakoval/rails/efo/app/extensions/**
/Users/mityakoval/rails/efo/app/assets
/Users/mityakoval/rails/efo/app/channels
/Users/mityakoval/rails/efo/app/controllers
/Users/mityakoval/rails/efo/app/controllers/concerns
/Users/mityakoval/rails/efo/app/extensions
/Users/mityakoval/rails/efo/app/helpers
/Users/mityakoval/rails/efo/app/jobs
/Users/mityakoval/rails/efo/app/mailers
/Users/mityakoval/rails/efo/app/models
/Users/mityakoval/rails/efo/app/models/concerns
/Users/mityakoval/rails/efo/app/template.xlsx
/Users/mityakoval/.rvm/gems/ruby-2.4.1#web_app/gems/font-awesome-rails-4.7.0.2/app/assets
/Users/mityakoval/.rvm/gems/ruby-2.4.1#web_app/gems/font-awesome-rails-4.7.0.2/app/helpers
/Users/mityakoval/rails/efo/test/mailers/previews
The reason for it to be listed there twice is that it should be automatically loaded since it was placed under app directory and I have also manually added it to the autoload_paths in application.rb:
config.autoload_paths << File.join(Rails.root, 'app', 'extensions/**')
The strange thing is that my exceptions.rb is successfully loaded at all times, but the float.rb isn't unless eager loading is enabled.
Answers to this question say that it might be related to Spring (which I tend to believe), so I've added the folder to spring.rb:
%w(
.ruby-version
.rbenv-vars
tmp/restart.txt
tmp/caching-dev.txt
config/application.yml
app/extensions
).each { |path| Spring.watch(path) }
I've restarted Spring and the Rails server multiple times and nothing worked. Does anyone have any suggestions?
Ruby version: 2.4.1
Rails version: 5.1.5
EDIT
/Users/mityakoval/rails/efo/app/extensions/float.rb:
class Float
def comma_sep
self.to_s.gsub('.', ',')
end
end
rails console:
irb> num = 29.1
irb> num.comma_sep
NoMethodError: undefined method `comma_sep' for 29.1:Float
from (irb):2
A better way to monkeypatch a core class is by creating a module and including it in the class to be patched in an initializer:
# /lib/core_extensions/comma_seperated.rb
module CoreExtensions
module CommaSeperated
def comma_sep
self.to_s.gsub('.', ',')
end
end
end
# /app/initializers/core_extensions.rb
require Rails.root.join('lib', 'core_extensions', 'comma_seperated')
# or to require all files in dir:
Dir.glob(Rails.root.join('lib', 'core_extensions', '*.rb')).each do |f|
require f
end
Float.include CoreExtensions::CommaSeperated
Note that here we are not using the Rails autoloader at all and explicitly requiring the patch. Also note that we are placing the files in /lib not /app. Any files that are not application specific should be placed /lib.
Placing the monkey-patch in a module lets you test the code by including it in an arbitrary class.
class DummyFloat
include CoreExtensions::CommaSeperated
def initialize(value)
#value = value
end
def to_s
#value.to_s
end
end
RSpec.describe CoreExtensions::CommaSeperated do
subject { DummyFloat.new(1.01) }
it "produces a comma seperated string" do
expect(subject.comma_sep).to eq "1,01"
end
end
This also provides a much better stacktrace and makes it much easier to turn the monkey patch off and on.
But in this case I would argue that you don't need it in the first place - Rails has plenty of helpers to humanize and localize numbers in ActionView::Helpers::NumberHelper. NumberHelper also correctly provides helper methods instead of monkeypatching a core Ruby class which is generally best avoided.
See:
3 Ways to Monkey-Patch Without Making a Mess
I have some modules inside the lib folder in rails i.e.:
/lib/myapp/lib/**
I am working on them in development, however each time I have to restart server. I have been through a number of different questions on SO but most of them are for not for rails 3.1
I currently have an initializer that does this;
if Rails.env == "development"
lib_reloader = ActiveSupport::FileUpdateChecker.new(Dir["lib/**/*"], true) do
Rails.application.reload_routes! # or do something better here
end
ActionDispatch::Callbacks.to_prepare do
lib_reloader.execute_if_updated
end
end
if Rails.env == "development"
lib_reloader = ActiveSupport::FileUpdateChecker.new(Dir["lib/myapp/lib/*"], true) do
Rails.application.reload_routes! # or do something better here
end
ActionDispatch::Callbacks.to_prepare do
lib_reloader.execute_if_updated
end
end
Is there a generic way to do this? Its very time consuming having to restart the server every single time!
Get rid of the initializer and in your application.rb file put following line:
config.autoload_paths += Dir["#{config.root}/lib/**/"]
One thing to watch out is that your module and class names should follow the naming convention for autoreload to work. for example if you have file lib/myapp/cool.rb, then your constant for class/module declaration in cool.rb should look like this:
Myapp::Cool
If you have file lib/myapp/lib/cool.rb and you want it to use Cool as class/module name instead of Myapp::Lib::Cool then your autoload should look like this:
config.autoload_paths += Dir["#{config.root}/lib/myapp/lib/**/"]
As long as you are running in devmode, rails will automatically reload all classes/modules that are in autoload path and follow naming conventions.
Add to application_controller.rb or your base controller:
before_filter :dev_reload if Rails.env.eql? 'development'
def dev_reload
# add lib files here
["rest_client.rb"].each do |lib_file|
ActiveSupport::Dependencies.load_file lib_file
end
end
Worked for me.
I'm trying to reference Rails.root in my application.rb but it is nil, why is that?
I can explain why, but I can't give you a workaround.
Rails.root is defined in rails/railties/lib/rails.rb
def root
application && application.config.root
end
In application.rb, the instance of application is not yet created, because the Application class is being defined... The application is only initialized after, in environment.rb:
# Load the rails application
require File.expand_path('../application', __FILE__)
# Initialize the rails application
Testapp::Application.initialize!
EDIT
The workaround is right before our eyes:
my_rails_root = File.expand_path('../..', __FILE__)
Are you using Rails 3.x? If not, you should be using RAILS_ROOT rather than Rails.root.
I had the same issue when I tried to use it before the module and class declaration. Try using it inside and see if that makes a difference e.g.
module MyApp
class Application < Rails::Application
puts Rails.root
end
end