0
Presenters! (On Rails)   Mike Desjardins    @mdesjardins
Design Pattern?        Per our good friends at Wikipedia:In software engineering, a design pattern is a generalreusable so...
Design Pattern?        Per our good friends at Wikipedia:In software engineering, a design pattern is a generalreusable so...
Design Pattern?        Per our good friends at Wikipedia:In software engineering, a design pattern is a generalreusable so...
Design Pattern?        Per our good friends at Wikipedia:In software engineering, a design pattern is a generalreusable so...
Design Pattern?        Per our good friends at Wikipedia:In software engineering, a design pattern is a generalreusable so...
Design Pattern?        Per our good friends at Wikipedia:In software engineering, a design pattern is a generalreusable so...
Design Pattern?        Per our good friends at Wikipedia:In software engineering, a design pattern is a generalreusable so...
Swell!     MVC!   Note to any Microsoft                 knuckleheads:         This is not a presentation on               ...
Model View  Controller        ControllersModel                 Views
GOsh, What’s Wrong With        MVC?    As your project gets more complex, theControllers and Views become “bloated” despit...
GOsh, What’s Wrong With        MVC?    As your project gets more complex, theControllers and Views become “bloated” despit...
These are just the filters in CityEats’ Orders                          Controller!skip_before_filter :protect_private_envir...
AW SHUCKS, Actions                 too! create action: Here’s the OrdersController’sdef create if params[:iframe]   @styli...
AW SHUCKS, Actions                 too! create action: Here’s the OrdersController’sdef create if params[:iframe]   @styli...
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 ...
Thicker than a five dollar                    malt   = order_form.fields_for :reservation do |reservation_form|    = render ...
Who willmaintainand test all thislogic?!?
Presenters to theRescue!
Let’s Review...        ControllersModel                 Views
GOLLY, that’s bad     news!         Controllers Model                 Views
Presenter           s           PresenterController Model             View
Represent “Current State of the            View”               Presenter  Controller   Model                   View
Invoicing!  Scripps needed a   way to previewInvoices that were to      be sent to Restaurants, as well   as view existing...
Invoiceclass InvoicePresenter                  Presenter attr_accessor :reservation_transactions, :non_reservation_transac...
Invoice                     Presenterdef render_performance_summary(context) by_source = {} total = 0 reservation_transact...
Invoicedef render_line_items(context)                                                  Presenter                          ...
Invoice%section %h4#invoice-header  Invoice %h4#ce-logo                                                     Presenter  %im...
...and the controller is tiny, too!def show invoice = Invoice.find(params[:id]) @presenter = InvoicePresenter.new(invoice)e...
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.
LiveDemo
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 SimpleDel...
Decorator Pattern?
Decorator Pattern?  Delegate                Decorator Hey look, it’s UML! I read about this in a Computer Science Archaeol...
Decorator Pattern?     Delegate           Decorator+jumpFromSpaceBalloon
Decorator Pattern?     Delegate               Decorator+jumpFromSpaceBalloon   +jumpFromSpaceBalloon                      ...
Decorator Pattern?     Delegate           SimpleDelegator+jumpFromSpaceBalloon   +initializer(thing: Delegate)            ...
Decorator Pattern?          SimpleDelegator  Model         Exhibitor          +initializer(a_model: Model)          +rende...
Exhibitor Pattern          Uses “Decorator Pattern” to extend an          existing model          Implements Decorator Pat...
some People in the railsCommunity conflate thesetwo notions (exhibitor vs.       presenter) But now you’re smarter    than ...
FURTHER Readinghttp://blog.jayfields.com/2007/03/rails-presenter-pattern.htmlhttp://broadcastingadam.com/2011/06/present_yo...
FURTHER Readinghttp://blog.jayfields.com/2007/03/rails-presenter-pattern.htmlSimple one, does some similar stuff w/ delegat...
Quest ions?    Retro Clip Art Provided By Tack-o-Rama             http://tackorama.net
Presenters in Rails
Upcoming SlideShare
Loading in...5
×

Presenters in Rails

6,168

Published on

Internal company presentation on the use of the Presenter pattern in Ruby on Rails.

Published in: Technology
1 Comment
4 Likes
Statistics
Notes
No Downloads
Views
Total Views
6,168
On Slideshare
0
From Embeds
0
Number of Embeds
4
Actions
Shares
0
Downloads
33
Comments
1
Likes
4
Embeds 0
No embeds

No notes for slide
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Transcript of "Presenters in Rails"

    1. 1. Presenters! (On Rails) Mike Desjardins @mdesjardins
    2. 2. Design Pattern? Per our good friends at Wikipedia:In software engineering, a design pattern is a generalreusable solution to a commonly occurring problemwithin a given context in software design.
    3. 3. Design Pattern? Per our good friends at Wikipedia:In software engineering, a design pattern is a generalreusable solution to a commonly occurring problemwithin a given context in software design. Observer
    4. 4. Design Pattern? Per our good friends at Wikipedia:In software engineering, a design pattern is a generalreusable solution to a commonly occurring problemwithin a given context in software design. Observer Factory
    5. 5. Design Pattern? Per our good friends at Wikipedia:In software engineering, a design pattern is a generalreusable solution to a commonly occurring problemwithin a given context in software design. Observer Factory Bridge
    6. 6. Design Pattern? Per our good friends at Wikipedia:In software engineering, a design pattern is a generalreusable solution to a commonly occurring problemwithin a given context in software design. Observer Factory Bridge Singleton
    7. 7. Design Pattern? Per our good friends at Wikipedia:In software engineering, a design pattern is a generalreusable solution to a commonly occurring problemwithin a given context in software design. Observer Factory Bridge Singleton Decorator
    8. 8. Design Pattern? Per our good friends at Wikipedia:In software engineering, a design pattern is a generalreusable solution to a commonly occurring problemwithin a given context in software design. Observer Factory Bridge Singleton Visitor Decorator
    9. 9. Swell! MVC! Note to any Microsoft knuckleheads: This is not a presentation on “MVP.”
    10. 10. Model View Controller ControllersModel Views
    11. 11. GOsh, What’s Wrong With MVC? As your project gets more complex, theControllers and Views become “bloated” despite your best efforts.
    12. 12. GOsh, What’s Wrong With MVC? As your project gets more complex, theControllers and Views become “bloated” despite your best efforts.
    13. 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 doesnt seem so at a glance. -Timbefore_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. 14. AW SHUCKS, Actions too! create action: Here’s the OrdersController’sdef 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 endend
    15. 15. AW SHUCKS, Actions too! create action: Here’s the OrdersController’sdef 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 endend
    16. 16. Gee-Willikers!
    17. 17. It’s not just the controllers that get bloated, Views get messed up, too...
    18. 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. 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. 20. Who willmaintainand test all thislogic?!?
    21. 21. Presenters to theRescue!
    22. 22. Let’s Review... ControllersModel Views
    23. 23. GOLLY, that’s bad news! Controllers Model Views
    24. 24. Presenter s PresenterController Model View
    25. 25. Represent “Current State of the View” Presenter Controller Model View
    26. 26. Invoicing! Scripps needed a way to previewInvoices that were to be sent to Restaurants, as well as view existing invoices
    27. 27. Invoiceclass 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 dont 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 endend
    28. 28. Invoice Presenterdef 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. 29. Invoicedef 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 theend 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} - ThankYou", 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. 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, pleaseinclude 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. 31. ...and the controller is tiny, too!def show invoice = Invoice.find(params[:id]) @presenter = InvoicePresenter.new(invoice)enddef new account = @restaurant.account invoice_date = @restaurant.next_invoice_date @presenter = InvoicePresenter.new(InvoicePreview.new(account, invoice_date))end
    32. 32. Made in the Shade
    33. 33. Whoop-de-freakin-Do!
    34. 34. Have you ever written a good view test?
    35. 35. Have you ever written a good view test? No, seriously. Be Honest.
    36. 36. LiveDemo
    37. 37. Can’t I just do all this with Helpers?
    38. 38. Helpers don’t have State
    39. 39. Not Jesus.
    40. 40. Avdi Grimm
    41. 41. http://www.objectsonrails.com
    42. 42. Exhibitor Pattern Uses “Decorator Pattern” to extend an existing model Implements Decorator Pattern using Ruby’s SimpleDelegator class
    43. 43. Decorator Pattern?
    44. 44. Decorator Pattern? Delegate Decorator Hey look, it’s UML! I read about this in a Computer Science Archaeology Book once!
    45. 45. Decorator Pattern? Delegate Decorator+jumpFromSpaceBalloon
    46. 46. Decorator Pattern? Delegate Decorator+jumpFromSpaceBalloon +jumpFromSpaceBalloon +drinkRedBull
    47. 47. Decorator Pattern? Delegate SimpleDelegator+jumpFromSpaceBalloon +initializer(thing: Delegate) +drinkRedBull Gosh, Rubysure is spiffy!
    48. 48. Decorator Pattern? SimpleDelegator Model Exhibitor +initializer(a_model: Model) +render_body(context:View)
    49. 49. Exhibitor Pattern Uses “Decorator Pattern” to extend an existing model Implements Decorator Pattern using Ruby’s SimpleDelegator class# exhibits/text_post_exhibit.rbrequire delegateclass TextPostExhibit < SimpleDelegator def initialize(model, context) @context = context super(model) end def render_body @context.render(partial: "/posts/text_body", locals: {post: self}) endend
    50. 50. some People in the railsCommunity conflate thesetwo notions (exhibitor vs. presenter) But now you’re smarter than all of them!
    51. 51. FURTHER Readinghttp://blog.jayfields.com/2007/03/rails-presenter-pattern.htmlhttp://broadcastingadam.com/2011/06/present_yourself/http://railsvideos.net/railsconf-2012-presenters-and-decorators-a-co
    52. 52. FURTHER Readinghttp://blog.jayfields.com/2007/03/rails-presenter-pattern.htmlSimple one, does some similar stuff w/ delegation like Avdi without usingSimpleDelagatorhttp://broadcastingadam.com/2011/06/present_yourself/Does some neat stuff with memoizationhttp://railsvideos.net/railsconf-2012-presenters-and-decorators-a-coVery Good RailsConf 2012 Presentation by Mike Moore. UsesActiveDecorator to implement a form of Exhibitor
    53. 53. Quest ions? Retro Clip Art Provided By Tack-o-Rama http://tackorama.net
    1. A particular slide catching your eye?

      Clipping is a handy way to collect important slides you want to go back to later.

    ×