Variable class idea and request for critique

Hi all,

I’m fairly new to Ruby but really enjoying it. I’ve been working on a
medium large project that is non rails but uses ActiveRecord and
assorted other pieces of the rails framework. I’d been finding myself
having more and more difficulty writing unit tests as I went because
many of my classes were calling class methods of other classes
internally–such as the models from rails–and these were hard to factor
out. For example:

class ImportantModel < ActiveRecord::Base
end

class Processor
def self.do_hard_processing(something)
#…
end
end

class SomeClass

def a_method
         #...
         something = ImportantModel.find_by_a_lot_of_stuff(one, two)
         processed = Processor.do_hard_processing(something)
         if _the_time_is_right?(processed)
            #...etc
    end

end

If those had been objects I could have swapped mocks in for them when I
wanted to test SomeClass, but since they’re classes–constants by
definition–I couldn’t! I know about fixtures to help with this
problem, but sometimes (as in the case of the Processor class) they
aren’t really possible, and sometimes you really don’t want to involve
the working of a model…you just want to test SomeClass’s logic with
some hard coded mock objects.

One solution is pretty simple:

class SomeClass

attr_accessor :important_model
    attr_accessor :processor

def initialize
   @important_model = ImportantModel
       @processor = Processor
    end

def a_method
         something =
	important_model.find_by_a_lot_of_stuff(one, two)
         processed = processor.do_hard_processing(something)
         if _the_time_is_right?(processed)
            #...etc
    end

end

This is an improvement, because now in a testing environment, for
instance, you could set

sc = SomeClass.new
sc.important_model = HardCodedModel
#or even
sc.imporant_model = mockObjectOfSomeSort
#since there’s nothing says it has to be a class

But there were two things I didn’t like: one, the repetetive
nature–you’re writing some variation of important_model 3 times, for
each class you want to make variable Second of all, I wanted to make it
easy to decide on the class/object returned conditionally or
dynamically, with a block, which can save you a lot of time in testing
code (and could be useful in production, for that matter).

The solution I came up with was to add a class method called
variable_class to Object, which would provide an indirect way to refer
to classes like ImportantModel or Processor (or even allow them to be
not classes at all but arbitrary objects), and which had support for a
block.

require ‘rubygems’
require ‘active_support/inflector’

class Object

def self.variable_class(klass, accessor_name = nil, &block)
klass_str = klass.name
klass_sym = klass_str.to_sym
klass_translated_str =
“#{Inflector.underscore(klass_str.tr_s(’::’,’_’))}_class”
instance_var_sym = “@#{klass_translated_str}”.to_sym

get_method_sym = accessor_name ? accessor_name.to_sym :
                                 klass_translated_str.to_sym
set_method_sym = "#{get_method_sym.to_s}=".to_sym

if block_given?

  define_method(get_method_sym) do
    block.call(self)
  end

else

  define_method(get_method_sym) do
    instance_variable_get(instance_var_sym) ||

instance_variable_set(instance_var_sym,

klass)
end

  define_method(set_method_sym) do |custom_class|
    instance_variable_set(instance_var_sym, custom_class)
  end

end

end

Basically, given two classes (here I’m just going to make them classes
and not note the object possibility anymore)

class One
def self.do
puts “one”
end
end

class Two
def self.do
puts “two”
end
end

All the following will work:

class SimplestCase
variable_class One

def test
one_class.do
end
end

sc = SimplestCase.new
sc.test #“one”
sc.one_class = Two
sc.test #“two”

The default is to make the class variable settable/gettable with the
underscored name of the class plus _class. Any :: operators get turned
into underscores as well, so Mod::KlassName would become
mod_klass_name_class by default.

If you need a shorter/different name, you can pass it as a symbol in the
second argument slot.

class AlmostAsSimple
variable_class One, :my_own_name

def test
my_own_name.do
end
end

sc = AlmostAsSimple.new
sc.test #“one”
sc.my_own_name = Two
sc.test #“two”

If you want to use a block, you can do it like so:

class BlockExample
@@use_two = false

    def self.go_two
       @@use_two = true
    end

variable_class One do
       @@use_two ? Two : One
    end

    def test
      one_class.do
    end

end

be = BlockExample.new
be.test #“one”
BlockExample.go_two
be.test #“two”

And if you want to use instance rather than class methods / variables in
the block, self is passed along for you.

class BlockExample2

def initialize
	@use_two = false
end

    def go_two
       @use_two = true
    end

def use_two?
          @use_two
end

variable_class One do |be2obj|
       be2obj.use_two? ? Two : One
    end

    def test
      one_class.do
    end

end

be2 = BlockExample2.new
be2.test #“one”
be2.go_two
be2.test #“two”

Using this approach, I’ve been able to actually test the UNITS in my
unit tests, by being able to swap in mocks for classes as well as
objects and isolating the behavior of the class I’m testing. But I
wanted to run it by the people on this fine list to see what suggestions
people had for it before I put it in too many spots. Is there a much
simpler way to accomplish the same? Or something that could be added
now to make it more flexible? Or a way to tighten up the code? I want
it to be as Ruby-like as possible.

Thanks for any critiques or suggestions.

Edmund

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs