2. Design Pattern?
Per our good friends at Wikipedia:
In software engineering, a design pattern is a general
reusable solution to a commonly occurring problem
within a given context in software design.
3. Design Pattern?
Per our good friends at Wikipedia:
In software engineering, a design pattern is a general
reusable solution to a commonly occurring problem
within a given context in software design.
Observer
4. Design Pattern?
Per our good friends at Wikipedia:
In software engineering, a design pattern is a general
reusable solution to a commonly occurring problem
within a given context in software design.
Observer
Factory
5. Design Pattern?
Per our good friends at Wikipedia:
In software engineering, a design pattern is a general
reusable solution to a commonly occurring problem
within a given context in software design.
Observer
Factory Bridge
6. Design Pattern?
Per our good friends at Wikipedia:
In software engineering, a design pattern is a general
reusable solution to a commonly occurring problem
within a given context in software design.
Observer
Factory Bridge
Singleton
7. Design Pattern?
Per our good friends at Wikipedia:
In software engineering, a design pattern is a general
reusable solution to a commonly occurring problem
within a given context in software design.
Observer
Factory Bridge
Singleton
Decorator
8. Design Pattern?
Per our good friends at Wikipedia:
In software engineering, a design pattern is a general
reusable solution to a commonly occurring problem
within a given context in software design.
Observer
Factory Bridge
Singleton
Visitor Decorator
9. Swell!
MVC! Note to any Microsoft
knuckleheads:
This is not a presentation on
“MVP.”
11. GOsh, What’s Wrong With
MVC?
As your project gets more complex, the
Controllers and Views become “bloated” despite
your best efforts.
12. GOsh, What’s Wrong With
MVC?
As your project gets more complex, the
Controllers and Views become “bloated” despite
your best efforts.
13. These are just the filters in CityEats’ Orders
Controller!
skip_before_filter :protect_private_environments, except: [:new]
before_filter :set_user,
only: [:new, :credit_user_account, :create, :iframe, :payment_form, :offer_details]
# Are all three of these filters necessary? It doesn't seem so at a glance. -Tim
before_filter :load_restaurant,
only: [:new, :create, :iframe, :credit_user_account, :payment_form, :offer_details],
:if => lambda { |c| params[:restaurant_id].present? }
before_filter :load_restaurant_and_authenticate,
only: [:new],
:if => lambda { |c| params[:restaurant_id].present? || params[:restaurant_offer_id].present? }
before_filter :load_offer_and_set_restaurant,
only: [:new, :credit_user_account, :create, :payment_form, :offer_details],
:if => lambda { |c| params[:restaurant_offer_id].present? }
before_filter :require_restaurant,
only: [:new]
before_filter :merge_request_ip_address,
only: [:create]
around_filter :load_restaurant_time_zone,
only: [:new, :show, :create, :destroy, :iframe, :payment_form, :credit_user_account]
before_filter :load_watched_video,
only: [:create]
before_filter :init_reservation,
only: [:new, :iframe, :payment_form, :credit_user_account, :create, :offer_details]
before_filter :init_order,
only: [:new, :iframe, :payment_form, :credit_user_account, :create, :offer_details]
before_filter :set_price,
only: [:new, :create, :credit_user_account, :payment_form]
before_filter :init_gateway_request_filter,
only: [:new, :credit_user_account, :payment_form]
14. AW SHUCKS, Actions
too! create action:
Here’s the OrdersController’s
def create
if params[:iframe]
@styling = @restaurant.try(:restaurant_widget_customization) ||
RestaurantWidgetCustomization.new(:restaurant_id => @restaurant.try(:id))
@styling.merge(params["restaurant_widget_customization"]) if params["restaurant_widget_customization"].present?
end
@order.group_emailable = params[:group_emailable]
@reservation.landing_tag = cookies['landing_tag'] if cookies['landing_tag'].present?
if @order.save
UserMailer.confirm_order(@order).deliver
@order.user.accept_current_terms_of_service!(request.remote_ip)
flash["ignore_order_is_conversion"] = true #this is used to render the conversion tracking pixel - naudo feb.6.2012
flash_message(:notice, 'Your order was successfully created.')
if @order.invite_facebook_friends_to_reservation? && current_user.present? && current_user.is_a_facebook_user?
render(:invite_on_facebook) and return
end
render(:iframe_confirm, layout: 'minimal') and return if params[:iframe]
cookies['landing_tag'] = nil
redirect_to @order
else
# Reverse any preauth and/or subscription
@gateway_transaction.reverse_authorization_and_or_subscription if @gateway_transaction.present?
render(:iframe, layout: 'minimal') and return if params[:iframe]
render :new, layout: choose_layout
end
end
15. AW SHUCKS, Actions
too! create action:
Here’s the OrdersController’s
def create
if params[:iframe]
@styling = @restaurant.try(:restaurant_widget_customization) ||
RestaurantWidgetCustomization.new(:restaurant_id => @restaurant.try(:id))
@styling.merge(params["restaurant_widget_customization"]) if params["restaurant_widget_customization"].present?
end
@order.group_emailable = params[:group_emailable]
@reservation.landing_tag = cookies['landing_tag'] if cookies['landing_tag'].present?
if @order.save
UserMailer.confirm_order(@order).deliver
@order.user.accept_current_terms_of_service!(request.remote_ip)
flash["ignore_order_is_conversion"] = true #this is used to render the conversion tracking pixel - naudo feb.6.2012
flash_message(:notice, 'Your order was successfully created.')
if @order.invite_facebook_friends_to_reservation? && current_user.present? && current_user.is_a_facebook_user?
render(:invite_on_facebook) and return
end
render(:iframe_confirm, layout: 'minimal') and return if params[:iframe]
cookies['landing_tag'] = nil
redirect_to @order
else
# Reverse any preauth and/or subscription
@gateway_transaction.reverse_authorization_and_or_subscription if @gateway_transaction.present?
render(:iframe, layout: 'minimal') and return if params[:iframe]
render :new, layout: choose_layout
end
end
26. Invoicing!
Scripps needed a
way to preview
Invoices that were to
be sent to
Restaurants, as well
as view existing
invoices
27. Invoice
class InvoicePresenter Presenter
attr_accessor :reservation_transactions, :non_reservation_transactions, :transactions, :id, :date, :due_date, :account,
:reservation_transactions_total, :restaurant
def initialize(thing)
self.restaurant = thing.account.accountable
self.transactions = thing.transactions
self.date = thing.date
self.account = thing.account
self.reservation_transactions = thing.reservation_transactions
self.non_reservation_transactions = thing.non_reservation_transactions
if thing.is_a? Invoice
init_from_invoice(thing)
elsif thing.is_a? InvoicePreview
init_from_invoice_preview(thing)
else
raise ArgumentError.new("I don't know what to do with this thing.")
end
end
.
.
.
private
def init_from_invoice(invoice)
self.id = invoice.id
self.due_date = invoice.due_date || self.date.end_of_month + Invoice::INVOICE_DAYS_AFTER
end
def init_from_invoice_preview(preview)
self.id = "PREVIEW"
invoiced_on_date = self.date < Date.today ? Date.today : self.date
due_date = invoiced_on_date + Invoice::INVOICE_DAYS_AFTER
self.due_date = due_date
end
end
28. Invoice
Presenter
def render_performance_summary(context)
by_source = {}
total = 0
reservation_transactions.each do |txn|
unless txn.source.nil? # how does this happen?
by_source[txn.source.reservation_source.name] =
by_source.fetch(txn.source.reservation_source.name,0) + 1
total = total + 1
end
end
context.render partial: 'invoice_performance_summary',
locals: {total: total, by_source: by_source}
end
29. Invoice
def render_line_items(context)
Presenter
Yeah, it can still be kinda gross...
output = []
# First, the one time fees.
one_time_fee_total = 0.0
one_time_fees.each do |otf|
amount = otf[:unit_price] * otf[:quantity]
item = {unit_price: otf[:unit_price], quantity: otf[:quantity], description: otf[:description], discount: 0.00, amount: amount}
one_time_fee_total = one_time_fee_total + amount
output << context.render(partial: 'invoice_item', locals: {item: item})
end
unless one_time_fees.empty?
output << context.render(partial: 'invoice_total', locals: {total: one_time_fee_total, description: 'One Time Fees Subtotal', cssclass: 'sub-total'})
end
# Next, the reservation transactions
reservation_fee_total = 0.0
grouped_reservation_transactions.each do |txn|
amount = txn[:unit_price] * txn[:quantity]
item = {unit_price: txn[:unit_price], quantity: txn[:quantity], description: txn[:description], discount: 0.00, amount: amount}
reservation_fee_total = reservation_fee_total + amount
output << context.render(partial: 'invoice_item', locals: {item: item})
end
unless grouped_reservation_transactions.empty?
output << context.render(partial: 'invoice_total', locals: {total: reservation_fee_total, description: 'Reservation Fees Fees Subtotal', cssclass: 'sub-total'})
end
unless monthly_fee_cap_amount.blank? || monthly_fee_cap_amount.zero?
output << context.render(partial: 'invoice_total', locals: {total: "After Monthly Fee Cap - #{number_to_currency monthly_fee_cap_amount}", description: 'Balance at the
end of last period', cssclass: 'monthly-cap'})
end
# Other Totals
output << context.render(partial: 'invoice_total', locals: {total: balance_at_end_of_last_period, description: 'Balance at the end of last period', cssclass: 'sub-total'})
output << context.render(partial: 'invoice_total', locals: {total: last_payment_received_amount, description: "Payment Received - #{last_payment_received_on} - Thank
You", cssclass: 'sub-total'})
output << context.render(partial: 'invoice_total', locals: {total: sales_tax, description: 'Tax', cssclass: 'tax'})
output << context.render(partial: 'invoice_total', locals: {total: reservation_fee_total + one_time_fee_total + sales_tax, description: 'Total', cssclass: 'total'})
output.join
end
30. Invoice
%section
%h4#invoice-header
Invoice
%h4#ce-logo
Presenter
%img{:alt => 'CityEats', :src => '/assets/logo-cityeats-black.png'}
#invoice-summary
%h4 Invoice Summary:
%table
%tr
But the view is
%th
Invoice Id:
%td= @presenter.id
outta site!
%tr#invoice-date
%th Invoice Date:
%td= @presenter.date
%tr#due-date
%th Due Date:
%td= @presenter.due_date
%tr#amount-due
%th
Amount Due:
%td= number_to_currency(@presenter.amount_due)
#bill-to
%h4 Bill To:
= render partial: 'invoice_bill_to_address', locals: {name: @presenter.restaurant.name, address: @presenter.restaurant.address}
%h4 Remittance
%p The amount owing will automatically be charged to your credit card or debited from your bank account, according to the terms of your contract. <br /><em>If paying by check, please
include a copy of this statement.</em>
%h4 Fee Summary
%table#fee-summary
%tr
Gosh, no references to
%th Description
%th.quantity Quantity
%th.unit-price Unit Price
%th.discount Discount
models anywhere!
%th.amount Amount
= raw @presenter.render_line_items(self)
%h4 Performance Summary
= raw @presenter.render_performance_summary(self)
= javascript_include_tag "invoicing"
31. ...and the controller is tiny, too!
def show
invoice = Invoice.find(params[:id])
@presenter = InvoicePresenter.new(invoice)
end
def new
account = @restaurant.account
invoice_date = @restaurant.next_invoice_date
@presenter = InvoicePresenter.new(InvoicePreview.new(account, invoice_date))
end
49. Decorator Pattern?
SimpleDelegator
Model Exhibitor
+initializer(a_model: Model)
+render_body(context:View)
50. Exhibitor Pattern
Uses “Decorator Pattern” to extend an
existing model
Implements Decorator Pattern using
Ruby’s SimpleDelegator class
# exhibits/text_post_exhibit.rb
require 'delegate'
class TextPostExhibit < SimpleDelegator
def initialize(model, context)
@context = context
super(model)
end
def render_body
@context.render(partial: "/posts/text_body", locals: {post: self})
end
end
51. some People in the rails
Community conflate these
two notions (exhibitor vs.
presenter)
But now you’re smarter
than all of them!
53. FURTHER Reading
http://blog.jayfields.com/2007/03/rails-presenter-pattern.html
Simple one, does some similar stuff w/ delegation like Avdi without using
SimpleDelagator
http://broadcastingadam.com/2011/06/present_yourself/
Does some neat stuff with memoization
http://railsvideos.net/railsconf-2012-presenters-and-decorators-a-co
Very Good RailsConf 2012 Presentation by Mike Moore. Uses
ActiveDecorator to implement a form of Exhibitor
54. Quest ions?
Retro Clip Art Provided By Tack-o-Rama
http://tackorama.net