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
of 54

Presenters in Rails

Internal company presentation on the use of the Presenter pattern in Ruby on Rails.
Published on: Mar 4, 2016
Published in: Technology      
Source: www.slideshare.net


Transcripts - 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 generalreusable solution to a commonly occurring problemwithin a given context in software design.
  • 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. 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. 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. 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. 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. 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. Swell! MVC! Note to any Microsoft knuckleheads: This is not a presentation on “MVP.”
  • 10. Model View Controller ControllersModel Views
  • 11. GOsh, What’s Wrong With MVC? As your project gets more complex, theControllers and Views become “bloated” despite your best efforts.
  • 12. GOsh, What’s Wrong With MVC? As your project gets more complex, theControllers 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 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. 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. 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. Gee-Willikers!
  • 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 willmaintainand test all thislogic?!?
  • 21. Presenters to theRescue!
  • 22. Let’s Review... ControllersModel Views
  • 23. GOLLY, that’s bad news! Controllers Model Views
  • 24. Presenter s PresenterController Model View
  • 25. Represent “Current State of the View” Presenter Controller Model View
  • 26. Invoicing! Scripps needed a way to previewInvoices that were to be sent to Restaurants, as well as view existing invoices
  • 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. 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. 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. 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. ...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. Made in the Shade
  • 33. Whoop-de-freakin-Do!
  • 34. Have you ever written a good view test?
  • 35. Have you ever written a good view test? No, seriously. Be Honest.
  • 36. LiveDemo
  • 37. Can’t I just do all this with Helpers?
  • 38. Helpers don’t have State
  • 39. Not Jesus.
  • 40. Avdi Grimm
  • 41. http://www.objectsonrails.com
  • 42. Exhibitor Pattern Uses “Decorator Pattern” to extend an existing model Implements Decorator Pattern using Ruby’s SimpleDelegator class
  • 43. Decorator Pattern?
  • 44. Decorator Pattern? Delegate Decorator Hey look, it’s UML! I read about this in a Computer Science Archaeology Book once!
  • 45. Decorator Pattern? Delegate Decorator+jumpFromSpaceBalloon
  • 46. Decorator Pattern? Delegate Decorator+jumpFromSpaceBalloon +jumpFromSpaceBalloon +drinkRedBull
  • 47. Decorator Pattern? Delegate SimpleDelegator+jumpFromSpaceBalloon +initializer(thing: Delegate) +drinkRedBull Gosh, Rubysure is spiffy!
  • 48. Decorator Pattern? SimpleDelegator Model Exhibitor +initializer(a_model: Model) +render_body(context:View)
  • 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. some People in the railsCommunity conflate thesetwo notions (exhibitor vs. presenter) But now you’re smarter than all of them!
  • 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. 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. Quest ions? Retro Clip Art Provided By Tack-o-Rama http://tackorama.net

Related Documents