Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Zeitwerk, require_dependency and splitting Ruby classes into a few files

Before Rails 6 there was a simple way to split any Ruby class into multiple files without introducing any additional modules or concerns. It was quite useful for those God classes which happened to be a bit too large to be kept in one file:

# god.rb
class God < ApplicationRecord
  require_dependency 'god~callbacks'
  require_dependency 'god~scopes'
  require_dependency 'god~search'
  require_dependency 'god~something_else'
  require_dependency 'god~and_more'
  require_dependency 'god~and_even_more'
  ...
end

# god~callbacks.rb
class God
  before_save :nullify_blanks
  before_save :nullify_unrelated_attrs
  ...
end

and the file structure has looked like this:

models/
  god.rb
  god~callbacks.rb
  god~scopes.rb
  god~search.rb
  ...

And that has worked great — anytime you change anything in any of those files the changes were correctly reloaded.

Since Rails 6 a new class loader is used — the Zeitwerk. And that's great but seems that it doesn't work with require_dependency, at least when that method is used in such a non-typical way.

So the question is, is it possible to split a class definition into a few files and keep the auto-reloading working with Zeitwerk. And the goal is to split a class without introducing any inner modules/concerns.

And please, no need to comment on whether a 1000-line long class is a good design practice or not, that's not the point of the question.

like image 517
Dmitry Sokurenko Avatar asked Oct 25 '25 02:10

Dmitry Sokurenko


2 Answers

Technically I believe that is possible, though this way to split code is not conventional and the solution includes more vinegar than sugar :).

First, you need to tell Zeitwerk to ignore those files. For example, in an initializer you can do this:

# ignore accepts a glob pattern.
Rails.autoloaders.main.ignore("god~*")

Second, you need to load the files, so that reloading interprets them again:

class God < ApplicationRecord
  load "#{__dir__}/god~callbacks.rb"
  ...
end

I assume those files are not loaded in other places, so idempotence does not matter. If it matters, then you need some extra vinegar.

The solution above is untested, but you see the idea.

It is an unusual way to split code, you know that, normally you use modules and the name of the module reflects what the code is organizing, which is what the file names are doing. I'd recommend going with modules. But if you want to keep that style, in principle you can, though the solution is a bit forced.

like image 88
Xavier Noria Avatar answered Oct 26 '25 18:10

Xavier Noria


You can opt out of using Zeitwerk by setting config.autoload = :classic in your application.rb file. If you want to continue using Zeitwerk, then why not replace the require_dependency calls with include?

like image 38
Kevin Blicharski Avatar answered Oct 26 '25 16:10

Kevin Blicharski