F:\app\a.rb
require 'rubygems'
require 'active_support'
module A
extend ActiveSupport::Autoload
autoload :B,'F:\app\test\b'
end
F:\app\a\b.rb
module A
class B
end
end
in the irb:
require 'f:\app\a'
A::B
=> NameError: uninitialized constant A::B
Which place I wrong?
When declaring an autoload, the specified path has to be relative to one in $LOAD_PATH or $:, the short-hand alias of same. Since autoload is built in to Ruby, it does not require ActiveSupport. The Autoload library actually does a bunch of other things not specifically related to that, basically making the autoload smarter but still functionally similar.
I don't think you're able to use absolute paths as you have here.
Usually you see these defined as:
autoload(:B, 'a/b')
This is because somewhere you've defined your load path:
$LOAD_PATH << 'F:\app'
Try using load instead of autoload.
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.
I see that the following configuration in application.rb:
config.autoload_paths += %W(#{config.root}/app/models/custom_pack/base)
config.autoload_paths += Dir["#{config.root}/app/models/custom_pack/custom_container/**/"]
config.autoload_paths += Dir["#{config.root}/app/models/custom_pack/helpers/**/"]
config.autoload_paths += Dir["#{config.root}/app/models/custom_pack/extensions/**/"]
is in the autoload_paths:
Rails.application.config.autoload_paths.grep /custom/
=> ["/Users/dviglione/projects/core/app/models/custom_pack/base", "/Users/dviglione/projects/core/app/models/custom_pack/custom_container/", "/Users/dviglione/projects/core/app/models/custom_pack/helpers/", "/Users/dviglione/projects/core/app/models/custom_pack/extensions/"]
But these files are not being loaded. Because I get an error:
`method_missing': undefined method `create_element' for #<MyElement:0x007f82eca39898>
That create_element method is defined in one of the files that should have been loaded.
However, when I use require/require_relative, it does work:
# /initializers/my_initializer.rb
require "custom_pack/base"
# models/custom_pack/base.rb
require_relative 'custom_container/base'
require_relative 'custom_container/parent'
require_relative 'custom_container/child'
Dir[File.join(File.expand_path("../helpers", __FILE__), "*_helper.rb")].each { |file| require file }
Dir[File.join(File.expand_path("../extensions", __FILE__), "*_adapter.rb")].each { |file| require file }
From what I read from the documentation, when you use require 'erb', Ruby looks for the file in the directories listed in $LOAD_PATH. That is, Ruby iterates over all its directories and for each one of them checks whether they have a file called "erb.rb". If it finds any of them, the interpreter loads it and ends the search. Otherwise, it tries again in the next directory of the list. If the list gets exhausted, LoadError is raised. For autoloading, the idea is that when a constant like Post is hit and missing, if there's a post.rb file for example in app/models Rails is going to find it, evaluate it, and have Post defined as a side-effect. Rails has a collection of directories similar to $LOAD_PATH in which to look up post.rb. That collection is called autoload_paths.
So why does require/require_relative work, but autoload_paths does not?
There's a number of things that could be going on here.
1.If a file is following the path: lib/foo/bar.rb the class needs to be defined like:
class Foo::Bar
end
2.Autoload also lazy loads files, which means your models are only loaded when you call them. For example:
puts "I was loaded!"
class MyLibrary
end
irb(main):001:0> require 'mylibrary'
I was loaded!
=> true
irb(main):001:0> autoload :MyLibrary, 'mylibrary'
=> nil
irb(main):002:0> MyLibrary.new
I was loaded!
=> #<MyLibrary:0x0b1jef>
As for the actual usage of autoload, it's recommended to start using require instead. In theory autoload sounds nice but it can cause problems when certain classes are dependent on other modules. Because of this autoload is in the process of being deprecated.
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
Recently, I can't make changes to my app without restarting my development server, otherwise I receive this error:
LoadError (Unable to autoload constant BotFeedback, expected ../../bot_feedback.rb to define it)
This hasn't been a problem before and I'm not entirely sure why this has become a problem. I have these settings configured in application.rb:
# Auto-load the bot and its subdirectories
config.paths.add File.join('app', 'bot'), glob: File.join('**', '*.rb')
config.autoload_paths += Dir[Rails.root.join('app', 'bot', '*')]
My app/bot folder includes files such as:
bot.rb with:
require "#{Rails.root}/app/bot/orderbot.rb"
Bot.on :message do |message|
OrderBot.new()
..
end
def somefunction
OrderBot.new()
..
end
orderbot.rb with:
require "#{Rails.root}/app/bot/bot_feedback.rb"
require "#{Rails.root}/app/bot/bot_demo.rb"
require "#{Rails.root}/app/bot/bot_helper.rb"
class OrderBot
include BotFeedback
include BotDemo
include BotHelper
(many more includes)
..
end
bot_feedback.rb with:
require "#{Rails.root}/app/models/concerns/sharedmethods.rb"
class OrderBot
include Sharedmethods
module BotFeedback
...
end
end
bot_demo.rb with:
class OrderBot
module BotDemo
..
end
end
bot_helper.rb with:
require "#{Rails.root}/app/models/concerns/sharedmethods.rb"
class OrderBot
include Sharedmethods
module BotHelper
...
end
end
My guess is that including the sharedmethods file is causing this because I don't see anything else being a problem. Changing the sharedmethods file in the rails app has always seemed to require restarting the server.
I would appreciate any help/suggestions.
UPDATE:
Looks like using 'load' instead of 'require' seems to solve the problem. I'm not sure this is the right way to go about it though.. because it will take up more memory if the files are being loaded again and again?
load "#{Rails.root}/app/bot/bot_feedback.rb"
load "#{Rails.root}/app/bot/bot_demo.rb"
load "#{Rails.root}/app/bot/bot_helper.rb"
Finally figured out the answer after learning everything about rails 'autoload' and 'require'
I don't actually need to 'require' any of the files because they are already autoloaded. The culprit was the 'module', it wasn't necessary.
I'm having some trouble to namespace a module that I include in a model.
in /app/models/car.rb
class Car
include Search::Car
end
in /lib/search/car.rb
module Search
module Car
include ActiveSupport::Concern
# methods in here
end
end
in /config/application.rb
config.autoload_paths += Dir["#{config.root}/lib/**/"]
config.autoload_paths += Dir["#{config.root}/lib/search/*"]
The weird thing is that I don't get any errors directly when I fire up the server.
But if I refresh the browser after a while I get this error:
Expected #{Rails.root}/lib/search/car.rb to define Car
The nature of the problem indicates that it has something to do with:
/config/environments/development.rb
config.cache_classes = false
I also tried put a file search.rb directly in /lib where I define Search:
module Search
# Put shared methods here
end
What am I doing wrong?
UPDATE:
Ok, turns out that if I rename Search::Car to Search::CarSearch it works.
Is it not possible to have Modules/Classes of the same name in a different scope?
The error is coming from your autoload_paths. config.autoload_paths += Dir["#{config.root}/lib/**/"] will add all directories and their subdirectories under lib directory. meaning that you are telling rails to autoload lib/search/ directory, therefore car.rb under that directory is expected to define Car and not Search::Car. In order for rails to expect lib/search/car.rb to define Search::Car, you need to autoload lib/ directory and not lib/search. if you change you autoload to config.autoload_paths += Dir["#{config.root}/lib/"] and put search.rb in lib/ with following code:
module Search
require 'search/car'
end
then rails will understand and expect lib/search/car.rb do define Search::Car and referencing Car module/class in other places of your code will not reference to this car.rb.
You should remove this line (you should only have autoload for lib directory):
config.autoload_paths += Dir["#{config.root}/lib/search/*"]