Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Powerful Interfaces

Powerful Interfaces

We explore the best practices in using interfaces as the foundation for designing object oriented applications in Ruby and Rails. We will talk about some of the techniques that make it possible to write loosely coupled components, run faster tests and have a more enjoyable programming experience.

9958009345c43a8461e8673fbd0ae256?s=128

Carlos Souza

April 26, 2012
Tweet

Transcript

  1. Powerful Interfaces @caike RailsConf 2012

  2. why developing Rails apps with fine-grained components is freaking awesome!

  3. Granularity

  4. Problems

  5. Not Fun! http://www.flickr.com/photos/lianneviau/499003712/sizes/l/in/photostream/

  6. Interface

  7. class Payment < ActiveRecord::Base def complete end def cancel end

    end
  8. class PaymentsController < ApplicationController def create payment = Payment.new(params[:payment]) if

    payment.save redirect_to(payment, success:'You are now subscribed!') else render :index end end end
  9. class PaymentsController < ApplicationController def create payment = Payment.new(params[:payment]) if

    payment.save redirect_to(payment, success:'You are now subscribed!') else render :index end end end Client
  10. class PaymentsController < ApplicationController def create payment = Payment.new(params[:payment]) if

    payment.save redirect_to(payment, success:'You are now subscribed!') else render :index end end end Client Interface
  11. class PaymentsController < ApplicationController def create payment = Payment.new(params[:payment]) if

    payment.save redirect_to(payment, success:'You are now subscribed!') else render :index end end end
  12. class PaymentsController < ApplicationController def create payment = Payment.new(params[:payment]) if

    payment.save redirect_to(payment,success: 'You are now subscribed!') else render :index end end end
  13. class PaymentsController < ApplicationController def create payment = Payment.new(params[:payment]) if

    payment.save redirect_to(payment,success: 'You are now subscribed!') else render :index end end end Coarse-Grained
  14. Callbacks Observers

  15. Three Laws of Interfaces

  16. do what its methods say it does. #1

  17. class PaymentsController < ApplicationController def create payment = Payment.new(params[:payment]) if

    payment.save redirect_to(payment, success:'You are now subscribed!') else render :index end end end
  18. Payment#save vs. Payment#place

  19. class PaymentsController < ApplicationController def create payment = Payment.new(params[:payment]) if

    payment.save redirect_to(payment, success:'You are now subscribed!') else render :index end end end
  20. class PaymentsController < ApplicationController def create payment = Payment.new(params[:payment]) if

    payment.place redirect_to(payment, success:'You are now subscribed!') else render :index end end end
  21. class Payment < ActiveRecord::Base protected :save ## # Add your

    comments here. # Really. Please do it :) ## def place end end
  22. do no harm. #2

  23. <h2>Registered Users</h2> <div class="users"> <ul> <% User.recent.paginate(@page).each do |user| %>

    <li><%= link_to(user.full_name, user) %></li> <% end %> </ul> </div>
  24. <h2>Registered Users</h2> <div class="users"> <ul> <% User.recent.paginate(@page).each do |user| %>

    <li><%= link_to(user.full_name, user) %></li> <% end %> </ul> </div>
  25. <h2>Registered Users</h2> <div class="users"> <ul> <% @users.each do |user| %>

    <li><%= link_to(user.full_name, user) %></li> <% end %> </ul> </div>
  26. notify client if something goes wrong. #3

  27. raise "Invalid HTTP request"

  28. raise "Invalid HTTP request"

  29. raise "Invalid HTTP request" vs. raise "PayX Authentication Error"

  30. raise "PayX Authentication Error"

  31. raise "PayX Authentication Error"

  32. do what its methods say it does. #1

  33. do no harm. #2

  34. notify client if something goes wrong. #3

  35. class PaymentsController < ApplicationController def create payment = Payment.new(params[:payment]) if

    payment.save redirect_to(payment, success:'You are now subscribed!') else render :index end end end
  36. class PaymentsController < ApplicationController def create payment = Payment.new(params[:payment]) if

    payment.save redirect_to(payment,success: 'You are now subscribed!') else render :index end end end
  37. class PaymentsController < ApplicationController def create payment = Payment.new(params[:payment]) if

    payment.place redirect_to(payment,success: 'You are now subscribed!') else render :index end end end
  38. class PaymentsController < ApplicationController def create payment = Payment.new(params[:payment]) if

    payment.place redirect_to(payment,success: 'You are now subscribed!') else render :index end end end doing too much
  39. Factory Method Pattern

  40. class PaymentsController < ApplicationController def create payment = Payment.new(params[:payment]) if

    payment.place redirect_to(payment,success: 'You are now subscribed!') else render :index end end end
  41. class PaymentsController < ApplicationController def create payment = Payment.build_with(params[:payment]) if

    payment.place redirect_to(payment,success: 'You are now subscribed!') else render :index end end end
  42. class PaymentsController < ApplicationController def create payment = Payment.build_with(params[:payment]) if

    payment.place redirect_to(payment, success:'You are now subscribed!') else render :index end end end
  43. class PaymentsController < ApplicationController def create payment = Payment.build_with(params[:payment]) if

    payment.place redirect_to(payment, success:'You are now subscribed!') else render :index end end end
  44. class PlanSubscriber def self.build_for(user) end def subscribe(payment_info) end end

  45. class PaymentsController < ApplicationController def create payment = Payment.build_with(params[:payment]) if

    payment.place redirect_to(payment, success:'You are now subscribed!') else render :index end end end
  46. class PaymentsController < ApplicationController def create payment = Payment.build_with(params[:payment]) if

    payment.place redirect_to(payment, success:'You are now subscribed!') else render :index end end end
  47. class PaymentsController < ApplicationController def create plan_subscriber = PlanSubscriber.build_for(current_user) if

    plan_subscriber.subscribe(params[:payment]) payment = plan_subscriber.payment redirect_to(payment, success:'You are now subscribed!') else render :index end end end
  48. Design by Contract

  49. Pre-Conditions Post-Conditions Invariants

  50. Pre-Conditions

  51. User must have a valid email address. Product must be

    available.
  52. Pre-Conditions Post-Conditions

  53. User must have a new payment. Seat must be reserved.

  54. Pre-Conditions Post-Conditions Invariants

  55. User must be registered. Store location must be open.

  56. Pre-Conditions Post-Conditions Invariants

  57. Fat models Thin controllers

  58. Models != AR::Base

  59. class PaymentsController < ApplicationController def create payment = Payment.new(params[:payment]) if

    payment.save redirect_to(payment, success:'You are now subscribed!') else render :index end end end
  60. class PaymentsController < ApplicationController def create plan_subscriber = PlanSubscriber.build_for(current_user) if

    plan_subscriber.subscribe(params[:payment]) payment = plan_subscriber.payment redirect_to(payment, success:'You are now subscribed!') else render :index end end end
  61. Rails is not your application http://blog.firsthand.ca/2011/10/rails-is-not-your- application.html

  62. Extract your application into its own ruby gem.

  63. None
  64. http://objectsonrails.com

  65. Rails Models Complexity Pyramid

  66. 0 1 2 Rails Models Complexity Pyramid

  67. 0 Rails out-of-the-box 1 2 Rails Models Complexity Pyramid

  68. 0 Rails out-of-the-box 1 Protect AR::Base methods from controller 2

    Rails Models Complexity Pyramid
  69. 0 2 Rails out-of-the-box Hide AR::Base classes from controller 1

    Protect AR::Base methods from controller Rails Models Complexity Pyramid
  70. Thank you. @caike