6. class LoanSignupController < ApplicationController
def create
loan = Loan.create(params[:loan])
if loan.persisted?
redirect_to loan_path(loan.id)
else
render :new
end
end
end
class Loan < ActiveRecord::Base
validate :amount, :title, persistence: true
end
15. def show
loan = LoanPresenter.new(Loan.find(params[:id]))
render loan.to_json
end
class LoanPresenter
def initialize(loan)
@loan = loan
end
def to_json
{
id: @loan.id,
amount: @loan.amount.to_money,
title: @loan.title
}
end
end
26. “An object that wraps a row in database table
or view, encapsulates database access, and
adds domain logic on that data.”
Martin Fowler
Patterns of Enterprise Application Architecture (2003)
27. An object that wraps a row in database table
or view, encapsulates database access, and
adds domain logic on that data.
Martin Fowler
Patterns of Enterprise Application Architecture (2003)
51. class LoanRepository
#... skipped code
private
def map_to_entity(record)
Entities::Loan.new.tap do |loan|
loan.id = record.id
loan.title = record.title
loan.amount_cents = (record.amount * 100).to_i
end
end
end
52. class LoanRepository
#... skipped code
private
def map_to_entity(record)
mapper.map(record)
end
def mapper
@mapper ||= Loan::Mapper.new
end
end
53. class LoanEntity
attribute :id
attribute :status
attribute :owner_id
attribute :loan_parts
def principal_remaining
if status != "paid"
loan_parts.inject(0){|sum, loan_part|
sum + loan_part.principal_remaining
}
else
0
end
end
end
54. class LoanValidator < ActiveModel::Validator
MINIMUM_LOAN_AMOUNT = 5_000
def validate(loan_entity)
unless loan.amount >= MINIMUM_LOAN_AMOUNT
loan.errors.add(
:amount,
"must be greater or equal to #{MINIMUM_LOAN_AMOUNT}"
)
end
end
end
61. class LoanRepository
def all_for_investor(investor_id)
loans = LoanService.fetch_for_investor(investor_id)
.and_then do |records|
borrower_ids = records.map{|record| record[:borrower_id] }
borrowers = Borrower.find(borrower_ids)
records.map do |record|
map_loan(record, borrowers)
end
end
end
#...skipped code
end
62. class LoanRepository
#...skipped code
private
def map_loan(loan, borrower)
Entities::Loan.new.tap do |loan|
loan.id = record[:id]
loan.title = record[:title]
loan.amount_cents = record[:amount_cents]
loan.borrower = map_loan_borrower(loan, borrowers)
end
end
def map_loan_borrower(loan, borrowers)
borrower = borrowers.detect{|borrower| loan.borrower_id = borrower.id }
Entities::Borrower.new.tap do |borrower|
borrower.id = record.borrower.id
borrower.name = record.borrower.full_name
end
end
end
66. class SellLenderLoanPart
def initialize(lender_source = nil, loan_part_source = nil)
@lender_source = lender_source || LenderRepository.new
@loan_part_source = loan_part_source || LoanPartRepository.new
end
def call(buyer_id, loan_part_id)
buyer = @lender_source.find(buyer_id)
loan_part = @loan_part_source.find(loan_part_id)
seller_loan_part = loan_part.snapshot
@loan_part_source.create(seller_loan_part.sell)
loan_part.change_ownership_to(buyer)
@loan_part_source.update(loan_part)
end
end
67. Problems and challenges
● more application layers
● a little less flexibility in how you access data
● mind-shift to work with in-memory objects
● responsibilities of entities
● validations
69. Repository mediates between the domain and
data mapping layers using a collection-like
interface for accessing domain objects
Martin Fowler
Patterns of Enterprise Application Architecture (2003)
70. Summary
● Now we are able to move in small steps
● Abstract away from data sources
● Process driven applications
but…
● I would never start with repositories