Delegation is a very useful software pattern and this post focusses on how this pattern can be applied in the context of a Rails application and the associated advantages of doing so.

If you are unfamiliar with this pattern, Wikipedia has a very good explanation:

In software engineering, the delegation pattern is a design pattern in object-oriented programming where an object, instead of performing one of its stated tasks, delegates that task to an associated helper object.

There is an Inversion of Responsibility in which a helper object, known as a delegate, is given the responsibility to execute a task for the delegator.

Also, this Stackoverflow post has some interesting posts elaborating on the practical benefits of the concepts of delegation in a language neutral context.

Ruby's metaprogramming facilities enable us to implement delegation in a much easier and consise manner than many other object oriented languages.

Using dynamic interception of method calls, it is very straightforward to forward method invocations to a target object. This is very well illustrated by the implementation of Delegate class provided by Ruby standard library:

classDelegator<BasicObject#...## Handles the magic of delegation through \_\_getobj\_\_.#defmethod_missing(m,*args,&block)r=truetarget=self.__getobj__{r=false}ifr&&target.respond_to?(m)target.__send__(m,*args,&block)elsif::Kernel.respond_to?(m,true)::Kernel.instance_method(m).bind(self).(*args,&block)elsesuper(m,*args,&block)endendend

This is arguably the most common use case for delegators. Often we end up adding a lot of methods in our models that have nothing to do with domain logic whatsoever. Does something like this look familiar?

Such methods which are primarily written for handling presentation concerns come into the perview of Presenter logic and are best left out of models. Instead we can decorate our model instances using presenter classes before passing them to views:

classUserDecorator<SimpleDelegatordefformatted_date_of_birthdate_of_birth.strftime("Born on %m/%d/%Y")endend

I find this to be more elegant from an object oriented perspective compared to the conventional approach using Rails helper modules.

There are many libraries for implementing additional convenience utilities around presenters. While the above simple approach takes us quite far, if you find yourself repeating the decorator instantiation boilerplate, generators and conventions offered by a library like Draper can be helpful.

Note that one of the biggest strengths of decorators is on demand composability. For instance we may have various user centric helper methods for reporting. We can extract them into a UserReportPresenter decorator that is used only in the UserReportsController. Accordingly we can have multiple use-case specific delegators layered one upon another, each delegating to its immediate target transparently.

We may want to have some approach to restrict what can be decorated by a decorator. We may be tempted to define something like this:

classUserReportPresenter<SimpleDecoratordefinitialize(decorated)unlessdecorated.is_a?UserraiseArgumentError.new("Expected entity being decorated to be User")endsuperendend

However this is not something we would want to do as it breaks composability. One approach would be to "unwrap" the decorated before instance check:

whiledecorated.is_a?Decoratordecorated=decorated.__getobj__endunlessdecorated.is_a?UserraiseArgumentError.new("Expected entity being decorated to be User")end

but I strongly recommend not resorting to is_a? checks at all and relying instead on behavior checks using responds_to?.

However one thing that we should keep in mind that SimpleDelegator allows the decorated target to be run time configurable. While this is not a problem in the above implementation as we use an entity specific key, this may become a problem if, for example, we were using transparent memoization through Memoist:

The above implementation is broken because even if we were to change the decorated instance using __setobj__ our memoized method would continue returning the output of invocation of the method on previously decorated instance.

A simple solution for the above is to flush the cache when the decorated entity changes:

This is something I recently found to be useful. Sometimes when we need to define operations that make sense for a set of instances, we just resort to class methods in model. However a better object oriented design would be to implement such behaviors on a dedicated collection resource.

Decorating ActiveRecord::CollectionProxy is helpful because we get facilities like scope chaining, lazy-loading etc. for free.

classEmployeesReportDelegator<SimpleDelegatordefmonth_wise_performance_statsjoins(:assessments).group('MONTH(assessments.created_at)').select('assessments.evaluation_rank as rank')endend

This is occassionaly useful especially when dealing with third party SDKs. We can leverage ActiveSupport::Callbacks to wrap custom callback hooks around specific behaviors of decorated objects:

classMailDispatchDelegator<SimpleDelegatorincludeActiveSupport::Callbacksdefine_callbacks:dispatchdefdispatchrun_callbacks:dispatchdosuperendendendclassMailFilterDelegator<MailDispatchDelegatorset_callback:dispatch,:beforedo|object|ifvalidated_domains.include?object.email.domainraiseValidationError.new("Domain not whitelisted")endendend

While creating a dedicated Delegator makes sense in a variety of use cases, often we want to just delegate just a few methods to a contained object. ActiveSupport Module extensions provide a convenient approach to delegate specific methods to any contained object. This comes in very handy in controllers:

Since the official endorsement of concerns from Rails 4 concerns have soared in popularity, however I have often observend that concerns are overused, especially for use cases that are better handled by other patterns.

Since this post is about delegation, let us look at a few things that delegation has to offer over concerns:

While it is true that concerns can be injected at run time, however code that runs in the included hooks potentially modifies the host instance making run time switching of concerns not very practical in most cases. Delegation offers a clearer approach and we have already demonstrated that switching delegated instances in delegator instances is quite useful.

This is particularly useful for objects that did not originate in our code. Rather than polluting the library generated objects with application specific behavior, which may cause subtle unintended side-effects, it is much more elegant to pass around decorated instances in application code and pass the unwrapped instances back to the library should there be a need to do so.

All in all concnerns are more suitable for application specific classes where core functionality is shared among multiple classes, and delegation is more useful for implementing auxiliary use case specific behaviors or transparently augmenting existing behavior.

A good example of former use case would be a cross-cutting functionality like using a completed_at column for scoping on completion status or checking if a model (eg. Payment, Project etc.) has been completed :

moduleCompletionSupportextendActiveSupport::Concernincludeddo%i[completecompleted].eachdo|name|scopename,->{where'completed_at is not null'}endscope:incomplete,->{wherecompleted_at:nil}enddefcomplete!self.completed_at=DateTime.nowsave!enddefcomplete?completed_at.present?endalias_method:completed?,:complete?defincomplete?!complete?endalias_method:not_completed?,:incomplete?end

Last but not the least, delegation makes sense in command line applications as well. An excellent example would be github's hub command line utility that makes a lot of github features like pull-requests accessible from the command line, while delegating everything else to git.

While observer pattern can help towards decoupling and the advantags therein overlap with those offered by delegates in some cases, in general I refrain from using Observer pattern because it makes the flow of logic harder to trace - especially in larger applications.

Refinements are a recent addition to the Ruby language where in we can selectively monkey-patch modules (and hence classes) with custom behavior. But a key aspect of refinements is lexical scoping. The offical docs explain this very well:

Refinements are lexical in scope. When control is transferred outside the scope the refinement is deactivated. This means that if you require or load a file or call a method that is defined outside the current scope the refinement will be deactivated:

While this explicit aspect of Refinements is a commendable improvement over adhoc monkey-patching in many scenarios - the convenience offered by transparent overlaying of behavior that decorators offer us, is, in most practical cases, more appealing.

The primary exception to the above would be cases where the code consuming the augmented instances rely on instance_of? checks to determine the identity of passed instance. Using refinements we are not changing the class of the instance, where as while passing the wrapped instance we fail on the instance_of? checks as the wrapped instances are actually instances of a different class though they implement interchangeable behavior.

This concludes our post on delegation. As always any insights on pragmatic usage of this pattern is more than welcome. Please use the comments section to share any feedback or criticism.