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

Building Extractable Libraries in Rails - BostonRB

Building Extractable Libraries in Rails - BostonRB

Presentation for BostonRB 1/08/2013

Patrick Robertson

January 08, 2013
Tweet

More Decks by Patrick Robertson

Other Decks in Programming

Transcript

  1. Isolate Interaction with your Domain Models Use the DCI pattern

    at the glue points of your application code
  2. Rails 3 brought a wonderful convention for removing /lib from

    automatically being loaded on application boot...
  3. ... however our first instinct is to do this: #

    config/application.rb # Custom directories with classes and modules you want to be autoloadable. config.autoload_paths += %W(#{config.root}/lib) Let’s try something different!
  4. We have created a namespace and setup a proper layout

    when we need to extract our code outside the application into a gem. ... in ten seconds or less :)
  5. It’s so simple to do this: # lib/twitter_wrangler.rb module TwitterWrangler

    OAUTH_KEY = ‘ABCDEFG1234ZXYZB2324’ end Or even this: # lib/twitter_wrangler.rb module TwitterWrangler OAUTH_KEY = ENV[‘TWITTER_KEY’] end
  6. But we already have an entry point for our library

    code. So let’s exploit the heck out of it!
  7. # lib/twitter_wrangler/configuration.rb module TwitterWrangler class Configuration attr_accessor :oauth_key def initialize

    oauth_key = nil end end class << self attr_accessor :configuration end def self.configure self.configuration ||= Configuration.new yield(configuration) if block_given? end end
  8. # lib/twitter_wrangler/configuration.rb module TwitterWrangler class Configuration attr_accessor :oauth_key def initialize

    oauth_key = nil end end class << self attr_accessor :configuration end def self.configure self.configuration ||= Configuration.new yield(configuration) if block_given? end end
  9. # lib/twitter_wrangler/configuration.rb module TwitterWrangler class Configuration attr_accessor :oauth_key def initialize

    oauth_key = nil end end class << self attr_accessor :configuration end def self.configure self.configuration ||= Configuration.new yield(configuration) if block_given? end end
  10. # lib/twitter_wrangler/configuration.rb module TwitterWrangler class Configuration attr_accessor :oauth_key def initialize

    oauth_key = nil end end class << self attr_accessor :configuration end def self.configure self.configuration ||= Configuration.new yield(configuration) if block_given? end end
  11. # lib/twitter_wrangler/configuration.rb module TwitterWrangler class Configuration attr_accessor :oauth_key def initialize

    oauth_key = nil end end class << self attr_accessor :configuration end def self.configure self.configuration ||= Configuration.new yield(configuration) if block_given? end end
  12. # lib/twitter_wrangler/configuration.rb module TwitterWrangler class Configuration attr_accessor :oauth_key def initialize

    oauth_key = nil end end class << self attr_accessor :configuration end def self.configure self.configuration ||= Configuration.new yield(configuration) if block_given? end end
  13. # lib/twitter_wrangler/roles/tweeting_patient.rb module TwitterWrangler::Roles::TweetingPatient def bmi_twitter_message “Today my BMI is

    #{body_mass_index} and I’m #{percent_of_body_mass_index_goal} from my goal of #{body_mass_index_goal}!” end end
  14. # lib/twitter_wrangler/support/fake_patient.rb class FakePatient def body_mass_index;27;end def percent_of_body_mass_index_goal;”95%”;end def body_mass_index_goal;25;end

    end # spec/twitter_wrangler/roles/tweeting_patient_spec.rb describe TweetingPatient do let(:patient) { FakePatient.new } before { FakePatient.extend TweetingPatient } it ‘asserts some fantastic things’ do #GLORIOUS SPECS end end
  15. # spec/twitter_wrangler/roles/tweeting_patient_spec.rb describe TweetingPatient do let(:patient) { OpenStruct.new(body_mass_index: 27, percent_of_body_mass_index_goal:

    “95%”, body_mass_index_goal: 25) } before { patient.extend TweetingPatient } it ‘asserts some fantastic things’ do #GLORIOUS SPECS end end
  16. # lib/twitter_wrangler/bmi_twitter_update.rb module TwitterWrangler class BMITwitterUpdate attr_reader: patient def initialize(patient)

    @patient = patient @patient.extend TweetingPatient end def call TwitterWranglerQueue.add @patient.bmi_twitter_message end end end
  17. # lib/twitter_wrangler/bmi_twitter_update.rb module TwitterWrangler class BMITwitterUpdate attr_reader: patient def initialize(patient)

    @patient = patient @patient.extend TweetingPatient end def call TwitterWranglerQueue.add @patient.bmi_twitter_message end end end
  18. # lib/twitter_wrangler/bmi_twitter_update.rb module TwitterWrangler class BMITwitterUpdate attr_reader: patient def initialize(patient)

    @patient = patient @patient.extend TweetingPatient end def call TwitterWranglerQueue.add @patient.bmi_twitter_message end end end
  19. # lib/twitter_wrangler/bmi_twitter_update.rb module TwitterWrangler class BMITwitterUpdate attr_reader: patient def initialize(patient)

    @patient = patient @patient.extend TweetingPatient end def call TwitterWranglerQueue.add @patient.bmi_twitter_message end end end
  20. # spec/models/patient_bmi_observer_spec.rb describe PatientBMIObserver do let(:bmi_update) { mock() } before

    do BMITwitterUpdate.stubs(:new).returns bmi_update bmi_update.stubs :call end it ‘sends a twitter update on mah BMI’ do # expect #call to have been called once end end
  21. # spec/models/patient_bmi_observer_spec.rb describe PatientBMIObserver do let(:bmi_update) { mock() } before

    do BMITwitterUpdate.stubs(:new).returns bmi_update bmi_update.stubs :call end it ‘sends a twitter update on mah BMI’ do # expect #call to have been called once end end
  22. Provides clear separation of Domain Model and outside code Creates

    isolatable tests Easy path forward for gem extraction Boilerplate :( YAGNI :(