SlideShare una empresa de Scribd logo
1 de 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

Más contenido relacionado

Similar a Presenters in Rails

Rails MVC by Sergiy Koshovyi
Rails MVC by Sergiy KoshovyiRails MVC by Sergiy Koshovyi
Rails MVC by Sergiy KoshovyiPivorak MeetUp
 
Rails antipattern-public
Rails antipattern-publicRails antipattern-public
Rails antipattern-publicChul Ju Hong
 
Rails antipatterns
Rails antipatternsRails antipatterns
Rails antipatternsChul Ju Hong
 
Angular.js Fundamentals
Angular.js FundamentalsAngular.js Fundamentals
Angular.js FundamentalsMark
 
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 2017Joke Puts
 
Fragments: Why, How, What For?
Fragments: Why, How, What For?Fragments: Why, How, What For?
Fragments: Why, How, What For?Brenda Cook
 
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) Againjonknapp
 
Building Mobile Friendly APIs in Rails
Building Mobile Friendly APIs in RailsBuilding Mobile Friendly APIs in Rails
Building Mobile Friendly APIs in RailsJim Jeffers
 
5 Reasons To Love CodeIgniter
5 Reasons To Love CodeIgniter5 Reasons To Love CodeIgniter
5 Reasons To Love CodeIgniternicdev
 
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 stuffMoshe Kaplan
 
Manipulating Magento - Mage Titans Italy 2018
Manipulating Magento - Mage Titans Italy 2018Manipulating Magento - Mage Titans Italy 2018
Manipulating Magento - Mage Titans Italy 2018Joke Puts
 
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
 
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 MaintainMarsBased
 
Angular server side rendering - Strategies & Technics
Angular server side rendering - Strategies & Technics Angular server side rendering - Strategies & Technics
Angular server side rendering - Strategies & Technics Eliran Eliassy
 
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...Coupa Software
 
WebcampZG - Rails 4
WebcampZG - Rails 4WebcampZG - Rails 4
WebcampZG - Rails 4shnikola
 
A Brief Introduction to JQuery Mobile
A Brief Introduction to JQuery MobileA Brief Introduction to JQuery Mobile
A Brief Introduction to JQuery MobileDan Pickett
 
Introduction to Ruby on Rails
Introduction to Ruby on RailsIntroduction to Ruby on Rails
Introduction to Ruby on RailsDiki Andeas
 

Similar a 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
 

Último

Spring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUKSpring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUKJago de Vreede
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Victor Rentea
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Jeffrey Haguewood
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAndrey Devyatkin
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...Martijn de Jong
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...Zilliz
 
[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdfSandro Moreira
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FMESafe Software
 
Cyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdfCyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdfOverkill Security
 
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...apidays
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesrafiqahmad00786416
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Victor Rentea
 
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 AmsterdamDEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 AmsterdamUiPathCommunity
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native ApplicationsWSO2
 
Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024The Digital Insurer
 
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...Orbitshub
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Exploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusExploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusZilliz
 

Último (20)

Spring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUKSpring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUK
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
 
[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Cyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdfCyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdf
 
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challenges
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
 
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 AmsterdamDEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024
 
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Exploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusExploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with Milvus
 

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

Notas del editor

  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