Blog post describing EDR: "Building Rich Domain Models in Rails. Separating Persistence" (though this post uses an older and more verbose version of the API).
Originally named for "Entity, Data, Repository", based somewhat on the Repository pattern. The edr gem separates lookup, persistence, and domain model responsibilities into distinct classes.
Domain Model classes are defined as "plain old Ruby objects" whose lifecycle is managed through a Repository class. The Repository creates Domain Model classes on DB reads and persisting them to the DB on saves through an ActiveRecord::Base. The Repository class can be viewed as a sort of simple Data Mapper, mapping a single database table onto a single Domain Model object. In this context, the ActiveRecord::Base subclass operates primarily as a Row Data Gateway.
Each Domain Model class has a single Repository that uses a single ActiveRecord::Base subclass.
ActiveRecord::Schema.define(:version => 1) do create_table "orders", :force => true do |t| t.decimal "amount" t.date "deliver_at" t.datetime "created_at", :null => false t.datetime "updated_at", :null => false end create_table "items", :force => true do |t| t.string "name" t.decimal "amount" t.integer "order_id" t.datetime "created_at", :null => false t.datetime "updated_at", :null => false end end
STEP1: Define data (Active Record) classes
class OrderData < ActiveRecord::Base self.table_name = "orders" attr_accessible :amount, :deliver_at validates :amount, numericality: true has_many :items, class_name: 'ItemData', foreign_key: 'order_id' end class ItemData < ActiveRecord::Base self.table_name = "items" attr_accessible :amount, :name validates :amount, numericality: true validates :name, presence: true end
STEP2: Define domain model classes
Fields and associations on the Domain Model are determined via ActiveRecord reflection. The Domain Model is coupled to its ActiveRecord class by naming convention.
Be aware that your Domain Model test/specs will need to stub/mock out dependencies upon their Repository and other Domain Model objects. That is, you Domain Model instances will lack fields or associations in their tests. This is because Domain Model objects are POROs until they are registered with edr at runtime. As you should not want to test the framework, this should facilitate testing your Domain Model and your persistence in isolation from one another and from the edr framework.
class Order def add_item attrs repository.create_item self, attrs end end class Item end
STEP3: map data objects to domain objects
Domain Model classes should share the same name as the AR classes except they should not end in "Data". So OrderData < ActiveRecord::Base maps to the Order domain model class.
You probably want the below in a
STEP4: Define repository classes
Your Repository class maps ActiveRecord CRUD results onto Domain Model instances.
module OrderRepository extend Edr::Repository set_model_class Order def self.find_by_amount amount where(amount: amount) end def self.find_by_id id where(id: id).first end def self.create_item order, attrs item = ItemRepository.create_item(order, attrs) data(order).reload return item end end module ItemRepository extend Edr::Repository set_model_class Item def self.create_item order, attrs item = Item.build(attrs) item.order_id = order.id persist item end end
In an ideal world, the Domain Model classes would remain blissfully unaware of their Repositories. However, associations between Domain Model objects necessitate either awareness of the associated Domain Model class' Repository or arbitration via a Unit of Work. While a UoW could be defined/used by edr, this likely will remain beyond the scope of this gem.
It is my hope that, in the near future, we will all instead switch over to using Datamapper 2 when it is ready. However, as of the time of the writing of this README, DM2 is still pre-alpha.
Add this line to your application's Gemfile:
And then execute:
Or install it yourself as:
$ gem install edr