REUSABLE RUBY
     ROUTE 9 RUBY GROUP – JUNE 20, 2012



                BLAKE CARLSON
     BLAKE@COIN-OPERATED.NET   • @SKINANDBONES
GITHUB.COM/SKINANDBONES • LINKEDIN.COM/IN/BLAKECARLSON
WHY GO           REUSABLE?
                                    sloth love
                                  reusable ruby
‣ D-R-Y
‣ Maintainability
‣ Leads to better solutions
‣ Make large projects
  adaptable to change
‣ Your co-workers will love you
‣ Make gems, share code
OUR REUSABLE RUBY
          TOOLBOX*
            * this list is not everything, okay




‣ Inheritance
‣ Modules
‣ Composition
‣ Gems & Bundler
STAY      CLASSY RUBY

‣ Class inheritance
‣ ActiveRecord, ActionController,
  etc.
‣ Meant for things that can be
  instantiated or inherited
‣ Sorry, NO Interfaces or Abstract
  Classes in Ruby   (sad face?)
WE ♥ MODULES
‣ Namespaces
‣ Inject a bucket o’ instance methods into
  something else
‣ The included callback
‣ ActiveSupport::Concern
‣ Testing
‣ Pitfalls
MODULES: NAMESPACES


 # Namespaces
 module Foo
  class Widget
   # ... one thing ...
  end
 end

 module Bar
  class Widget
   # ... something else ...
  end
 end

 x = Foo::Widget.new
 y = Bar::Widget.new
MODULES: METHOD INJECTION

module Sluggable

 def generate_slug(str)
  str.to_s.strip.downcase.gsub(/[^a-z0-9s]/, '').gsub(/s+/, '-')
 end

end

class Post < ActiveRecord::Base
 include Sluggable

 before_validation :update_slug

 private

 def update_slug
  self.slug = generate_slug(title)
 end

end

# Note (1): this is a super naive solution
# Note (2): check out the stringex gem for things like String#to_url.
MODULES: THE BASICS

Let’s make generate_slug available to all AR models

Put this code into a file like lib/sluggable.rb ...

         module Sluggable
          # ...
         end

         ActiveRecord::Base.send :include, Sluggable



Somewhere, like in config/initializers ...

         require 'sluggable'
MODULES: COMMON IDIOMS

 module Sluggable
  module ClassMethods
   def find_by_slug(str)
    # ...
   end
  end

  module InstanceMethods
   def generate_slug(str)
    # ...
   end

   private

   def update_slug
    # ...
   end
  end

  def self.included(base)
   base.extend          ClassMethods
   base.send :include,    InstanceMethods
   base.before_validation :update_slug
  end
 end
ACTIVESUPPORT::CONCERN
                              Bring THE PRETTY
                       require 'active_support/concern'

                       module Sluggable
                        extend ActiveSupport::Concern

                         included do
                           before_validation :update_slug
                         end

                         module ClassMethods
                          def find_by_slug(str)
                           # ...
                          end
                         end

                         private

                        def update_slug
                         # ...
                        end
                       end


https://github.com/rails/rails/blob/master/activesupport/lib/active_support/concern.rb
ACTIVESUPPORT::CONCERN
               What about module dependencies?
           module Foo
            def self.included(base)
             base.class_eval do
              def self.method_injected_by_foo
                # ...
              end
             end
            end
           end

           module Bar
            def self.included(base)
             base.method_injected_by_foo
            end
           end

           class Host
            include Foo # We need to include this dependency for Bar
            include Bar # Bar is the module that Host really needs
           end



https://github.com/rails/rails/blob/master/activesupport/lib/active_support/concern.rb
ACTIVESUPPORT::CONCERN
       require 'active_support/concern'

       module Foo
        extend ActiveSupport::Concern
        included do
          class_eval do
           def self.method_injected_by_foo
             # ...
           end
          end
        end
       end

       module Bar
        extend ActiveSupport::Concern
        include Foo

        included do
          self.method_injected_by_foo
        end
       end

       class Host
        include Bar # works, Bar takes care now of its dependencies
       end



https://github.com/rails/rails/blob/master/activesupport/lib/active_support/concern.rb
MODULES: TESTING
Test just the module, stub aggressively, use a Dummy class
         # sluggable_test.rb
         require 'spec_helper'

         class Dummy
          include ActiveModel::Validations
          extend ActiveModel::Callbacks
          include Sluggable

          define_model_callbacks :validation

          attr_accessor :title, :slug

          def initialize(title)
           self.title = title
          end
         end

         describe Sluggable do
          let(:dummy) { Dummy.new('a special title') }

          it "should update the slug when validated" do
            dummy.valid?
            dummy.slug.should eq('a-special-title')
          end
         end
MODULES: THE                DARK SIDE

 Classes that use too many modules can
 become a handful.
 1. Which module defined which method?
 2. What methods are defined in the class?
 3. Method name collisions when including
    modules
 4. You can’t reference a modularized entity, you
    must pass around the object that contains it.
MODULES: THE                DARK SIDE

‣ Over-engineering / pre-awesome-ization
‣ Which module defined which method?
‣ What methods are defined in the class?
‣ Method name collisions when including
  modules
‣ You can’t reference a modularized entity, you
  must pass around the object that contains it.
COMPOSITION:
WRANGLE THOSE MODULES



‣ A.K.A. aggregation
‣ A class is composed of several other
  classes, use delegation
Example: Composition

 class Avatar                                     class Person < ActiveRecord::Base
   attr_reader :master                              # ...

   def initialize(master)                           def avatar
     @master = master                                 @avatar ||= Avatar.new(self)
   end                                              end

   def exists?                                      def avatar_exists?
     master.has_avatar?                               avatar.exists?
   end                                              end

   def create(file)                               end
     master.update_attribute :has_avatar, true
     # thumbnail and install the given avatar
   end

   def destroy
     master.update_attribute :has_avatar, false
     # delete avatar file
   end

   # etc.
 end




http://37signals.com/svn/posts/1553-models-vs-modules
GEMS             & BUNDLER

‣ NO FEAR, make private gems to share
  code between projects
‣ Use Bundler to bootstrap gem creation
  https://github.com/radar/guides/blob/master/
  gem-development.md

‣ Use :git in your Gemfile to use gems
  directly from git repositories
  http://gembundler.com/git.html
THE END
RESOURCES

‣   http://www.fakingfantastic.com/2010/09/20/concerning-yourself-with-active-
    support-concern/

‣   http://yehudakatz.com/2009/11/12/better-ruby-idioms/

‣   http://metabates.com/2011/02/07/building-interfaces-and-abstract-classes-in-ruby/

‣   http://37signals.com/svn/posts/1553-models-vs-modules

‣   http://andrzejonsoftware.blogspot.com/2011/01/code-reuse-in-ruby-why-
    composition-is.html

‣   https://github.com/rails/rails/blob/master/activesupport/lib/active_support/
    concern.rb

‣   https://github.com/rails/rails/tree/master/activemodel

Reusable Ruby • Rt 9 Ruby Group • Jun 2012

  • 1.
    REUSABLE RUBY ROUTE 9 RUBY GROUP – JUNE 20, 2012 BLAKE CARLSON BLAKE@COIN-OPERATED.NET • @SKINANDBONES GITHUB.COM/SKINANDBONES • LINKEDIN.COM/IN/BLAKECARLSON
  • 2.
    WHY GO REUSABLE? sloth love reusable ruby ‣ D-R-Y ‣ Maintainability ‣ Leads to better solutions ‣ Make large projects adaptable to change ‣ Your co-workers will love you ‣ Make gems, share code
  • 3.
    OUR REUSABLE RUBY TOOLBOX* * this list is not everything, okay ‣ Inheritance ‣ Modules ‣ Composition ‣ Gems & Bundler
  • 4.
    STAY CLASSY RUBY ‣ Class inheritance ‣ ActiveRecord, ActionController, etc. ‣ Meant for things that can be instantiated or inherited ‣ Sorry, NO Interfaces or Abstract Classes in Ruby (sad face?)
  • 5.
    WE ♥ MODULES ‣Namespaces ‣ Inject a bucket o’ instance methods into something else ‣ The included callback ‣ ActiveSupport::Concern ‣ Testing ‣ Pitfalls
  • 6.
    MODULES: NAMESPACES #Namespaces module Foo class Widget # ... one thing ... end end module Bar class Widget # ... something else ... end end x = Foo::Widget.new y = Bar::Widget.new
  • 7.
    MODULES: METHOD INJECTION moduleSluggable def generate_slug(str) str.to_s.strip.downcase.gsub(/[^a-z0-9s]/, '').gsub(/s+/, '-') end end class Post < ActiveRecord::Base include Sluggable before_validation :update_slug private def update_slug self.slug = generate_slug(title) end end # Note (1): this is a super naive solution # Note (2): check out the stringex gem for things like String#to_url.
  • 8.
    MODULES: THE BASICS Let’smake generate_slug available to all AR models Put this code into a file like lib/sluggable.rb ... module Sluggable # ... end ActiveRecord::Base.send :include, Sluggable Somewhere, like in config/initializers ... require 'sluggable'
  • 9.
    MODULES: COMMON IDIOMS module Sluggable module ClassMethods def find_by_slug(str) # ... end end module InstanceMethods def generate_slug(str) # ... end private def update_slug # ... end end def self.included(base) base.extend ClassMethods base.send :include, InstanceMethods base.before_validation :update_slug end end
  • 10.
    ACTIVESUPPORT::CONCERN Bring THE PRETTY require 'active_support/concern' module Sluggable extend ActiveSupport::Concern included do before_validation :update_slug end module ClassMethods def find_by_slug(str) # ... end end private def update_slug # ... end end https://github.com/rails/rails/blob/master/activesupport/lib/active_support/concern.rb
  • 11.
    ACTIVESUPPORT::CONCERN What about module dependencies? module Foo def self.included(base) base.class_eval do def self.method_injected_by_foo # ... end end end end module Bar def self.included(base) base.method_injected_by_foo end end class Host include Foo # We need to include this dependency for Bar include Bar # Bar is the module that Host really needs end https://github.com/rails/rails/blob/master/activesupport/lib/active_support/concern.rb
  • 12.
    ACTIVESUPPORT::CONCERN require 'active_support/concern' module Foo extend ActiveSupport::Concern included do class_eval do def self.method_injected_by_foo # ... end end end end module Bar extend ActiveSupport::Concern include Foo included do self.method_injected_by_foo end end class Host include Bar # works, Bar takes care now of its dependencies end https://github.com/rails/rails/blob/master/activesupport/lib/active_support/concern.rb
  • 13.
    MODULES: TESTING Test justthe module, stub aggressively, use a Dummy class # sluggable_test.rb require 'spec_helper' class Dummy include ActiveModel::Validations extend ActiveModel::Callbacks include Sluggable define_model_callbacks :validation attr_accessor :title, :slug def initialize(title) self.title = title end end describe Sluggable do let(:dummy) { Dummy.new('a special title') } it "should update the slug when validated" do dummy.valid? dummy.slug.should eq('a-special-title') end end
  • 14.
    MODULES: THE DARK SIDE Classes that use too many modules can become a handful. 1. Which module defined which method? 2. What methods are defined in the class? 3. Method name collisions when including modules 4. You can’t reference a modularized entity, you must pass around the object that contains it.
  • 15.
    MODULES: THE DARK SIDE ‣ Over-engineering / pre-awesome-ization ‣ Which module defined which method? ‣ What methods are defined in the class? ‣ Method name collisions when including modules ‣ You can’t reference a modularized entity, you must pass around the object that contains it.
  • 16.
    COMPOSITION: WRANGLE THOSE MODULES ‣A.K.A. aggregation ‣ A class is composed of several other classes, use delegation
  • 17.
    Example: Composition classAvatar class Person < ActiveRecord::Base attr_reader :master # ... def initialize(master) def avatar @master = master @avatar ||= Avatar.new(self) end end def exists? def avatar_exists? master.has_avatar? avatar.exists? end end def create(file) end master.update_attribute :has_avatar, true # thumbnail and install the given avatar end def destroy master.update_attribute :has_avatar, false # delete avatar file end # etc. end http://37signals.com/svn/posts/1553-models-vs-modules
  • 18.
    GEMS & BUNDLER ‣ NO FEAR, make private gems to share code between projects ‣ Use Bundler to bootstrap gem creation https://github.com/radar/guides/blob/master/ gem-development.md ‣ Use :git in your Gemfile to use gems directly from git repositories http://gembundler.com/git.html
  • 19.
  • 20.
    RESOURCES ‣ http://www.fakingfantastic.com/2010/09/20/concerning-yourself-with-active- support-concern/ ‣ http://yehudakatz.com/2009/11/12/better-ruby-idioms/ ‣ http://metabates.com/2011/02/07/building-interfaces-and-abstract-classes-in-ruby/ ‣ http://37signals.com/svn/posts/1553-models-vs-modules ‣ http://andrzejonsoftware.blogspot.com/2011/01/code-reuse-in-ruby-why- composition-is.html ‣ https://github.com/rails/rails/blob/master/activesupport/lib/active_support/ concern.rb ‣ https://github.com/rails/rails/tree/master/activemodel