rails: rails 6 lib folder doesn't work

rails 6 lib folder doesn’t work

to reproduce:

rails new my_app

add a file to: lib/my_lib.rb

# lib/my_lib.rb
class MyLib

  def self.hello
    puts 'Hello'
  end
end

rails console

MyLib.hello

NameError: uninitialized constant MyLib
from (pry):1:in `__pry__'

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 16 (12 by maintainers)

Commits related to this issue

Most upvoted comments

Can I add lib folder to autoload paths? is it a best practice with Zeitwerk, or discouraged?

A better option is to add an app/lib folder which will be auto-loaded and eager-loaded by default without any extra configuration.

There are still legitimate cases where someone would want to use the lib. Mainly, it is code that is not specific to the application domain. Putting it in app/lib seems weird. There are a few concrete cases I use the lib folder for namely: reports, search, analytics, importing, exporting, integration to private apis without a gem.

Anything in the lib folder should be extractable to a separate gem and shared with the world.

In a rails 6+ application all you need to do is

# config/application.rb

config.autoload_paths << config.root.join('lib')

Further reading:

I think we can close this one.

The lib folder does not belong to the autoload paths since Rails 3. @pixeltrix’s is the recommended modern idiom if you want to autoload.

Otherwise, lib belongs to $LOAD_PATH and you can require files in that directory.

The lib folder does not belong to the autoload paths by default, you need to

require "my_lib"

before using MyLib.

@jeromedalbert that’s right.

Please note that you should also ignore subdirectories that are not meant to be namespaces. For example:

Rails.autoloaders.main.ignore('lib/tasks', 'lib/assets')

Otherwise, Zeitwerk would define a Tasks constant, but that is not your intention (and could potentially conflict with a genuine Tasks constant).

Thank you, @yagudaev, the autoload paths worked for me. I think that was my initial thought, I’m not sure why I got turned around about it. Maybe b/c most of the comments in this thread are saying to not do that, or maybe b/c I was thinking I want it to be reloaded, not autoloaded. Not sure, but adding config.autoload_paths << config.root.join('lib') is working thus far.

Just in case, let me add that lib is added to $LOAD_PATH by Rails automatically.

Bundler.require(*Rails.groups)

# lib not in $LOAD_PATH yet here.

module MyApp
  class Application < Rails::Application
    # Now, it is.
    ...
  end
end

In my view, conceptually there is no probem in making files within lib autoloadable and reloadable. There are dozens of gems that use Zeitwerk to load their code (their own instance).

You only need to be careful, because if you have

lib/tasks

and you put lib in as autoload path, you are saying that lib/tasks defines the namespace Tasks. So, you can do that, but you need to be careful. And that was the main reason lib was removed from the default autoload paths, because it has a mix of things.

You can still have lib/code, or tell Zeitwerk to ignore lib/tasks, but recently people have switched to leave tasks in lib/tasks, and actual Ruby code in app/lib.

However, you can pick and choose whatever combination feels better to you, technically a few are possible.

I have worked on apps that put code closely related to the application in app/, and code less closely related to the application (i.e. that could be extracted to a gem in the future) in lib/. Having the two folders separated (and not one included in another) makes sense according to this point of view.

So I wanted the app I was working on to work with lib/, but I was getting the following error with Rails 6, where the path was truncated:

No such file to load -- se_importer/omega.rb (LoadError)

The issue was caused by the last two lines in my config/application.rb:

    config.autoload_paths   << Rails.root.join('lib')
    config.eager_load_paths << Rails.root.join('lib')
    config.autoload_paths   << Rails.root.join('lib/**/*')
    config.eager_load_paths << Rails.root.join('lib/**/*')

Removing those lines to only keep the base paths worked great:

    config.autoload_paths   << Rails.root.join('lib')
    config.eager_load_paths << Rails.root.join('lib')

Thanks to @yagudaev for pointing to the right resources.

@JoshCheek are you trying to have a few apps inside of a rails project? What you are describing reminds me of Django which has many apps as it came from publishing so they had many domains/brands.

In rails, this is accomplished with Rails Engines: https://guides.rubyonrails.org/engines.html.

The hard part about “use app/lib” is that it implies I won’t be namespacing my constants, or that if I do, it’ll be using the Rails pattern of appending the namespace to the end (SmthController, SmthMailer, SmthPresenter),

There are a few component types like Models that don’t follow that naming convention. Some gems like graphql, activeadmin also take liberty with it. In this case, omitting it would make sense.

Otherwise the trick I mentioned should work well here it is:

# config/application.rb

config.autoload_paths << config.root.join('lib')

I’m using it here for a live project: https://github.com/yagudaev/search_on_rails_demo/blob/main/lib/search/in_memory.rb#L2 and https://github.com/yagudaev/search_on_rails_demo/blob/main/config/application.rb#L36. It’s a reference implementation of search for a course and new gem I’m working on.

The best practice to accomplish that nowadays is to move that code to app/lib. Only the Ruby code you want to reload, tasks or other auxiliary files are OK in lib.

If you do that, there is nothing to configure, it works automatically, and your code base remains simple and standard.

Let me clarify, since we are on it, that “reloading” does not “restart” the server, it only reloads application level code, what’s below app to get the idea. The server stays up, initializers do not run again, the operating system process is the same, etc. Only application code is reloaded in memory.

@yagudaev I agree that things in lib/ should be extractable into a separate gem.

However I do not think it is correct that files in lib/ should be auto-loaded, because a plain Ruby gem must require all of its files explicitly, which does not seem to be compatible with Rails auto-loading semantics.

I put “other” code that is app-specific into app/lib and assume it will be part of that Rails app permanently (and so I use ActiveSupport extensions etc. in that code). That code is auto-loaded (and reloaded) just fine.

For code that I plan to extract into a separate gem at some point, I put it in lib/ and I add that to $LOAD_PATH:

# config/application.rb

$LOAD_PATH.insert(0, File.expand_path(File.join(__dir__, '..', 'lib')))

This means I have to restart my Rails server every time I make changes to files in this directory, which is a pain (and your snippet above does not solve that issue - I just tested in a Rails 6 app). It may work if I remove the require statements but then the gem will not work standalone.

If there is a way to auto-reload code in lib/ while being both Rails-compliant and gem-compliant then that would be very useful. Until then I just suck it up and restart my server every time I make a change.