I have a hard time understanding an issue with Rails autoloading.
So I have a controller which is located in a folder named api. The constroller is declared as class Api::TestController.
In another folder, I have a file called app/my_folder/service_name/api.rb
In production, I get this error : load_missing_constant': Unable to autoload constant Api, expected /app/my_folder/service_name/api.rb to define it (LoadError)
application.rb has this line :
config.autoload_paths += Dir[Rails.root.join('app', 'my_folder', '{**/}')]
I have no issues in development.
Why is this happening ?
Its happening since the auto load paths are only used for auto loading. In order for your class to be loaded in production you need to augment the eager_load_paths as well:
config.eager_load_paths += Dir[Rails.root.join('app', 'my_folder', '{**/}')]
However this isn't exactly a great practice - any directory in app is automatically used as a root directory. What you're doing is adding every subdirectory of a given directory as root directories.
Using the scope resultion operator (class Api::TestController) for namespace definition should also be avoided as it leads to suprising constant lookup behavior and is the source of autoloading bugs. Instead you should nest the class explicitly and setup an inflection for the acronym:
# config/initializers/inflections.rb
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym 'API'
end
# every time you use Api as a constant name a kitten dies.
module API
class TestController
puts Module.nesting.inspect # [TestController::API, API]
end
end
Related
So have a rails 5 project and would like to load a directory like this
/app
/services
/user
foo.rb
as the constant ::Services::User::Foo
Does anyone have experience in getting rails autoload paths to load the constants in this manner?
foo.rb
module Services
module User
class Foo
end
end
end
SOLUTION
Add this to your application.rb file
config.autoload_paths << Rails.root.join('app')
See discussions here on autoloading
https://github.com/rails/rails/issues/14382#issuecomment-37763348
https://github.com/trailblazer/trailblazer/issues/89#issuecomment-149367035
Auto loading
You need to define Services::User::Foo inside app/services/services/user/foo.rb
If you don't want this weird subfolder duplication, you could also move services to app/models/services or lib/services.
You could also leave foo.rb in app/services/user/foo.rb, but it should define User::Foo.
Eager loading
If you don't need any magic with namespaces and class names, it is pretty straightforward :
Dir[Rails.root.join('app/services/**/*.rb')].each{|rb| require rb}
This will eagerly load any Ruby script inside app/services and any subfolder.
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'd like to add a microapp for errors to be processed (file I placed inside the lib folder).
module MyApp
class Application < Rails::Application
config.autoload_paths += %W( #{config.root}/lib )
config.exceptions_app = FooApp.new(Rails.public_path)
end
end
But Rails raises an unitialized constant (NameError) during it's initialization. Recently I found a similar post and tried every solution from there, but got no result. What actually I've tried:
Name a class and a filename according to convention (even tried to simplify to a single word - class Foo, filename lib/foo.rb).
Use config.eager_load_paths += %W( #{config.root}/lib ) rather than config.autoload_paths, same effect. :(
Create an initializer file and load a class with require: require "#{Rails.root}/lib/foo"
It doesn't work - seems initializers are performed after the initial Rails configuration.
Move the file into app/misc but it doesn't help.
Put a class inside a module with the same name, rename a class while it's still in the module - no effect.
The only working solution I found - is to require a file right inside the Rails configuration block, but... it's a freaky solution. Probably there still exists an idiomatic one?
Is there a way to tell Rails that all files in a certain folder are contained in a certain namespace?
Ie.
I have a file bar.rb in app/foo. Rails will assume this file defines Bar, but instead I want this file to define Foo::Bar.
I know I can achieve this by adding my root to Rails' autoload paths, but that isn't a real solution. Is there any other way I can tell Rails that all files within app/foo reside in the Foo namespace?
EDIT: File tree for clarification
app
assets
controllers
models
foo
bar.rb
quux.rb
I would like to be able to define Foo::Bar and Foo::Quux in respectively bar.rb and quux.rb, while also using Rails autoloading. Without having to resort to the tree structure as below:
app
assets
controllers
models
foo
foo
bar.rb
quux.rb
You can autoload files with namespaces corresponding to directories inside /app by adding /app to your autoload paths in your application config.
# config/application.rb
config.autoload_paths << "#{config.root}/app"
I'm not sure whether or not this is what the author of the question meant by
I know I can achieve this by adding my root to Rails' autoload paths, but that isn't a real solution.
But this is definitely a real solution and the correct one. Doing this is fine and not at all unusual in my experience.
There are only minor side effects which are the cost of adopting the inconsistent naming conventions you want. If you refer to an undefined constant that matches the name of another directory in the app you'll get a slightly different error message.
# Models::Foo => LoadError (Unable to autoload constant Models::Foo, expected /app/models/foo.rb to define it)
But if you have existing namespaces that match directories in app loading will still work fine. Here's what Rails is doing:
It takes the paths added to config.autoload_paths and adds them to the defaults (the directories under app: app/models, app/controllers, app/foo etc). Then when a constant is referenced that is not already loaded Rails proceeds through those paths, looking for paths that match the constants. So when you reference Foo::Bar it looks for app/models/foo/bar.rb, app/controllers/foo/bar.rb etc. until it finds a file that defined Foo::Bar. All we're doing is adding app/foo/bar.rb to that lookup.
Rails doesn't assume namespace, it assumes path to the source file depending on a namespace, so:
$ cat app/foo/bar.rb
module Foo
class Bar
def bar
puts "i'm here"
end
end
end
$ rails c
2.2.0 :007 > b=Foo::Bar.new
=> #<Foo::Bar:0x00000005272770>
2.2.0 :008 > b.bar
i'm here
=> nil
You can try adding the folder you Rail's autoload paths configuration
# config/application.rb
# Rails 4
config.autoload_paths << Rails.root.join("app", "foo")
# Rails 5+
config.eager_load_paths << Rails.root.join("app", "foo")
I place a file name g.rb in side Rails.root/lib folder
The file content is like this:
module Google
end
Then I add
config.autoload_paths += %W(#{config.root}/lib #{Rails.root}/app/delayed_jobs)
to my Rails.root/config/application.rb
However, when I try to invoke Google from rails console, an exception is thrown. The exception goes away only if I execute require 'google'.
Why? Shouldn't my file is autoloaded and shouldn't I access the module without any extra require statement?
Hmm, I discovered an interesting thing. In order for Rails to auto load my class, the class name should be compliant to the file name and the folder structure.
For example, if I want to have Google module autoloaded, I must placed it inside google.rb, directly under /lib (incase I specify autoload from /lib).
If I want to auto load Google::Docs, then I either place it inside google.rb or google/docs.rb
I had a similar problem with getting my module to run on Heroku. In addition to the autoload naming convention stated by Stephen C, I found out that the module code must be require'd due to a threadsafe assumption made by the Rails' production environment on Heroku (even though threadsafe was commented out in my production.rb configuration file.) As soon as I require'd the module file before calling include on the module, everything started to work.
require 'mymodule'
include Mymodule
Please take a look at this excellent article on the subject of getting Modules to load correctly in Heroku (production).
That's because the point of autoload is not to 'require' everything up front (startup penalty). Classes are loaded as they are needed/referenced. In order to do this, you need some way to know where to look for the class. Otherwise, you would have to load every file in the autoload directory in advance to see what classes are declared. It's a tradeoff, but requiring everything in advance (as marbaq suggests) is not autoloading.
You can use the autoload command as provided by Ruby, which takes two arguments, the module to load (symbolized, i.e. :Google in your case), and the second argument is the filename, which would be g.rb if lib is in your load path ($:). See the Ruby docs for autoload.
Change config.autoload_paths to config.eager_load_paths
(based on Rails issue #6850 and Force reload! from lib directory in rails 3.2 console)
I faced the same problem just now, and my "solution" (or rather workaround) was to manually require every needed file from Rails.root/lib in my application.rb.
require 'lib/message'
require 'lib/store'
require 'lib/vault/vault.rb'
require 'lib/custom_loggers'
module MyApplication
class Application < Rails::Application
My next step would be to categorize the files in module folders as you mention.
i found this solution recently
config/application.rb
module AppName
class Application < Rails::Application
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += Dir[Rails.root.join('app', 'models', '{**}')]
config.autoload_paths += Dir[Rails.root.join('app', 'lib', 'extensions')]
end
end
the first config call induces rails to auto-load all sub-directories of the app/models directory
so now i can have /app/models/sub_directory/model.rb auto-loaded
(handy for organising an app with a large code base)
the second config call induces rails to autoload the lib/extensions directory
hope this helps
note: i believe this is rails 3 specific