Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Ruby Plus Rails Plus Your Application Minus Rails

3,890 views

Published on

Ruby on Rails is a famously opinionated framework that handles ninety percent of the problems a typical web application faces. But what about the last ten percent? Every app has to jump off the Rails eventually, whether that means integrating with an external web service, importing data from another database, or providing enhanced search functionality. In this talk I'll lead you through some patterns for handling those cases, and discuss certain features of Rails 3 that let you leverage the best pieces of the core framework without resorting to ugly hacks. Finally we'll discuss what a new, more modular core framework means for a post-Rails world.

Published in: Technology
  • Be the first to comment

Ruby Plus Rails Plus Your Application Minus Rails

  1. 1. RubyPlus RailsPlus Your ApplicationMinus RailsBrian Guthriebguthrie@thoughtworks.comhttp://twitter.com/bguthrie
  2. 2. TheLastTenPercentBrian Guthriebguthrie@thoughtworks.comhttp://twitter.com/bguthrie
  3. 3. How to do complex things in Ruby (without shooting yourself in the foot) 2010
  4. 4. How to do simple things in Ruby (with care and craft) 2011
  5. 5. the last10%
  6. 6. So what’s hard about this?(Is it time to go check out the talk in the other room? That guy seems cool.)
  7. 7. Nothing!
  8. 8. Every good talk needs a rant
  9. 9. not complex
  10. 10. There’s an army of programmers looking for Rails bugs
  11. 11. Your code? Just you.
  12. 12. Craft is even more important.
  13. 13. notcookiecutter
  14. 14. Most Ruby programmers are Rails programmers first
  15. 15. Learning what Rails doesn’t dois as important as learning what it does
  16. 16. For those craftspeople here: Thank you
  17. 17. Gaps in Rails:Where’s the problem?
  18. 18. “Prior to Rails 3.0, if a plugin or gemdeveloper wanted to have an objectinteract with Action Pack helpers, it wasrequired to either copy chunks of codefrom Rails, or monkey patch entirehelpers to make them handle objects thatdid not exactly conform to the ActiveRecord interface. This would result incode duplication and fragile applicationsthat broke on upgrades. - ActiveModel README
  19. 19. Rails 2Active Active Action ActionRecord Resource Mailer Pack Active Railties Support
  20. 20. Presentation Logic Data
  21. 21. Presentation Logic Dataform_for @user do
  22. 22. Presentation Logic Dataform_for @user do @user = User.find(params[:id])
  23. 23. Presentation Logic Dataform_for @user do @user = User.find(params[:id])
  24. 24. Presentation Logic Dataform_for @user do @user = User.find(params[:id])
  25. 25. Presentation Logic Dataform_for @user do @user = User.find(params[:id]) @fb_user = FbUser.find(params[:id])
  26. 26. Presentation Logic Data form_for @user do @user = User.find(params[:id])form_for @fb_user do @fb_user = FbUser.find(params[:id])
  27. 27. • Domain models that aren’t database tables• Importing and transforming data• Web service clients• Presenter objects (not a comprehensive list)
  28. 28. Rails 2Active Active Action ActionRecord Resource Mailer Pack Active Railties Support
  29. 29. Rails 3 Active ModelActive Active Action ActionRecord Resource Mailer Pack Active Railties Support
  30. 30. Active gem active_model Model Active gem active_support,Support :require => false
  31. 31. the principles Simplicity Testability Abstractability Community
  32. 32. the principlesCode Simplicity Testability Abstractability Community
  33. 33. S Single responsibility principle An object should have only a single responsibility.O Open/closed principle Software entities … should be open for extension, but closed for modification.L Liskov substitution principle Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.I Dependency inversion principle Depend upon Abstractions. Do not depend upon concretions.D Interface segregation principle Many client specific interfaces are better than one general purpose interface.
  34. 34. S Single responsibility principle An object should have only a single responsibility.O Open/closed principle Software entities … should be open for extension, but closed for modification.L Liskov substitution principle Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.I Dependency inversion principle Depend upon Abstractions. Do not depend upon concretions.D Interface segregation principle Many client specific interfaces are better than one general purpose interface.
  35. 35. SD Card Principles
  36. 36. S Single responsibility principle An object should have only a single responsibility.O Open/closed principle Software entities … should be open for extension, but closed for modification.L Liskov substitution principle Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.I Dependency inversion principle Depend upon Abstractions. Do not depend upon concretions.D Interface segregation principle Many client specific interfaces are better than one general purpose interface.
  37. 37. S Single responsibility principle An object should have only a single responsibility.O Open/closed principle Software entities … should be open for extension, but closed for modification.L Liskov substitution principle Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program. I Dependency inversion principle Depend upon Abstractions. Do not depend upon concretions.D Interface segregation principle Many client specific interfaces are better than one general purpose interface.Mini SD Card Principles
  38. 38. the principles Simplicity TestabilityArchitecture Abstractability Community
  39. 39. IdiomaticityIdiom (plural idioms) 1. A manner of speaking, a way of expressing oneself. 2. An artistic style (for example, in art, architecture, or music); an instance of such a style. 3. An expression peculiar to or characteristic of a particular language, especially when the meaning is illogical or separate from the meanings of its component words. 4. (linguistics) A communicative system under study, which could be called either a dialect or a language, when its status as a language or dialect is irrelevant. 5. (programming) A programming construct or phraseology generally held to be the most efficient, elegant or effective means to achieve a particular result or behavior. http://en.wiktionary.org/wiki/idiom
  40. 40. IdiomaticityIdiom (plural idioms) 1. A manner of speaking, a way of expressing oneself. 2. An artistic style (for example, in art, architecture, or music); an instance of such a style. 3. An expression peculiar to or characteristic of a particular language, especially when the meaning is illogical or separate from the meanings of its component words. 4. (linguistics) A communicative system under study, which could be called either a dialect or a language, when its status as a language or dialect is irrelevant. 5. (programming) A programming construct or phraseology generally held to be the most efficient, elegant or effective means to achieve a particular result or behavior. http://en.wiktionary.org/wiki/idiom
  41. 41. Some of the best Ruby programmers have gone against commonly-accepted style
  42. 42. Keep it simple when you canBreak the rules when you must (or when it sounds like fun)
  43. 43. The best* Ruby code is clean, simple, and follows community idioms. * Easiest to maintain.
  44. 44. the recipeBuild an objectTest that objectExpose a familiar API
  45. 45. Mmm... Masala Ruby
  46. 46. the recipeBuild an objectTest that objectExpose a familiar API
  47. 47. Antipattern: Bad Modeling
  48. 48. Dosa Tracker(I smell a startup opportunity)
  49. 49. CSV Filename,city,address,district,review,dosa typesSukh Sagar,Chennai,2nd Ave,Anna Nagar,plain|paper|masala|mysore masala|ravaSri Sai Sagar,Bangalore,"5th Cross, 18th Main Road, HAL 2ndStage",Indiranagar,plain|paper|masala|rava masala|mysore masala|coconut
  50. 50. Columnsname,city,address,district,review,dosa typesSukh Sagar,Chennai,2nd Ave,Anna Nagar,plain|paper|masala|mysore masala|ravaSri Sai Sagar,Bangalore,"5th Cross, 18th Main Road, HAL 2ndStage",Indiranagar,plain|paper|masala|rava masala|mysore masala|coconut
  51. 51. Dataname,city,address,district,review,dosa typesSukh Sagar,Chennai,2nd Ave,Anna Nagar,plain|paper|masala|mysore masala|ravaSri Sai Sagar,Bangalore,"5th Cross, 18th Main Road, HAL 2ndStage",Indiranagar,plain|paper|masala|rava masala|mysore masala|coconut
  52. 52. ETLCSV (extract, transform, load)
  53. 53. Hand-written SQLCSV
  54. 54. Slight improvement CSVCSV Mapper module ABunchOfSuperAwesomeDataTransformationMethods def some_conversion_that_wont_make_sense_six_months_from_now(inputs) end end
  55. 55. Active Support CSV ActiveCSV Record Mapper module DosaTracker class DosaStandMapper def initialize(inputs={}) end def to_model # Output end end end
  56. 56. module DosaTracker class DosaStandMapper def initialize(inputs={}) @inputs = inputs end def location [ @inputs["city"], @inputs["address"], @inputs["landmark"] ].join(", ") end endend
  57. 57. require spec_helperdescribe DosaTracker::DosaStandMapper do context "#location" do it "should include the city and address" do DosaTracker::DosaStandMapper.new( city => Chennai, address => 2nd Avenue, Anna Nagar ).location.should match("Chennai, 2nd Avenue, Anna Nagar") end endend
  58. 58. context "#rating" do [ [ "pretty good", 4 ], [ "bad", 1 ], [ "okay", 3 ], [ "awesome", 5 ], [ "meh", 2 ] ].each do |review, rating| it "should match a review of #{review} to a rating of #{rating} stars" do DosaTracker::DosaStandMapper.new( review => "it was sort of #{review}" ).rating.should == rating end endend
  59. 59. def rating case @inputs["review"] when /bad/; 1 when /meh/; 2 when /okay/; 3 when /pretty good/; 4 when /awesome/; 5 endend
  60. 60. TipKeep objects testable by reducing internal dependencies. Each method tested individually.
  61. 61. context ".build" do EXAMPLE_CSV = <<-CSVname,city,address,district,review,dosa typesSukh Sagar,Chennai,2nd Ave,Anna Nagar,plain|paper|masala|mysore masala|ravaSri Sai Sagar,Bangalore,"5th Cross, 18th Main Road, HAL 2nd Stage",Indiranagar,plain|paper|masala|rava masala|mysore masala|coconutCSV it "should instantiate an array of mappers based on the file its given" do csv_file = Rails.root.join("test", "tmp.csv") csv_file.open("w") { |io| io << EXAMPLE_CSV } mappers = DosaTracker::DosaStandMapper.build(csv_file.open) mappers.size.should == 2 mappers[0].name.should == Sukh Sagar end end
  62. 62. def self.build(io) CSV.read(io, :headers => :first_row).map do |row| self.new(row.to_hash) endend
  63. 63. TipInitializers are dumb, factory methods are smart. Follow Rails factory patterns where appropriate.
  64. 64. Factory method
  65. 65. Factory method Initializer
  66. 66. context "#dosa_types" do it "map a single dosa type to a list with a single item" do DosaTracker::DosaStandMapper.new( dosa types => rava ).dosa_types.should == [ DosaType.new(:name => "rava") ] end it "map pipe-delimited dosa types to a list of multiple types" do DosaTracker::DosaStandMapper.new( dosa types => rava|masala|paper|butter ).dosa_types.should == [ DosaType.new(:name => "rava"), DosaType.new(:name => "masala"), DosaType.new(:name => "paper"), DosaType.new(:name => "butter") ] endenddef dosa_types @inputs["dosa types"].split("|").map do |name| DosaType.new(:name => name) endend
  67. 67. TipDont stub in the class you’re testing. It’s a smell. Reduce dependencies instead.
  68. 68. context "#to_model" do it "should expose a model object with the newly-populated data" do DosaTracker::DosaStandMapper.new( name => Sukh Sagar, city => Chennai, address => 2nd Avenue, Anna Nagar, review => Pretty good, dosa types => plain|paper|masala|mysore masala|rava ).to_model.location.should match("Chennai, 2nd Avenue, Anna Nagar") endend
  69. 69. class DosaStand < ActiveRecord::Base # Domain logic hereendmodule DosaTracker class DosaStandMapper def to_attributes { name: self.name, rating: self.rating, location: self.location, dosa_types: self.dosa_types } end def to_model DosaStand.new(self.to_attributes) end endend
  70. 70. TipAbstract when youve got a pattern. But not before.
  71. 71. TipWhen youve abstracted a pattern, make it a gem. This is how good ideas are born.You don’t have to release it.
  72. 72. Antipattern:Too much view logic
  73. 73. http://www.flickr.com/photos/donpezzano/3496645152/ Active Model
  74. 74. http://www.flickr.com/photos/donpezzano/3496645152/ Active Model No integration dependencies
  75. 75. http://www.flickr.com/photos/donpezzano/3496645152/ Active Model No integration dependencies Mix and match
  76. 76. http://www.flickr.com/photos/donpezzano/3496645152/ Active Model No integration dependencies Mix and match Quack like a duck
  77. 77. http://www.flickr.com/photos/donpezzano/3496645152/ Active Model No integration dependencies Mix and match Quack like a duck Keep it consistent
  78. 78. autoload :AttributeMethodsautoload :BlockValidator, active_model/validatorautoload :Callbacksautoload :Conversionautoload :DeprecatedErrorMethodsautoload :Dirtyautoload :EachValidator, active_model/validatorautoload :Errorsautoload :Lintautoload :MassAssignmentSecurityautoload :Name, active_model/namingautoload :Namingautoload :Observer, active_model/observingautoload :Observingautoload :Serializationautoload :TestCaseautoload :Translationautoload :Validationsautoload :Validator
  79. 79. Mix and match to suit your needs
  80. 80. class DosaSearchController < ApplicationController def new @search = DosaSearch.new end def show @search = DosaSearch.new(params[:search]) @results = @search.results endend
  81. 81. describe DosaSearch do include Test::Unit::Assertions include ActiveModel::Lint::Tests def model subject end ActiveModel::Lint::Tests.public_instance_methods.map{|m| m.to_s}.grep(/^test/).each do |m| example m.gsub(_, ) do send m end endend
  82. 82. class DosaSearch include ActiveModel::Conversion def initialize(attrs={}) @attributes = attrs end def persisted? false endend
  83. 83. TipIf you accept attributes, mass-assign. It’s simple and straightforward.
  84. 84. context "attributes" do it "should assign city" do DosaSearch.new(:city => "Chennai").city.should == "Chennai" endendcontext "validations" do it "should not be valid if a city is not specified" do DosaSearch.new.should have(1).error_on(:city) end it "should be valid if it has a city" do DosaSearch.new(:city => "Mysore").should be_valid endend
  85. 85. class DosaSearch include ActiveModel::Conversion include ActiveModel::Validations attr_reader :attributes attr_accessor :city validates_presence_of :city def initialize(attrs={}) @attributes = attrs @attributes.each do |key, value| send "#{key}=", value end end def persisted? false endend
  86. 86. class DosaSearchController < ApplicationController def new @search = DosaSearch.new end def show @search = DosaSearch.new(params[:search]) @results = @search.results endend
  87. 87. Antipattern: Slavish Adherence (with zombies)
  88. 88. The Uncanny Valley
  89. 89. class Person include Validatable validates_presence_of :name attr_accessor :nameend
  90. 90. class Person include Validatable validates_presence_of :name, :times => 1 attr_accessor :nameend
  91. 91. class MortgageApplication include Validatable validates_presence_of :ssn, :groups => [:saving, :underwriting] validates_presence_of :name, :groups => :underwriting attr_accessor :name, :ssnend
  92. 92. class MortgageApplication include Validatable validates_presence_of :ssn, :groups => [:saving, :underwriting] validates_presence_of :name, :groups => :underwriting attr_accessor :name, :ssnend>> application = MortgageApplication.new>> application.ssn = 377990118>> application.valid_for_saving?=> trueapplication.valid_for_underwriting?=> false
  93. 93. Validations! Everywhere!
  94. 94. http://www.flickr.com/photos/rodolphoreis/5253294758/ [your library here]
  95. 95. Validations was a great gem. It solved a real need.
  96. 96. Database.establish_connection
  97. 97. ActiveRecordDatabase.establish_connection
  98. 98. ActiveRecord ValidatableDatabase.establish_connection
  99. 99. ActiveRecord ValidatableDatabase.establish_connection [your library here]
  100. 100. Post-Rails?http://www.flickr.com/photos/vogelium/2105821119
  101. 101. Rails isn’t going anywhere
  102. 102. What is Ruby on Rails anyway?
  103. 103. What is Ruby on Rails anyway? Routing?
  104. 104. What is Ruby on Rails anyway? Routing? Requires?
  105. 105. What is Ruby on Rails anyway? Routing? Requires? A set of conventions?
  106. 106. Post-Rails:use individual pieces without being tied to the whole
  107. 107. ActiveRecord anywhere First-class SinatraUnintrusive ActiveSupport
  108. 108. More generally, code is now more portable
  109. 109. Rails 3 Active ModelActive Active Action ActionRecord Resource Mailer Pack Active Railties Support
  110. 110. Sinatra Active ModelActive ActionRecord Mailer Active Support
  111. 111. What happens when stuff gets modular
  112. 112. How to learn Ruby
  113. 113. How to learn Ruby (secret)
  114. 114. “Dude, this is RubyConf. How many people here do you really think don’t already know Ruby?”
  115. 115. 1Read Ruby Code
  116. 116. 1aRead good Ruby Code
  117. 117. 2Write Ruby Code
  118. 118. 2aWrite Ruby Code (without Rails)
  119. 119. 3Structure your libraries like gems Run an internal gem server Release a gem!
  120. 120. Robots do what every other robot does
  121. 121. Robots do what every other robot does Don’t be a robot!
  122. 122. Brian Guthriebguthrie@thoughtworks.comhttp://twitter.com/bguthriequestions? http://www.flickr.com/photos/jonnya/4212907371/

×