views:

31

answers:

1

I have some Ruby code that uses protected level attr_readers to be able to implement an == method so that we can assert some resultant Calendar is equal to an expected Calendar), but those protected accessors would not be needed for any code besides assertions in test code.

Some code I have looks like this:

class Calendar    
  def initialize(date_times)
    @date_times = date_times
  end

  def next_date_time
    @date_times.find { |time| time - DateTime.now > 0 }
  end

  def ==(other)
    @date_times == other.date_times
  end

  protected

  attr_reader :date_times
end

How can I avoid the need for protected attr_readers? Actually, ideally it would be great if I could remove the need for the def==(...... as well, since it is also only needed for tests!

All the Best, Alex

A First Stab At This:

module Kernel
  def subclass_with_equals(class_symbol)
    clazz = Kernel.const_get(class_symbol.to_s)
    Kernel.subclass_with_default_equals(clazz)
  end

  private

  def self.subclass_with_default_equals(base_class)
    sub_class = Class.new(base_class)
    sub_class.class_eval do
      def ==(other)
        instance_variables.all? { |v| self.instance_variable_get(v) == other.instance_variable_get(v) } and other.instance_of? self.class
      end
    end
    sub_class
  end
end

And at the top of my spec file I can add this line to add a default == method to my class:

Calendar = sub_class_with_equals :Calendar

This enables me to remove five or six lines from my Calendar class!

+2  A: 

You could monkey-patch the class from within the test code, and at that point you may as well get rid of the #protected() call.

Even better might be to, from within the test code, create a subclass of Calendar that has an accessor and == method.

DigitalRoss
Thanks @DigitalRoss, I think I will look to create a general purpose monkey patch that adds an == method to a class, that assumes all fields are equal, so I don't have to rewrite it for each file.
Alex Baranosky