Using Ruby Refinements in Rails

Its not very often I get the chance to use refinements in Rails. My past experiences with it in the context of Rails applications has been less than satisfactory due to the way the framework handles code reloading and the way refinements’ scopes work.

However, I feel strongly enough to use refinements in a recent project due to the use / abuse of ActiveSupport#try method.

Supposing we have an Order class that has a single attribute, date. Also assuming that we have a method which calls to_date on that attribute like so:

 1 class Order
 2   attr_accessor :date
 3 
 4   def confirmed_date
 5     @date.to_date
 6   end
 7 end
 8 
 9 o = Order.new
10 puts o.date.to_date

If the date attribute is nil, the above would throw an undefined method on NilClass error. A common pattern in Rails application is to wrap the above in a try method from ActiveSupport:

 1 require 'active_support'
 2 require 'active_support/core_ext/object/try'
 3 
 4 class Order
 5   attr_accessor :date
 6 
 7   def confirmed_date
 8     @date.try(:to_date)
 9   end
10 end

Using the try method will return nil and not throw an exception. Underneath the hood, ActiveSupport has overridden NilClass with a try method to only return nil, disregarding any arguments passed into it:

 1 # from lib/active_support/core_ext/object/try.rb
 2 
 3 class NilClass
 4   def try(*args)
 5     nil
 6   end
 7 
 8   def try!(*args)
 9     nil
10   end
11 end

This pattern is common in most Rails applications, and if you study the Rails source code, there are many examples given of this specific usage of try. But from a ruby perspective, this seems like a kind of anti-pattern to me. NilClass is just another object from the ruby ecosystem so why can’t we just define the missing to_date method on it in this case and not have to worry about calling another method from a dependency to deal with it? My immediate thought is to apply the NullObject pattern but I think it is overkill in this use case since there is only 1 method calling try.

Rather than overriding NilClass, my approach is to define to_date on it using Ruby refinements which I can use in the context I decide is applicable like so:

 1 module NilClassRefinements
 2   refine NilClass do
 3     def to_date
 4       nil
 5     end
 6   end
 7 end
 8 
 9 using NilClassRefinements
10 
11 class Order
12   attr_accessor :date
13 
14   def confirmed_date
15     @date.to_date
16   end
17 end
18 
19 o = Order.new
20 puts o.date.to_date.inspect

The above also returns nil if @date is nil and is easier to understand with the added benefit of not overriding the core data types in Ruby.

Stay curious and keep hacking!