Ruby provides the delegate
standard library.
However, we found that it is not appropriate for cases that require nearly every call to be proxied.
For instance, Rails uses #class
and #instance_of?
to introspect on Model classes when generating forms and URL helpers.
These methods are not forwarded when using Delegator
or SimpleDelegator
.
require "delegate"
class MyAwesomeClass
# ...
end
o = MyAwesomeClass.new
d = SimpleDelegator.new(o)
d.class #=> SimpleDelegator
d.is_a? MyAwesomeClass #=> false
DumbDelegator
, on the other hand, forwards almost ALL THE THINGS:
require "dumb_delegator"
class MyAwesomeClass
# ...
end
o = MyAwesomeClass.new
d = DumbDelegator.new(o)
d.class #=> MyAwesomeClass
d.is_a? MyAwesomeClass #=> true
Add this line to your Gemfile:
gem "dumb_delegator"
And then install:
$ bundle
Or install it yourself:
$ gem install dumb_delegator
This project adheres to Semantic Versioning.
The 0.8.0
release was downloaded 1.2MM times before the 1.0.0
work began.
Which is great! 🎉
But, we wanted to clean up some cruft, fix a few small things, and improve ergonomics.
And we wanted to do all of that while, hopefully, not breaking existing usage.
To that end, 1.0.0
dropped support for all EoL'd Rubies and only officially supported Ruby 2.4
- 2.7
when it was released.
However, most older Rubies, should still work.
Maybe… Shmaybe?
Except for Ruby 1.9, which probably does not work with DumbDelegator
> 1.0.0
.
If you're on an EoL'd Ruby, please try the 0.8.x
versions of this gem.
DumbDelegator
's API and usage patters were inspired by Ruby stdlib's SimpleDelegator
.
So the usage and ergonomics are quite similar.
require "dumb_delegator"
class Coffee
def cost
2
end
def origin
"Colombia"
end
end
class Milk < DumbDelegator
def cost
super + 0.4
end
end
class Sugar < DumbDelegator
def cost
super + 0.2
end
end
coffee = Coffee.new
cup_o_coffee = Sugar.new(Milk.new(coffee))
cup_o_coffee.origin #=> Colombia
cup_o_coffee.cost #=> 2.6
# Introspection
cup_o_coffee.class #=> Coffee
cup_o_coffee.__getobj__ #=> #<Coffee:0x00007fabed1d6910>
cup_o_coffee.inspect #=> "#<Sugar:70188197507600 obj: #<Milk:70188197507620 obj: #<Coffee:0x00007fabed1d6910>>>"
cup_o_coffee.is_a?(Coffee) #=> true
cup_o_coffee.is_a?(Milk) #=> true
cup_o_coffee.is_a?(Sugar) #=> true
There are many decorator implementations in Ruby.
One of the simplest is "SimpleDelegator
+ super
+ __getobj__
," but it has the drawback of confusing Rails.
It is necessary to redefine #class
, at a minimum.
If you're relying on Rails' URL Helpers with a delegated object, you also need to redefine #instance_of?
.
We've also observed the need to redefine other Rails-y methods to get various bits of 🧙 Rails Magic 🧙 to work as expected.
With DumbDelegator
, there's not a need for redefining these things because nearly every possible method is delegated.
Instances of DumbDelegator
will delegate #===
out of the box.
Meaning an instance can be used in a case
statement so long as the when
clauses rely on instance comparison.
For example, when using a case
with a regular expression, range, etc...
It's also common to use Class/Module in the where
clauses.
In such usage, it's the Class/Module's ::===
method that gets called, rather than the #===
method on the DumbDelegator
instance.
That means we need to override each Class/Module's ::===
method, or even monkey-patch ::Module::===
.
DumbDelegator
ships with an optional extension to override a Class/Module's ::===
method.
But you need to extend each Class/Module you use in a where
clause.
def try_a_case(thing)
case thing
when MyAwesomeClass
"thing is a MyAwesomeClass."
when DumbDelegator
"thing is a DumbDelegator."
else
"Bad. This is bad."
end
end
target = MyAwesomeClass.new
dummy = DumbDelegator.new(target)
try_a_case(dummy) #=> thing is a DumbDelegator.
MyAwesomeClass.extend(DumbDelegator::TripleEqualExt)
try_a_case(dummy) #=> thing is a MyAwesomeClass.
If necessary, you could also override the base Module::===
, though that's pretty invasive.
🐲 There be dragons! 🐉
::Module.extend(DumbDelegator::TripleEqualExt)
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Added some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request
- Ruby 1.8 support (use the
blankslate
gem?)