SlideShare a Scribd company logo
Presenters!
 (On Rails)
   Mike Desjardins
    @mdesjardins
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.
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
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
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
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
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
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
Swell!


     MVC!   Note to any Microsoft
                 knuckleheads:
         This is not a presentation on
                     “MVP.”
Model View
  Controller
        Controllers




Model                 Views
GOsh, What’s Wrong With
        MVC?
    As your project gets more complex, the
Controllers and Views become “bloated” despite
                your best efforts.
GOsh, What’s Wrong With
        MVC?
    As your project gets more complex, the
Controllers and Views become “bloated” despite
                your best efforts.
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]
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
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
Gee-
Willikers!
It’s not just the controllers
  that get bloated, Views
   get messed up, too...
Thicker than a five dollar
                    malt
   = order_form.fields_for :reservation do |reservation_form|
    = render "orders/reservation_hidden_fields", :reservation_form => reservation_form

     - unless mobile_prefered?
      = render "orders/restaurant_offer_details", :reservation_form => reservation_form

     .psuedo-section
       - if @order.restaurant.custom_logo_url.present?
        %p.logo=image_tag(@order.restaurant.custom_logo_url)
       %section#reservation_show
        - if @ios_app
          = render "orders/reservation_details"
        = render "orders/reservation_datetime_form_new", :order_form => order_form, :reservation_form => reservation_form
       - if mobile_prefered?
        = render "orders/restaurant_offer_details", :reservation_form => reservation_form
       = render "orders/reservation_info_form_new", :reservation_form => reservation_form
       = hidden_field_tag :orderPage_receiptResponseURL, credit_user_account_orders_url
       = hidden_field_tag :orderPage_declineResponseURL, credit_user_account_orders_url

      - if @ios_app
       #payment-info
         - if @payment_required
           - no_show_fee_amount = @restaurant.no_show_fee(@order.reservation)
           - if no_show_fee_amount && no_show_fee_amount > 0.0
             = render :partial => "shared/payment_details", :locals => { :countdown_minutes => nil, :payment_type =>
'noshow', :no_show_fee_amount => no_show_fee_amount }
           - else
             = render :partial => "shared/payment_details", :locals => { :countdown_minutes => nil, :payment_type =>
'purchase', :no_show_fee_amount => 0.0 }

      = render "orders/reservation_submit", :reservation_form => reservation_form, :order_form => order_form

 - if @layout != 'nometro' && @restaurant.metro.published?
  .sidebar
    = render "orders/reservation_faq"
    = render :partial => "orders/loyalty_box", :locals => {:restaurant => @restaurant}
    - if @order.has_offer?
Thicker than a five dollar
                    malt
   = order_form.fields_for :reservation do |reservation_form|
    = render "orders/reservation_hidden_fields", :reservation_form => reservation_form

     - unless mobile_prefered?
      = render "orders/restaurant_offer_details", :reservation_form => reservation_form

     .psuedo-section
       - if @order.restaurant.custom_logo_url.present?
        %p.logo=image_tag(@order.restaurant.custom_logo_url)
       %section#reservation_show
        - if @ios_app
          = render "orders/reservation_details"
        = render "orders/reservation_datetime_form_new", :order_form => order_form, :reservation_form => reservation_form
       - if mobile_prefered?
        = render "orders/restaurant_offer_details", :reservation_form => reservation_form
       = render "orders/reservation_info_form_new", :reservation_form => reservation_form
       = hidden_field_tag :orderPage_receiptResponseURL, credit_user_account_orders_url
       = hidden_field_tag :orderPage_declineResponseURL, credit_user_account_orders_url

      - if @ios_app
       #payment-info
         - if @payment_required
           - no_show_fee_amount = @restaurant.no_show_fee(@order.reservation)
           - if no_show_fee_amount && no_show_fee_amount > 0.0
             = render :partial => "shared/payment_details", :locals => { :countdown_minutes => nil, :payment_type =>
'noshow', :no_show_fee_amount => no_show_fee_amount }
           - else
             = render :partial => "shared/payment_details", :locals => { :countdown_minutes => nil, :payment_type =>
'purchase', :no_show_fee_amount => 0.0 }

      = render "orders/reservation_submit", :reservation_form => reservation_form, :order_form => order_form

 - if @layout != 'nometro' && @restaurant.metro.published?
  .sidebar
    = render "orders/reservation_faq"
    = render :partial => "orders/loyalty_box", :locals => {:restaurant => @restaurant}
    - if @order.has_offer?
Who will
maintain
and test
 all this
logic?!?
Presente
rs to the
Rescue!
Let’s Review...
        Controllers




Model                 Views
GOLLY, that’s bad
     news!
         Controllers




 Model                 Views
Presenter
           s
           Presenter




Controller




 Model             View
Represent “Current State of the
            View”
               Presenter




  Controller




   Model                   View
Invoicing!
  Scripps needed a
   way to preview
Invoices that were to
      be sent to
 Restaurants, as well
   as view existing
       invoices
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
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
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
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"
...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
Made in the
  Shade
Whoop-de-
freakin-Do!
Have you ever written
 a good view test?
Have you ever written
 a good view test?
  No, seriously. Be
      Honest.
Live
Demo
Can’t I just do all this with
          Helpers?
Helpers don’t have
      State
Presenters in Rails
Not Jesus.
Avdi Grimm
http://www.objectsonrails.com
Exhibitor Pattern
 Uses “Decorator Pattern” to extend an
 existing model
 Implements Decorator Pattern using
 Ruby’s SimpleDelegator class
Decorator Pattern?
Decorator Pattern?
  Delegate                Decorator




 Hey look, it’s UML! I read
 about this in a Computer
 Science Archaeology Book
           once!
Decorator Pattern?
     Delegate           Decorator
+jumpFromSpaceBalloon
Decorator Pattern?
     Delegate               Decorator
+jumpFromSpaceBalloon   +jumpFromSpaceBalloon
                        +drinkRedBull
Decorator Pattern?
     Delegate           SimpleDelegator
+jumpFromSpaceBalloon   +initializer(thing: Delegate)
                        +drinkRedBull




 Gosh, Ruby
sure is spiffy!
Decorator Pattern?
          SimpleDelegator




  Model         Exhibitor
          +initializer(a_model: Model)
          +render_body(context:View)
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
some People in the rails
Community conflate these
two notions (exhibitor vs.
       presenter)




 But now you’re smarter
    than all of them!
FURTHER Reading
http://blog.jayfields.com/2007/03/rails-presenter-pattern.html




http://broadcastingadam.com/2011/06/present_yourself/




http://railsvideos.net/railsconf-2012-presenters-and-decorators-a-co
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
Quest ions?


    Retro Clip Art Provided By Tack-o-Rama
             http://tackorama.net

More Related Content

Similar to Presenters in Rails (20)

PDF
Rails antipattern-public
Chul Ju Hong
 
PDF
Rails antipatterns
Chul Ju Hong
 
PDF
Code refactor strategy part #1
Tracy LOISEL
 
PDF
How to disassemble one monster app into an ecosystem of 30
fiyuer
 
PDF
Rails MVC by Sergiy Koshovyi
Pivorak MeetUp
 
PDF
Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código
Guilherme
 
PDF
How to stop being Rails Developer
Ivan Nemytchenko
 
KEY
Working Effectively With Legacy Code
scidept
 
PDF
DDD-rail Your Monorail
Andrew Hao
 
PDF
RoR 101: Session 4
Rory Gianni
 
PDF
Intro to-rails-webperf
New Relic
 
PDF
Factory patterns
Kuyseng Chhoeun
 
PDF
Ruby on Rails - Introduction
Vagmi Mudumbai
 
PDF
Domain Driven Design and Hexagonal Architecture with Rails
Declan Whelan
 
PDF
Resource and view
Papp Laszlo
 
PDF
RubyOnRails-Cheatsheet-BlaineKendall
tutorialsruby
 
PDF
RubyOnRails-Cheatsheet-BlaineKendall
tutorialsruby
 
PDF
Heroku pop-behind-the-sense
Ben Lin
 
PDF
Introduction to Ruby on Rails
Agnieszka Figiel
 
PPTX
IEEE Toronto Centennial Workshop: Building An ASP.NET Core Application
Thiago do Nascimento Fontes
 
Rails antipattern-public
Chul Ju Hong
 
Rails antipatterns
Chul Ju Hong
 
Code refactor strategy part #1
Tracy LOISEL
 
How to disassemble one monster app into an ecosystem of 30
fiyuer
 
Rails MVC by Sergiy Koshovyi
Pivorak MeetUp
 
Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código
Guilherme
 
How to stop being Rails Developer
Ivan Nemytchenko
 
Working Effectively With Legacy Code
scidept
 
DDD-rail Your Monorail
Andrew Hao
 
RoR 101: Session 4
Rory Gianni
 
Intro to-rails-webperf
New Relic
 
Factory patterns
Kuyseng Chhoeun
 
Ruby on Rails - Introduction
Vagmi Mudumbai
 
Domain Driven Design and Hexagonal Architecture with Rails
Declan Whelan
 
Resource and view
Papp Laszlo
 
RubyOnRails-Cheatsheet-BlaineKendall
tutorialsruby
 
RubyOnRails-Cheatsheet-BlaineKendall
tutorialsruby
 
Heroku pop-behind-the-sense
Ben Lin
 
Introduction to Ruby on Rails
Agnieszka Figiel
 
IEEE Toronto Centennial Workshop: Building An ASP.NET Core Application
Thiago do Nascimento Fontes
 

Recently uploaded (20)

PDF
NewMind AI - Journal 100 Insights After The 100th Issue
NewMind AI
 
PDF
Building Real-Time Digital Twins with IBM Maximo & ArcGIS Indoors
Safe Software
 
PPTX
Q2 FY26 Tableau User Group Leader Quarterly Call
lward7
 
PDF
IoT-Powered Industrial Transformation – Smart Manufacturing to Connected Heal...
Rejig Digital
 
PPTX
Webinar: Introduction to LF Energy EVerest
DanBrown980551
 
PDF
Reverse Engineering of Security Products: Developing an Advanced Microsoft De...
nwbxhhcyjv
 
PDF
CIFDAQ Market Insights for July 7th 2025
CIFDAQ
 
PPTX
OpenID AuthZEN - Analyst Briefing July 2025
David Brossard
 
PDF
Biography of Daniel Podor.pdf
Daniel Podor
 
PPTX
AUTOMATION AND ROBOTICS IN PHARMA INDUSTRY.pptx
sameeraaabegumm
 
PDF
CIFDAQ Market Wrap for the week of 4th July 2025
CIFDAQ
 
PPTX
From Sci-Fi to Reality: Exploring AI Evolution
Svetlana Meissner
 
PDF
"AI Transformation: Directions and Challenges", Pavlo Shaternik
Fwdays
 
PDF
DevBcn - Building 10x Organizations Using Modern Productivity Metrics
Justin Reock
 
PPTX
"Autonomy of LLM Agents: Current State and Future Prospects", Oles` Petriv
Fwdays
 
PDF
HubSpot Main Hub: A Unified Growth Platform
Jaswinder Singh
 
PDF
LLMs.txt: Easily Control How AI Crawls Your Site
Keploy
 
PDF
Newgen 2022-Forrester Newgen TEI_13 05 2022-The-Total-Economic-Impact-Newgen-...
darshakparmar
 
PDF
POV_ Why Enterprises Need to Find Value in ZERO.pdf
darshakparmar
 
PDF
Empower Inclusion Through Accessible Java Applications
Ana-Maria Mihalceanu
 
NewMind AI - Journal 100 Insights After The 100th Issue
NewMind AI
 
Building Real-Time Digital Twins with IBM Maximo & ArcGIS Indoors
Safe Software
 
Q2 FY26 Tableau User Group Leader Quarterly Call
lward7
 
IoT-Powered Industrial Transformation – Smart Manufacturing to Connected Heal...
Rejig Digital
 
Webinar: Introduction to LF Energy EVerest
DanBrown980551
 
Reverse Engineering of Security Products: Developing an Advanced Microsoft De...
nwbxhhcyjv
 
CIFDAQ Market Insights for July 7th 2025
CIFDAQ
 
OpenID AuthZEN - Analyst Briefing July 2025
David Brossard
 
Biography of Daniel Podor.pdf
Daniel Podor
 
AUTOMATION AND ROBOTICS IN PHARMA INDUSTRY.pptx
sameeraaabegumm
 
CIFDAQ Market Wrap for the week of 4th July 2025
CIFDAQ
 
From Sci-Fi to Reality: Exploring AI Evolution
Svetlana Meissner
 
"AI Transformation: Directions and Challenges", Pavlo Shaternik
Fwdays
 
DevBcn - Building 10x Organizations Using Modern Productivity Metrics
Justin Reock
 
"Autonomy of LLM Agents: Current State and Future Prospects", Oles` Petriv
Fwdays
 
HubSpot Main Hub: A Unified Growth Platform
Jaswinder Singh
 
LLMs.txt: Easily Control How AI Crawls Your Site
Keploy
 
Newgen 2022-Forrester Newgen TEI_13 05 2022-The-Total-Economic-Impact-Newgen-...
darshakparmar
 
POV_ Why Enterprises Need to Find Value in ZERO.pdf
darshakparmar
 
Empower Inclusion Through Accessible Java Applications
Ana-Maria Mihalceanu
 
Ad

Presenters in Rails

  • 1. Presenters! (On Rails) Mike Desjardins @mdesjardins
  • 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.”
  • 10. Model View Controller Controllers Model Views
  • 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
  • 17. It’s not just the controllers that get bloated, Views get messed up, too...
  • 18. Thicker than a five dollar malt = order_form.fields_for :reservation do |reservation_form| = render "orders/reservation_hidden_fields", :reservation_form => reservation_form - unless mobile_prefered? = render "orders/restaurant_offer_details", :reservation_form => reservation_form .psuedo-section - if @order.restaurant.custom_logo_url.present? %p.logo=image_tag(@order.restaurant.custom_logo_url) %section#reservation_show - if @ios_app = render "orders/reservation_details" = render "orders/reservation_datetime_form_new", :order_form => order_form, :reservation_form => reservation_form - if mobile_prefered? = render "orders/restaurant_offer_details", :reservation_form => reservation_form = render "orders/reservation_info_form_new", :reservation_form => reservation_form = hidden_field_tag :orderPage_receiptResponseURL, credit_user_account_orders_url = hidden_field_tag :orderPage_declineResponseURL, credit_user_account_orders_url - if @ios_app #payment-info - if @payment_required - no_show_fee_amount = @restaurant.no_show_fee(@order.reservation) - if no_show_fee_amount && no_show_fee_amount > 0.0 = render :partial => "shared/payment_details", :locals => { :countdown_minutes => nil, :payment_type => 'noshow', :no_show_fee_amount => no_show_fee_amount } - else = render :partial => "shared/payment_details", :locals => { :countdown_minutes => nil, :payment_type => 'purchase', :no_show_fee_amount => 0.0 } = render "orders/reservation_submit", :reservation_form => reservation_form, :order_form => order_form - if @layout != 'nometro' && @restaurant.metro.published? .sidebar = render "orders/reservation_faq" = render :partial => "orders/loyalty_box", :locals => {:restaurant => @restaurant} - if @order.has_offer?
  • 19. Thicker than a five dollar malt = order_form.fields_for :reservation do |reservation_form| = render "orders/reservation_hidden_fields", :reservation_form => reservation_form - unless mobile_prefered? = render "orders/restaurant_offer_details", :reservation_form => reservation_form .psuedo-section - if @order.restaurant.custom_logo_url.present? %p.logo=image_tag(@order.restaurant.custom_logo_url) %section#reservation_show - if @ios_app = render "orders/reservation_details" = render "orders/reservation_datetime_form_new", :order_form => order_form, :reservation_form => reservation_form - if mobile_prefered? = render "orders/restaurant_offer_details", :reservation_form => reservation_form = render "orders/reservation_info_form_new", :reservation_form => reservation_form = hidden_field_tag :orderPage_receiptResponseURL, credit_user_account_orders_url = hidden_field_tag :orderPage_declineResponseURL, credit_user_account_orders_url - if @ios_app #payment-info - if @payment_required - no_show_fee_amount = @restaurant.no_show_fee(@order.reservation) - if no_show_fee_amount && no_show_fee_amount > 0.0 = render :partial => "shared/payment_details", :locals => { :countdown_minutes => nil, :payment_type => 'noshow', :no_show_fee_amount => no_show_fee_amount } - else = render :partial => "shared/payment_details", :locals => { :countdown_minutes => nil, :payment_type => 'purchase', :no_show_fee_amount => 0.0 } = render "orders/reservation_submit", :reservation_form => reservation_form, :order_form => order_form - if @layout != 'nometro' && @restaurant.metro.published? .sidebar = render "orders/reservation_faq" = render :partial => "orders/loyalty_box", :locals => {:restaurant => @restaurant} - if @order.has_offer?
  • 20. Who will maintain and test all this logic?!?
  • 22. Let’s Review... Controllers Model Views
  • 23. GOLLY, that’s bad news! Controllers Model Views
  • 24. Presenter s Presenter Controller Model View
  • 25. Represent “Current State of the View” Presenter Controller Model View
  • 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
  • 32. Made in the Shade
  • 34. Have you ever written a good view test?
  • 35. Have you ever written a good view test? No, seriously. Be Honest.
  • 37. Can’t I just do all this with Helpers?
  • 43. Exhibitor Pattern Uses “Decorator Pattern” to extend an existing model Implements Decorator Pattern using Ruby’s SimpleDelegator class
  • 45. Decorator Pattern? Delegate Decorator Hey look, it’s UML! I read about this in a Computer Science Archaeology Book once!
  • 46. Decorator Pattern? Delegate Decorator +jumpFromSpaceBalloon
  • 47. Decorator Pattern? Delegate Decorator +jumpFromSpaceBalloon +jumpFromSpaceBalloon +drinkRedBull
  • 48. Decorator Pattern? Delegate SimpleDelegator +jumpFromSpaceBalloon +initializer(thing: Delegate) +drinkRedBull Gosh, Ruby sure is spiffy!
  • 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

Editor's Notes