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
#instance_of? to introspect on Model classes when generating forms and URL helpers.
These methods are not forwarded when using
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:
And then install:
Or install it yourself:
$ gem install dumb_delegator
This project adheres to Semantic Versioning.
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.7 when it was released.
However, most older Rubies, should still work.
Except for Ruby 1.9, which probably does not work with
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
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
Rails Model Decorator
There are many decorator implementations in Ruby.
One of the simplest is "
__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
We've also observed the need to redefine other Rails-y methods to get various bits of 🧙 Rails Magic 🧙 to work as expected.
DumbDelegator, there's not a need for redefining these things because nearly every possible method is delegated.
case statement support
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
In such usage, it's the Class/Module's
::=== method that gets called, rather than the
#=== method on the
That means we need to override each Class/Module's
::=== method, or even monkey-patch
DumbDelegator ships with an optional extension to override a Class/Module's
But you need to extend each Class/Module you use in a
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! 🐉
- 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