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
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.
8. 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'
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 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
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.
17. 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
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