In 2007, I gave a talk about using Presenters with Rails. Here I re-visit the topic drawing on experiences developing an application for the UK Government.
All THE Things 7 Patterns to Refactor Fat ActiveRecord Models http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ 1. Form Objects 2. Service Objects 3. View Models
class PersonPresenter def initialize(person) @person = person end def full_name "#{person.first_name} #{person.last_name}" end def date_of_birth person.date_of_birth.to_s(:full) end private attr_reader :person end
WHERE DID PRESENTERs COme From? Rails: Presenter Pattern http://blog.jayfields.com/2007/03/rails-presenter-pattern.html “Presenter was inspired by the various GUI patterns documented by Martin Fowler. The Presenter pattern addresses bloated controllers and views containing logic in concert by creating a class representation of the state of the view. An architecture that uses the Presenter pattern provides view specific data as attributes of an instance of the Presenter. The Presenter's state is an aggregation of model and user entered data.” – Jay Fields (2007)
Skinny Controller, Fat Model Skinny Controller, Fat Model http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model “Be aggressive! Try to keep your controller actions and views as slim as possible. A one-line action is a thing of wonder, as is a template that is mostly HTML. It is also much more maintainable than a view that is full of assignment statements and chained method calls.” – Jamis Buck (2007)
def lender_id lender && lender.id end def lender_id=(id) self.lender = Lender.find_by_id(id) end def received_on=(value) @received_on = QuickDateFormatter.parse(value) end def loans @loans ||= lender.loans.demanded.includes(:lending_limit).map {|loan| SettleLoan.new(loan) } end def loans_attributes=(values) values.each do |_, attributes| loan = loans_by_id[attributes['id'].to_i] loan.settled = (attributes['settled'] == '1') loan.settled_amount = attributes['settled_amount'] end end def attributes=(values) sanitize_for_mass_assignment(values).each do |attr, value| public_send("#{attr}=", value) end end def save # This is intentionally eager. We want to run all of the validations. return false if invalid?(:details) | invalid?(:save) | settled_loans.map(&:invalid?).any? ActiveRecord::Base.transaction do
def lender_id lender && lender.id end def lender_id=(id) self.lender = Lender.find_by_id(id) end def received_on=(value) @received_on = QuickDateFormatter.parse(value) end def loans @loans ||= lender.loans.demanded.includes(:lending_limit).map {|loan| SettleLoan.new(loan) } end def loans_attributes=(values) values.each do |_, attributes| loan = loans_by_id[attributes['id'].to_i] loan.settled = (attributes['settled'] == '1') loan.settled_amount = attributes['settled_amount'] end end def attributes=(values) sanitize_for_mass_assignment(values).each do |attr, value| public_send("#{attr}=", value) end end def save # This is intentionally eager. We want to run all of the validations. return false if invalid?(:details) | invalid?(:save) | settled_loans.map(&:invalid?).any? ActiveRecord::Base.transaction do
def lender_id lender && lender.id end def lender_id=(id) self.lender = Lender.find_by_id(id) end def received_on=(value) @received_on = QuickDateFormatter.parse(value) end def loans @loans ||= lender.loans.demanded.includes(:lending_limit).map {|loan| SettleLoan.new(loan) } end def loans_attributes=(values) values.each do |_, attributes| loan = loans_by_id[attributes['id'].to_i] loan.settled = (attributes['settled'] == '1') loan.settled_amount = attributes['settled_amount'] end end def attributes=(values) sanitize_for_mass_assignment(values).each do |attr, value| public_send("#{attr}=", value) end end def save # This is intentionally eager. We want to run all of the validations. return false if invalid?(:details) | invalid?(:save) | settled_loans.map(&:invalid?).any? ActiveRecord::Base.transaction do
def save # This is intentionally eager. We want to run all of the validations. return false if invalid?(:details) | invalid?(:save) | settled_loans.map(&:invalid?).any? ActiveRecord::Base.transaction do create_invoice! settle_loans! end true rescue ActiveRecord::RecordInvalid false end def grouped_loans @groups ||= LoanGroupSet.filter(loans) end private attr_writer :invoice def create_invoice! self.invoice = Invoice.create! do |invoice| invoice.lender = self.lender invoice.reference = self.reference invoice.period_covered_quarter = self.period_covered_quarter invoice.period_covered_year = self.period_covered_year invoice.received_on = self.received_on invoice.created_by = self.creator end end def settle_loans! settled_loans.each do |loan| loan.settle!(invoice, self.creator)