In its essence, a Repository separates area objects from how they’re persevered and gives a restricted interface to entry them. It’s a tactical sample described with much more phrases by Fowler and Evans than I’d like to incorporate on this introduction.
It stands in full opposition to what ActiveRecord sample promotes. Why hassle remodeling one into one other?
The issue with ActiveRecord sample comes from its biggest energy. It’s a double-edged sword. Immensely helpful in fast prototyping for a “solopreneur”. Versatile for a well-knit and disciplined group. Spiralling uncontrolled in a large organisation with a number of groups engaged on a comparatively large legacy software.
As of now naked ActiveRecord::Base
begins with 350 occasion strategies on its public interface. Add to that 496 strategies of ActiveRecord::Relation
that one normally interacts with. Performing a bigger refactoring that covers all doable utilization patterns of such ActiveRecord fashions turns into a nightmare. Preliminary guidelines consists of:
- huge question API
- callbacks
- relations, its extensions and the standard behaviour
- gems within the
Gemfile
that reachActiveRecord::Base
— including new strategies and altering behaviours
That’s a big scope to cowl. It interprets to a sure price of time, vitality and confidence to tug out any change on it in a manufacturing system that earns cash.
I keep in mind just a few previous makes an attempt from my colleagues to regulate the scope of ActiveRecord surfaced in bigger codebases.
There was the not_activerecord to assist specific the boundaries. There have been numerous approaches to question objects that addressed the learn half.
I additionally vaguely recall a quote from Adam Pohorecki on a DRUG meetup that you may get 80% advantages out of Repository by placing 20% effort into shaping ActiveRecord like this:
class Transaction
def self.of_id(id)
discover(id)
finish
def self.last_not_pending_of_user_id(user_id)
the place.not(standing: "pending").the place(user_id: user_id).order(:id).final
finish
finish
It depends very a lot on the self-discipline of group — to deal with ActiveRecord::Base
strategies as “non-public” and solely entry the mannequin by the application-specific class strategies.
This the repository I’d make in the present day, with none exterior dependencies within the framework you have already got:
class TransactionRepository
class File < ActiveRecord::Base
self.table_name = "transactions"
finish
private_constant :File
Transaction = Information.outline(File.attribute_names.map(&:to_sym))
class << self
def of_id(id)
as_struct(File.discover(id))
finish
def last_not_pending_of_user_id(user_id)
as_struct(File.the place.not(standing: "pending").the place(user_id: user_id).order(:id).final)
finish
non-public
def as_struct(file)
Transaction.new(**file.attributes.symbolize_keys)
finish
finish
finish
Let’s dissect this pattern a bit.
TransactionRepository
and its public strategies kind the API. Because it takes no dependencies and carries no state inside its lifecycle, the strategies are on the singleton. These are the one methods to entry the information and the floor could be very restricted.TransactionRepository::File
is the ActiveRecord class. We have now to level to its database desk withself.table_name
, since its namespace is “unconventional” to the framework mechanics. We might useFile
inside the repository and to implement its performance. This fixed just isn’t accessible exterior the repository — encapsulation is fulfilled.- Return values of repository queries are immutable structs. They’re not
ActiveRecord::Relation
. They’re notActiveRecord::Base
cases both.
Does this strategy have drawbacks? It actually does. Like all the pieces else it’s an artwork of selection. We’re buying and selling comfort off in a single space for predictability and maintainability within the different. YMMV.
The place huge ActiveRecord floor shines probably the most is the view layer and the quite a few framework helpers constructed on prime of it. We don’t get that advantages with our structs. We would get again a few of them by together with ActiveModel::Naming
behaviours.
Does this strategy have any alternate options? The CQRS — a separation of write and skim fashions, the place there was beforehand one, may very well be a viable possibility for some. Provided that writes and reads are applied and optimised in a different way, the ActiveRecord matches the learn half completely. It’s my most popular car to implement Learn Mannequin on prime of denormalised SQL database tables in Rails.