A few days ago I released the
disposable gem minor line 0.6.0. The goal was to remove unnecessary coupling to the old
uber gem that was once planned to hold a set of abstractions we need in all Trailblazer gems.
What I mainly worked on was removing
Uber::Delegate and supersede it with Ruby's built-in
Forwardable. We simply need to delegate a bunch of methods from one object to another, and
Forwardable does just that.
So what the new code basically did was to extend the
Disposable::Twin class and use
def_delegators to create automatic delegations.
class Disposable::Twin extend Forwardable # this used to be Uber::Delegate def_delegators :model, :title, :title= def_delegators :model, :email, :email=
Obviously, removing gem dependencies, even if they're your own, feels good, so I pushed this new gem after running tests in two projects and leaned back in my pricey office desk.
ArgumentError: wrong number of arguments (given 2, expected 1) # ruby/3.0.0/forwardable.rb:133:in `instance_delegate'
The developers reported that before the upgrade to
Disposable 0.6.0, code as the following worked fine.
class CreateForm < Reform::Form delegate :assignment, to: :model end
After the upgrade, this would break with the
ArgumentError illustrated above. My first suspicion was that this is some code not ready for Ruby 3, as this kind of exception is often observed in older code that runs with Ruby 3.
An hours going through the
Forwardable code and playing with the broken example app provided by some nice user didn't bring any progress.
At some point I realized that what the users were actually using to create delegations was the
delegate method not from
Forwardable but from
ActiveSupport::Delegate. The docs enlightened me that this module mixes a method
#delegate into the class - the method that was used by all users reporting the error.
It feels really stupid in hindsight, and it took at least two hours to understand the problem.
Here's the chain of sparks that lead to fixing the problem.
Reform::Formclass inherits from
Twin::Disposable. This means that each
Forwardablemethods mixed in. Of course, only after upgrading
disposableto 0.6.0 where we automatically included
- Before 0.6.0, the former delegation from
Uber::Delegateonly mixed in one method called
Forwardablemodule includes a method called
- Our nasty
ActiveSupport::Delegatemodule also has a method called
#delegate- just like
- In some Rails apps, delegation from ActiveSupport is automatically included into all classes.
The problem we were facing was that ActiveSupport and its delegation code was included automatically into the
Reform::Form class before the
disposable gem included
Forwardable and hence the latter overriding the
#delegate method you are expecting.
In other words,
Forwardable killed ActiveSupport's
#delegate method as it was loaded and included later.
We decided to removed
disposable for now and fixed the obscure issue. When using
delegate in a form it now refers to ActiveSupport's implementation.