This message was imported from the Ruby/Rails Modularity Slack server. Find more info in the import thread.
Message originally sent by slack user U723PSM8PD2
Hi.
We’ve been working on modularising our monolith, and we’ve found a big stumbling block is the two-way dependency caused by belongs_to
/ has_many
(or has_one
) So if we have one pack in engines/companies
and one in engines/employees
, and we have…
# engines/companies/app/models/company.rb
class Company < ApplicationRecord
has_many :employees, inverse_of: :company
end
# engines/employees/app/models/employee.rb
class Employee < ApplicationRecord
belongs_to :company, inverse_of: :employees
end
…then whichever way round we define the dependencies, Packwerk will complain about a cyclic dependency.
Reading back a bit here, it seems like the preferred solution is to have engines/employees
depend on engines/companies
and then replace the has_many
with a class method:
# engines/companies/app/models/company.rb
class Company < ApplicationRecord
end
# engines/employees/app/models/employee.rb
class Employee < ApplicationRecord
belongs_to :company
def self.for_company(company)
where(company: company)
end
end
…which does break the dependency, but has the unpleasant side-effect that we can’t define the inverse relationship any more, and so we open ourselves up to N+1 errors. So we’ve been using ActiveSupport hooks to mix the has_many
into the parent class from the child pack:
# engines/companies/app/models/company.rb
class Company < ApplicationRecord
end
ActiveSupport.run_load_hooks(:myorg_company)
# engines/employees/app/models/employee.rb
class Employee < ApplicationRecord
belongs_to :company, inverse_of: :employees
end
# engines/employees/app/models/concerns/company_extensions.rb
module Employees::CompanyExtensions
extend ActiveSupport::Concern
included do
has_many :employees, inverse_of: :company
end
end
# engines/employees/config/initializers/engines/companies.rb
ActiveSupport.on_load(:myorg_company) do
include Employees::CompanyExtensions
end
I’m about to propose that we use this approach throughout our codebase in order to tame the N+1s that have been sneaking in since we started modularising. What drawbacks do you see with this approach? I’d like to anticipate some of those concerns in my proposal (or be convinced out of it if they’re too severe).