Skip to content

Instantly share code, notes, and snippets.

@jmondo
Last active December 26, 2015 16:09
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jmondo/7177323 to your computer and use it in GitHub Desktop.
Save jmondo/7177323 to your computer and use it in GitHub Desktop.
simple delegator example from sfrails oct 2013
# it's all part of ruby!
require 'ostruct'
require 'delegate'
require 'forwardable'
require 'minitest/autorun' # can run rspec style tests with this, part of ruby
# ---------------------------------------------------------------------------------------------------------
# the classes
# using simple delegator with the country object /after/ this app has been made with bad dependency mgmt.
# we swap the country string 'FIN' out for a country object with Country.new('FIN')
# the parts of the app that expect a string still get a string and the parts that want a first-class object
# with its own methods and logic get what they want too.
class Account
extend Forwardable
attr_accessor :country, :name
def initialize(options)
@country = options[:country]
@name = options[:name]
end
def country
Country.new(@country) #=> 'FIN'
end
# works just like `delegate to:` in rails (with forwardable included)
def_delegators :country, :in_europe?, :in_united_states?, :country_name
end
class Country < SimpleDelegator
COUNTRIES = {
'FIN' => 'Finland'
}
def in_europe?
self == 'FIN'
end
def in_united_states?
self == 'USA'
end
def country_name
COUNTRIES[self]
end
end
class BillingInfo
attr_accessor :account
def initialize(options)
@account = options[:account]
end
# the super awesome OO app has tons of these
def gets_10_percent_off?
account.country == 'FIN'
end
end
# -----------------------------------------------------------------------
# the tests (just run ruby simple_delegator_example.rb and they will run)
describe Account do
let(:account) { Account.new(name: 'Fincorp inc, intl.', country: 'FIN') }
it "has a country code" do
assert_equal 'FIN', account.country
end
it "is in europe" do
assert account.in_europe?
end
it "is finland" do
assert_equal 'Finland', account.country_name
end
end
describe BillingInfo do
let(:account) { Account.new(name: 'Finnish co, inc, intl', country: 'FIN') }
let(:billing_info) { BillingInfo.new(account: account)}
it "gives 10% off for being in finland" do
assert billing_info.gets_10_percent_off?
end
it "gives full price for USA customer" do
account.country = 'USA'
refute billing_info.gets_10_percent_off?
end
end
# --------------------------------------------------------------------------------------------------------
# Can also be used to futureproof your code. Take basic objects like integers and wrap them in a Class.new
# so that down the line, you can add methods to the class. For now, they just act like their integer value.
class Money < SimpleDelegator
# add this method down the line
def in_dollars
self / 100.0
end
end
price = Money.new(100_00)
p price.in_dollars #=> 100.00
@sockmonk
Copy link

I really like the example of using this to improve a bad design and letting different parts of the app get a bare string or full object behavior. Makes it much easier and less risky to improve the code gradually.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment