SlideShare a Scribd company logo
1 of 54
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
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

5 Reasons To Love CodeIgniter
5 Reasons To Love CodeIgniter5 Reasons To Love CodeIgniter
5 Reasons To Love CodeIgniter
nicdev
 
Zend - Installation And Sample Project Creation
Zend - Installation And Sample Project Creation Zend - Installation And Sample Project Creation
Zend - Installation And Sample Project Creation
Compare Infobase Limited
 
WebcampZG - Rails 4
WebcampZG - Rails 4WebcampZG - Rails 4
WebcampZG - Rails 4
shnikola
 

Similar to Presenters in Rails (20)

Rails MVC by Sergiy Koshovyi
Rails MVC by Sergiy KoshovyiRails MVC by Sergiy Koshovyi
Rails MVC by Sergiy Koshovyi
 
Rails antipattern-public
Rails antipattern-publicRails antipattern-public
Rails antipattern-public
 
Rails antipatterns
Rails antipatternsRails antipatterns
Rails antipatterns
 
Going web native
Going web nativeGoing web native
Going web native
 
Angular.js Fundamentals
Angular.js FundamentalsAngular.js Fundamentals
Angular.js Fundamentals
 
Introduction to Magento 2 module development - PHP Antwerp Meetup 2017
Introduction to Magento 2 module development - PHP Antwerp Meetup 2017Introduction to Magento 2 module development - PHP Antwerp Meetup 2017
Introduction to Magento 2 module development - PHP Antwerp Meetup 2017
 
Fragments: Why, How, What For?
Fragments: Why, How, What For?Fragments: Why, How, What For?
Fragments: Why, How, What For?
 
From Backbone to Ember and Back(bone) Again
From Backbone to Ember and Back(bone) AgainFrom Backbone to Ember and Back(bone) Again
From Backbone to Ember and Back(bone) Again
 
Building Mobile Friendly APIs in Rails
Building Mobile Friendly APIs in RailsBuilding Mobile Friendly APIs in Rails
Building Mobile Friendly APIs in Rails
 
5 Reasons To Love CodeIgniter
5 Reasons To Love CodeIgniter5 Reasons To Love CodeIgniter
5 Reasons To Love CodeIgniter
 
Big Data Seminar: Analytics, Hadoop, Map Reduce, Mongo and other great stuff
Big Data Seminar: Analytics, Hadoop, Map Reduce, Mongo and other great stuffBig Data Seminar: Analytics, Hadoop, Map Reduce, Mongo and other great stuff
Big Data Seminar: Analytics, Hadoop, Map Reduce, Mongo and other great stuff
 
Manipulating Magento - Mage Titans Italy 2018
Manipulating Magento - Mage Titans Italy 2018Manipulating Magento - Mage Titans Italy 2018
Manipulating Magento - Mage Titans Italy 2018
 
Zend - Installation And Sample Project Creation
Zend - Installation And Sample Project Creation Zend - Installation And Sample Project Creation
Zend - Installation And Sample Project Creation
 
Building Large Web Applications That Are Easy to Maintain
Building Large Web Applications That Are Easy to MaintainBuilding Large Web Applications That Are Easy to Maintain
Building Large Web Applications That Are Easy to Maintain
 
Angular server side rendering - Strategies & Technics
Angular server side rendering - Strategies & Technics Angular server side rendering - Strategies & Technics
Angular server side rendering - Strategies & Technics
 
Continuous Quality
Continuous QualityContinuous Quality
Continuous Quality
 
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
 
WebcampZG - Rails 4
WebcampZG - Rails 4WebcampZG - Rails 4
WebcampZG - Rails 4
 
A Brief Introduction to JQuery Mobile
A Brief Introduction to JQuery MobileA Brief Introduction to JQuery Mobile
A Brief Introduction to JQuery Mobile
 
Introduction to Ruby on Rails
Introduction to Ruby on RailsIntroduction to Ruby on Rails
Introduction to Ruby on Rails
 

Recently uploaded

Recently uploaded (20)

ASRock Industrial FDO Solutions in Action for Industrial Edge AI _ Kenny at A...
ASRock Industrial FDO Solutions in Action for Industrial Edge AI _ Kenny at A...ASRock Industrial FDO Solutions in Action for Industrial Edge AI _ Kenny at A...
ASRock Industrial FDO Solutions in Action for Industrial Edge AI _ Kenny at A...
 
Extensible Python: Robustness through Addition - PyCon 2024
Extensible Python: Robustness through Addition - PyCon 2024Extensible Python: Robustness through Addition - PyCon 2024
Extensible Python: Robustness through Addition - PyCon 2024
 
IESVE for Early Stage Design and Planning
IESVE for Early Stage Design and PlanningIESVE for Early Stage Design and Planning
IESVE for Early Stage Design and Planning
 
Demystifying gRPC in .Net by John Staveley
Demystifying gRPC in .Net by John StaveleyDemystifying gRPC in .Net by John Staveley
Demystifying gRPC in .Net by John Staveley
 
What's New in Teams Calling, Meetings and Devices April 2024
What's New in Teams Calling, Meetings and Devices April 2024What's New in Teams Calling, Meetings and Devices April 2024
What's New in Teams Calling, Meetings and Devices April 2024
 
Designing for Hardware Accessibility at Comcast
Designing for Hardware Accessibility at ComcastDesigning for Hardware Accessibility at Comcast
Designing for Hardware Accessibility at Comcast
 
Top 10 Symfony Development Companies 2024
Top 10 Symfony Development Companies 2024Top 10 Symfony Development Companies 2024
Top 10 Symfony Development Companies 2024
 
Powerful Start- the Key to Project Success, Barbara Laskowska
Powerful Start- the Key to Project Success, Barbara LaskowskaPowerful Start- the Key to Project Success, Barbara Laskowska
Powerful Start- the Key to Project Success, Barbara Laskowska
 
Syngulon - Selection technology May 2024.pdf
Syngulon - Selection technology May 2024.pdfSyngulon - Selection technology May 2024.pdf
Syngulon - Selection technology May 2024.pdf
 
Custom Approval Process: A New Perspective, Pavel Hrbacek & Anindya Halder
Custom Approval Process: A New Perspective, Pavel Hrbacek & Anindya HalderCustom Approval Process: A New Perspective, Pavel Hrbacek & Anindya Halder
Custom Approval Process: A New Perspective, Pavel Hrbacek & Anindya Halder
 
WSO2CONMay2024OpenSourceConferenceDebrief.pptx
WSO2CONMay2024OpenSourceConferenceDebrief.pptxWSO2CONMay2024OpenSourceConferenceDebrief.pptx
WSO2CONMay2024OpenSourceConferenceDebrief.pptx
 
Free and Effective: Making Flows Publicly Accessible, Yumi Ibrahimzade
Free and Effective: Making Flows Publicly Accessible, Yumi IbrahimzadeFree and Effective: Making Flows Publicly Accessible, Yumi Ibrahimzade
Free and Effective: Making Flows Publicly Accessible, Yumi Ibrahimzade
 
Unpacking Value Delivery - Agile Oxford Meetup - May 2024.pptx
Unpacking Value Delivery - Agile Oxford Meetup - May 2024.pptxUnpacking Value Delivery - Agile Oxford Meetup - May 2024.pptx
Unpacking Value Delivery - Agile Oxford Meetup - May 2024.pptx
 
The UX of Automation by AJ King, Senior UX Researcher, Ocado
The UX of Automation by AJ King, Senior UX Researcher, OcadoThe UX of Automation by AJ King, Senior UX Researcher, Ocado
The UX of Automation by AJ King, Senior UX Researcher, Ocado
 
The Value of Certifying Products for FDO _ Paul at FIDO Alliance.pdf
The Value of Certifying Products for FDO _ Paul at FIDO Alliance.pdfThe Value of Certifying Products for FDO _ Paul at FIDO Alliance.pdf
The Value of Certifying Products for FDO _ Paul at FIDO Alliance.pdf
 
Where to Learn More About FDO _ Richard at FIDO Alliance.pdf
Where to Learn More About FDO _ Richard at FIDO Alliance.pdfWhere to Learn More About FDO _ Richard at FIDO Alliance.pdf
Where to Learn More About FDO _ Richard at FIDO Alliance.pdf
 
Enterprise Knowledge Graphs - Data Summit 2024
Enterprise Knowledge Graphs - Data Summit 2024Enterprise Knowledge Graphs - Data Summit 2024
Enterprise Knowledge Graphs - Data Summit 2024
 
FDO for Camera, Sensor and Networking Device – Commercial Solutions from VinC...
FDO for Camera, Sensor and Networking Device – Commercial Solutions from VinC...FDO for Camera, Sensor and Networking Device – Commercial Solutions from VinC...
FDO for Camera, Sensor and Networking Device – Commercial Solutions from VinC...
 
Simplified FDO Manufacturing Flow with TPMs _ Liam at Infineon.pdf
Simplified FDO Manufacturing Flow with TPMs _ Liam at Infineon.pdfSimplified FDO Manufacturing Flow with TPMs _ Liam at Infineon.pdf
Simplified FDO Manufacturing Flow with TPMs _ Liam at Infineon.pdf
 
Integrating Telephony Systems with Salesforce: Insights and Considerations, B...
Integrating Telephony Systems with Salesforce: Insights and Considerations, B...Integrating Telephony Systems with Salesforce: Insights and Considerations, B...
Integrating Telephony Systems with Salesforce: Insights and Considerations, B...
 

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?
  • 39.
  • 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

  1. \n
  2. \n
  3. \n
  4. \n
  5. \n
  6. \n
  7. \n
  8. \n
  9. \n
  10. \n
  11. \n
  12. \n
  13. \n
  14. \n
  15. \n
  16. \n
  17. \n
  18. \n
  19. \n
  20. \n
  21. \n
  22. \n
  23. \n
  24. \n
  25. \n
  26. \n
  27. \n
  28. \n
  29. \n
  30. \n
  31. \n
  32. \n
  33. \n
  34. \n
  35. \n
  36. \n
  37. \n
  38. \n
  39. \n
  40. \n
  41. \n
  42. \n
  43. \n
  44. \n
  45. \n
  46. \n
  47. \n
  48. \n
  49. \n
  50. \n
  51. \n
  52. \n
  53. \n