Rails autoloader not loading module - ruby-on-rails

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.

Related

Rails modules as strict namespaces

I'm quite new to rails and I'm a bit confused of how modules work here. I have a project structure like this:
# app/models/foo.rb
class Foo < ActiveRecord
# lib/external_service/foo.rb
module ExternalService
class Foo
# lib/external_service/bar.rb
module ExternalService
class Bar
attribute :foo, Foo # not the model
I have worked with many coding languages before and I expected it to be easily possible to use 'Foo' inside Bar and ExternalService just like that but
LoadError: Unable to autoload constant Foo, expected lib/external_service/foo.rb to define it
The ExternalService::Foo should normally not even be visible outside of ExternalService but the whole project dies on this thing
Am I just missing a kinda 'strict mode'-notation or anything to make sure that I obviously mean ExternalService::Foo inside the service and prevent the service from killing my model?
I know I can just prepend the module but i wanna keep the code readable.
so you are using rails 4
if you want to create a module, first you need to import or autoload your lib folder
for example in application.rb you can add lib folder to autoload:
config.autoload_paths << Rails.root.join('lib')
after that because you are using rails you should create a folder hierarchy with snake cased name of your module hierarchy
for example if you have:
module ExternalService
class Foo
...
end
end
your foo.rb file should be in a folder with name 'external_service'
{{project_root}}/lib/external_service/foo.rb
folder hierarchy is convention of rails.
Ruby behaves just like this and it's totally ok.
In this case the Foo-Model is already loaded, so ruby prefers this instead of the local one. Also alphabetically app/ is before lib/
A not so beautiful but quick fix is just to call it like this:
attribute :foo, ExternalService::Foo

Namespacing something in a custom directory under app?

I'm wanting to create a custom directory under app, and have a corresponding namespace, like:
app/my_widgets
and
#app/my_widgets/foo
module MyWidgets
class Foo
#...
end
end
This doesn't work.
LoadError: Unable to autoload constant Foo, expected /project_dir/app/my_widgets/foo.rb to define it.
I am calling:
MyWidgets::Foo.new()
I had guessed this could be because rails autoloads constants from subdirectories of app/... However, adding:
config.autoload_paths += %W{#{config.root}/app"}
Doesn't fix it as I thought it might. Is this a lost cause?
I have tried moving my_widgets/ to something else, eg 'services/' under app, and then everything works as expected.
Is there any way to have a namespace & corresponding directory directly under app/, though?

Way to load folder as module constant in rails app directory

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.

How can I add a services directory to the load path in Rails?

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.

Rails doesn't load my module from lib

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")

Resources