ActiveSupport Autoloading not working outside of Rails - ruby-on-rails

Given a file structure of
lib
app/
feature.rb
app.rb
And given the file app.rb that consists of:
require 'active_support'
require 'active_support/dependencies'
module App
extend ActiveSupport::Autoload
autoload :Feature, 'app/feature.rb'
def self.start
p Feature.new
end
end
It complains that LoadError: cannot load such file -- app/feature.rb
I've tried passing the absolute system path, no path, relative path to feature.rb from the module and the relative path from the execution point (one dir above lib).
This appears to be exactly the same thing that Rails is doing in the source.
Seems like this should be pretty intuitive, no?

The solution is the add the files to the Ruby interpreter load path like this: $LOAD_PATH.unshift(File.dirname(__FILE__)).
What that does is basically, allow Ruby to lookup those files. In Rails that is configured already when a new app is generated. When building a gem, in the spec you can add files or dirs to the load path. Outside of those, say a script in this instance, you must manually do that. autoload does not do that by default so you must specify (somewhat confusingly to me) the paths that Ruby can access.
I'm sure someone can clean up this explanation but that is how I understand it.

I don't believe Rails has lib on its autoload paths by default. We've got the following in config/application.rb:
config.eager_load_paths << Rails.root.join('lib')

Related

Rails.root - uninitialized constant

I am writing a script located in /bin of a rails 5 project. Basically I am trying to iterate through various files and paths. Something to help me with this effort is Rails.root so I don't have to worry about relative paths etc
The problem is, when I am attempting to use Rails.root in my script, I am getting an error:
uninitialized constant Rails (NameError)
My script looks something like this
bin/my_class.rb
class MyClass
def initialize
...
end
def my_function
...
Rails.root.to_s
end
end
MyClass.new.my_function
Then i call my script like
ruby bin/template_check.rb foo=bar
And it's breaking on the line where I am calling Rails.root with the error message:
uninitialized constant Rails (NameError)
I have tried
moving Rails.root to a constant above the class (thought maybe it was a name conflict or something?). didn't work
changing the name to ::Rails.root.to_s. did not work either
Does anyone know why I cannot access Rails.root inside of my script?
edit
I was able to get this resolved by adding
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment'))
to the top of my script. This loads rails. as a default, the script is slower, and I'm not a huge fan of loading all of rails just to use Rails.root, but for now it works.
Other solutions like the answers below are to either use rails runner to run the script, or to wrap everything in a rake task.
You can try with
rails r bin/my_class.rb if your file is under bin
other wise you can use
rails r #{file_name}.rb specify the file name
Run with $ rails runner bin/my_class.rb. You can find out more about rails runner here

LoadError for custom Ruby module

I am new to Ruby. I am dealing with a codebase of the following nature.
I have a main/ directory containing my entire codebase, and inside it I have files like:
main/lib/foo/test1.rb and
main/app/bar/test2.rb
Inside main/app/bar/test2.rb there is the line: require 'test1'
However, if I am in the main/ directory and I run ruby main/app/bar/test2.rb I get the following error: require': cannot load such file -- access_control (LoadError)
Now, upon Googling, I think this has something to do with /config/application.rb and adding the line: config.autoload_paths += Dir["#{config.root}/lib", "#{config.root}/lib/**/". I do this, but it doesn't seem to make any difference.
Presumably my production environment knows where to look in the require 'test1.rb' statement, while my environment does not. How can I fix this?
Thanks!
It sounds like you have a Ruby on Rails application.
Given that, you probably don't want to run any contained script directly with ruby. If it's designed to be executed directly, try running it with rails runner path/to/file.rb (from inside the directory that contains app/, lib/, config/, etc).
If it's inside app/ or lib/, though, it's more likely intended to be loaded as a library from some other script elsewhere in the application... those would both be unusual places to keep a stand-alone executable.

Issue Deploying Rails App to Heroku JWT auth.rb located in the Lib folder?

I'm trying to host an Rails API on Heroku and I'm having an issue when I sign up a user. Say's there's an error on Line 29. Line 29 is
jwt = Auth.encrypt({ user_id: #user_id })
Which makes a call to my Auth class, which isn't located in my project/app folder at all, but located outside in my project/lib/auth.rb. Could that be the issue?
If this works in dev, then yes, the issue is probably that the file is located under lib and Rails hasn't been configured to autoload files from that path.
You can either get rails to autoload files within lib by setting the following in your application.rb file:
config.autoload_paths << Rails.root.join('lib')
Or, alternatively you can add require 'auth' in your user_controller.rb.

Load lib files in production

I've upgraded one of my apps from Rails 4.2.6 to Rails 5.0.0. The Upgrade Guide says, that the Autoload feature is now disabled in production by default.
Now I always get an error on my production server since I load all lib files with autoload in the application.rb file.
module MyApp
class Application < Rails::Application
config.autoload_paths += %W( lib/ )
end
end
For now, I've set the config.enable_dependency_loading to true but I wonder if there is a better solution to this. There must be a reason that Autoloading is disabled in production by default.
My list of changes after moving to Rails 5:
Place lib dir into app because all code inside app is autoloaded in dev and eager loaded in prod and most importantly is autoreloaded in development so you don't have to restart server each time you make changes.
Remove any require statements pointing to your own classes inside lib because they all are autoloaded anyway if their file/dir naming are correct, and if you leave require statements it can break autoreloading. More info here
Set config.eager_load = true in all environments to see code loading problems eagerly in dev.
Use Rails.application.eager_load! before playing with threads to avoid "circular dependency" errors.
If you have any ruby/rails extensions then leave that code inside old lib directory and load them manually from initializer. This will ensure that extensions are loaded before your further logic that can depend on it:
# config/initializers/extensions.rb
Dir["#{Rails.root}/lib/ruby_ext/*.rb"].each { |file| require file }
Dir["#{Rails.root}/lib/rails_ext/*.rb"].each { |file| require file }
I just used config.eager_load_paths instead of config.autoload_paths like mention akostadinov on github comment:
https://github.com/rails/rails/issues/13142#issuecomment-275492070
# config/application.rb
...
# config.autoload_paths << Rails.root.join('lib')
config.eager_load_paths << Rails.root.join('lib')
It works on development and production environment.
Thanks Johan for suggestion to replace #{Rails.root}/lib with Rails.root.join('lib')!
Autoloading is disabled in the production environment because of thread safety. Thank you to #Зелёный for the link.
I solved this problem by storing the lib files in a lib folder in my app directory as recommended on Github. Every folder in the app folder gets loaded by Rails automatically.
There must be a reason that Autoloading is disabled in production by
default.
Here is a long discussion about this issue. https://github.com/rails/rails/issues/13142
This allows to have lib autoreload, and works in production environment too.
P.S. I have changed my answer, now it adds to both eager- an autoload paths, regardless of environment, to allow work in custom environments too (like stage)
# config/initializers/load_lib.rb
...
config.eager_load_paths << Rails.root.join('lib')
config.autoload_paths << Rails.root.join('lib')
...
Just change config.autoload_paths to config.eager_load_paths in config/application.rb file. Because in rails 5 autoloading is disabled for production environment by default. For more details please follow the link.
#config.autoload_paths << "#{Rails.root}/lib"
config.eager_load_paths << Rails.root.join('lib')
It works for both environment development and production.
In some sense, here is a unified approach in Rails 5 to centralize eager and autoload configuration, in the same time it adds required autoload path whenever eager load is configured otherwise it won't be able to work correctly:
# config/application.rb
...
config.paths.add Rails.root.join('lib').to_s, eager_load: true
# as an example of autoload only config
config.paths.add Rails.root.join('domainpack').to_s, autoload: true
...
For anyone struggled with this like me, it's not enough to just place a directory under app/. Yes, you'll get autoloading but not necessary reloading, which requires namespacing conventions to be fulfilled.
Also, using initializer for loading old root-level lib will prevent reloading feature during development.
The only thing that worked for me is adding the nested lib path in eager load paths AND adding a require_dependency in a config.to_prepare block.
# application.rb
...
config.to_prepare do
require_dependency("#{Rails.root}/lib/spree/core/product_filters.rb")
end
config.eager_load_paths << Rails.root.join('lib').join('spree').join('core')
...
Moving the lib folder to app helped solve a problem, my Twitter api would not run in production. I had "uninitialized constant TwitterApi" and my Twitter API was in my lib folder.
I had config.autoload_paths += Dir["#{Rails.root}/app/lib"] in my application.rb but it didn't work before moving the folder.
This did the trick
I agree that some dependencies belong in lib and some may belong in app/lib.
I prefer to load all files I've chosen to put in lib for all environments, hence I do this in config/application.rb immediately after requiring the bundle but before opening the MyApplicationName module.
# load all ruby files in lib
Dir[File.expand_path('../../lib/**/*.rb', __FILE__)].each { |file| require file }
This doesn't depend on Rails.root (which isn't defined yet), and doesn't depend on eager loading (which may be off for an environment).
to summarize Lev's answer: mv lib app was enough to have all my lib code autoloaded / auto-reloaded.
(rails 6.0.0beta3 but should work fine on rails 5.x too)

`const_missing': uninitialized constant (NameError)

Every time I try to run any class from my rails 2.2 app's lib directory using "script/runner -e production ClassName.run" I get the following error:
/usr/lib/ruby/gems/1.8/gems/rails-2.2.2/lib/commands/runner.rb:47:
/usr/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/dependencies.rb:89:in `const_missing': uninitialized constant ClassName (NameError)"
For some reason I don't get this error when I run it as test instead of production, and everything works fine.
O yeah run is a class method i.e. def self.run .... end
Any suggestions?
That error occurs when ruby can't find a Class or Module. I'd start out by:
Checking gem dependencies (are they same for all environments?)
Search your code for anything that defines ClassName, particularly Modules, Classes and plugins.
disable each of your plugins in dev, does any plugin suddenly cause that error?
if the code is in a lib add the lib require statement to your production.rb to force the lib to be loaded in production.
Hope that helps.
update Just to summarise the comments it was option 4.
Unless you only want to load the lib in production you should think about making sure all environments load the lib by doing one of the following:
Create a rails initializer (a .rb file under config/initializers) with the require in it
Add the path to the lib in config/environment.rb by enabling and modifying the config.load_paths variable.
I had multiple class definition(all STI classes) on the same file. and when I separated the definition into their respective files, It works

Resources