Reading, Writing, Writing with a Reader, and maybe some Tests in Ruby

I wanted to explore some basic behavior in Ruby that seemed strange to me and from there maybe come up with a use-case where this behavior could be taken advantage of and applied. I first started thinking about this behavior in labs where an object has an instance variable that is initialized to an array. I’ll give an example with a different domain, but the idea is the same:

class PokemonTrainer
  attr_reader :pokemon
  def initialize
@pokemon = []
end
  def catch_pokemon(pokemon)
@pokemon << pokemon
end
end

Methods are the public interface for a class. Since I get to define their behavior, I want a new pokemon trainer to have to catch a pokemon using the #catch_pokemon method which stores a pokemon in the @pokemon instance variable array. The class only provides an attr_reader for the instance variable because I want to be able to read back a trainer’s pokemon but do not want a user to write to that variable outside of the interface of #catch_pokemon. I don’t want or provide a #pokemon= method. But then it turns out I can do something like this:

way to go jeff

Without ever calling #catch_pokemon I was able to add squirtle to the list of Trainer Jeff’s pokemon. Is this just something weird with arrays and the shovel << method? Let’s try something else, I’ll make another class that specifically only has an attr_reader for some instance variable and not an attr_writer, which in theory will stop a user from writing to that variable.

class DogAlwaysNamedTicTac
  attr_reader :name
  def initialize
@name = "Tic Tac"
end
end

Great, a really useful class that produces dog objects with a name attribute that is initialized to “Tic Tac” and doesn’t provide any methods for updating the name. Alright, so lets play around in IRB…

all is right with the world so far

… and it all makes sense; tictac remains Tic Tac. But then I’m able to do something like this:

:o

Uh oh.

I can even force my way in there and change the name:

?

Tic Tac is now named “Popsicle” and I was able to write that change without a name= method. Weird. This seemed really puzzling and also felt like a big hack. Ruby’s flexiblity or casual-ness seemed maybe too casual, and I felt like I arrived at some weird behavior.

From here I want to branch out and cover 3 things:

- What’s happening/how to change this this behavior if so desired

- Realize/explore that this ‘hack’ is actually built in to Ruby

- Maybe try to come up with a scenario where you could use this behavior to your advantage.

What’s going on? / is there any other way?:

When method calls (i.e. clear, [0]=, shovel) were chained on to the return value of the attr_reader method they altered that value permanently or destructively. This means that the empty string returned from dog.name.clear (=> “”) must be the same String object as the initial call to dog.name (=> “Tic Tac”) returned.

they’re the same

The #name method returns the actual object stored in the instance variable @name. It’s like there’s an implicit ‘!’ on the method. Remember that this method gets defined by the attr_reader macro which just writes a boilerplate method definition for us that looks like this:

def name
@name
end

If instead #name returned a copy of the object, then a user could do anything they wanted to the copy, which would have the same value as the original object, but the object stored at the instance variable would remain unchanged. In ruby you can copy an object using #dup.

def name
@name.dup
end

To put it all together, let’s replace the attr_reader macro with our own definition of the #name method.

class DogAlwaysNamedTicTac
  def name
@name.dup
end
  def initialize
@name = "Tic Tac"
end
end

and test it in IRB…

cool

Syntactic Vinegar -or- ‘a feature not a bug’:

To ‘hack’ into tictac’s @name attribute we needn’t even have to go to the trouble of tictac.name.clear[0]= ‘whateverrr’ . Ruby makes it easy for us:

#instance_variable_set and the reciprocal #instance_variable_get are instance methods of Ruby’s basic Object class that all your classes inherit from. They have an intentionally clunky and awkward syntax because they violate the principle of encapsulation. Even the official Ruby documentation calls them frustrating.

The obvious questions being Why? and When?

A Use Case?

So the answer is I don’t know.

Cursory research into why these methods exist brought unclear results. One probably good example was in a kind of meta-programming way where you could dynamically generate the instance variables of an instance. With my more limited experience, the only example I could come up with where this functionality would be helpful and useful was in writing Rspec tests for interdependent methods. And even here stack overflow says maybe this is not the best practice but then in the same page someone says its ok and/or is ambivalent.

The example:

Here in the “trigger” phase in my test for the #genres query-type method I am forced to call the #add_genre command-type method to populate an instance of a Movie. This could be problematic if #add_genres was buggy or non-functional. I would have no way to isolate #genres and test if it was doing what was expected. I could refactor to something like:

instance_variable_set in action, use caution

Now #genres could be tested in isolation without calling the command-type method #add_genres. Thanks Ruby!