Class: Safer::IVar

Inherits:
Object
  • Object
show all
Defined in:
lib/safer/ivar.rb

Overview

Create accessor functions for instance variables, in which the accessor function is prefixed with the name of the class in which the instance variable is defined.

Usage

Create one or more instance variables using instance_variable . For example, the following code:

class Outer
  Safer::IVar.instance_variable(self, :variable)
  class Inner < Outer
    Safer::IVar.instance_variable(self, :variable)
  end
end
puts(Outer.instance_methods.grep(/__variable/).inspect)
puts(Outer::Inner.instance_methods.grep(/__variable/).inspect)

produces the following output:

["outer__variable", "outer__variable="]
["outer_inner__variable", "outer_inner__variable=", "outer__variable", "outer__variable="]

Accessors for Safer::IVar-defined instance variables can be created using export_reader, export_writer, and export_accessor .

Rationale

Safer::IVar is intended to improve the clarity and consistency of ruby code in at least the following (related) ways:

  1. Reducing the probability of instance variable usage errors.

  2. Documenting the instance variables attached to a class.

Error Reduction

Safer::IVar should help in reducing errors in at least the following ways:

  • Encapsulation errors. Traditional ruby instance variables defined by one class are transparently accessible to all subclasses. They are not, however, part of the public interface to a class (unless an accessor is defined), which means they are not generally documented. These two factors create a situation in which it is quite possible that a subclass inadvertantly re-define an instance variable in such a way as to render the subclass unusable. Bugs of this sort can be very difficult to resolve.

    Because instance variables generated by Safer::IVar are prefixed with a string derived from the class in which the variable is defined, it is much less likely that developers inadvertantly re-define instance variables in sub-classes, dramatically reducing the likelihood of this type of error.

  • Misspelling errors. For example, suppose you typically write software using the en-us dialect, and have an object with a @color instance variable. A en-uk speaker then submits a patch that sets the default color to green:

    --- ex1.rb	2010-09-28 06:24:52.000000000 -0400
    +++ ex2.rb	2010-09-28 06:25:00.000000000 -0400
    @@ -1,6 +1,7 @@
     class Foo
       def initialize
         @size = 3
    +    @colour = "green"
       end
       attr_reader :color
     end
    

    This code will not raise any exceptions, but its behavior does not match developer intent. On the other hand, using Safer::IVar

    --- ex1-safer.rb	2010-09-28 06:31:51.000000000 -0400
    +++ ex2-safer.rb	2010-09-28 06:32:08.000000000 -0400
    @@ -1,7 +1,8 @@
     class Foo
       Safer::IVar.instance_variable(self, :size, :color)
       Safer::IVar.export_reader(self, :color)
       def initialize
         self.foo__size = 3
    +    self.foo__colour = "green"
       end
     end
    

    The new code will raise an exception at the call to Foo.new, making it much less likely that the error will go undetected.

Documentation

Traditional ruby instance variables are defined and used in an ad hoc manner. As such, there is no natural location in which the instance variables defined by a class can be documented, and no obvious way to determine the set of instance variables used in a class. Safer::IVar instance variables will all be associated with a single call to Safer::IVar.instance_variable. This provides both a natural location for documenting an instance variable’s interpretation, as well as a string to search for when determining the set of instance variables defined by a class.

Class Method Summary collapse

Class Method Details

._symbol_names(klass, nmlist) ⇒ Object

Used internally.

klass

Class object for which to generate a symbol name.

nmlist

Array of Symbol objects.

return

Array of Symbol objects generated from klass and each element of nmlist.



115
116
117
118
119
120
121
# File 'lib/safer/ivar.rb', line 115

def self._symbol_names(klass, nmlist)
  kname = self.class_symbol_prefix(klass)

  nmlist.map do |nm|
    (kname + '__' + nm.to_s).to_sym
  end
end

.class_symbol_prefix(klass) ⇒ Object

Given a Class object, derive the prefix string for the accessor functions that instance_variable will create.



96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/safer/ivar.rb', line 96

def self.class_symbol_prefix(klass)
  klass.to_s.split('::').inject(nil) do |memo, el|
    # reset the symbol name when an anonymous class is encountered.
    if /^\#<.*>$/.match(el)
      nil
    elsif memo
      memo + '_' + el.downcase
    else
      el.downcase
    end
  end
end

.export_accessor(klass, *nmlist) ⇒ Object

export both reader and writer routines for instance variables in a nicer way.

klass

Class object for which to define accessors for instance variables defined by Safer::IVar.instance_variable.

nmlist

List of symbols for which to define accessors. Each symbol in nmlist should have previously been given as an argument to Safer::IVar.instance_variable(klass).



216
217
218
219
# File 'lib/safer/ivar.rb', line 216

def self.export_accessor(klass, *nmlist)
  self.export_reader(klass, *nmlist)
  self.export_writer(klass, *nmlist)
end

.export_reader(klass, *nmlist) ⇒ Object

export the reader routines for instance variables in a nicer way.

klass

Class object for which to define reader accessors for instance variables defined by Safer::IVar.instance_variable.

nmlist

List of symbols for which to define reader accessors. Each symbol in nmlist should have previously been given as an argument to Safer::IVar.instance_variable(klass).



181
182
183
184
185
186
187
188
# File 'lib/safer/ivar.rb', line 181

def self.export_reader(klass, *nmlist)
  symlist = self.symbol_names(klass, *nmlist)
  nmlist.size.times do |index|
    thisnm = nmlist[index]
    thissym = symlist[index]
    klass.class_eval("def #{thisnm} ; self.#{thissym} ; end")
  end
end

.export_writer(klass, *nmlist) ⇒ Object

export the writer routines for instance variables in a nicer way.

klass

Class object for which to define writer accessors for instance variables defined by Safer::IVar.instance_variable.

nmlist

List of symbols for which to define writer accessors. Each symbol in nmlist should have previously been given as an argument to Safer::IVar.instance_variable(klass).



197
198
199
200
201
202
203
204
205
206
# File 'lib/safer/ivar.rb', line 197

def self.export_writer(klass, *nmlist)
  symlist = self.symbol_names(klass, *nmlist)
  nmlist.size.times do |index|
    thisnm = nmlist[index]
    thissym = symlist[index]
    klass.class_eval(
      "def #{thisnm}=(value) ; self.#{thissym} = value ; end"
    )
  end
end

.instance_variable(klass, *nmlist) ⇒ Object

create accessor routines for an instance variable, with variable name determined by the class in which the instance variable was declared.

klass

Class object into which to generate the variable accessor functions.

nmlist

List of symbols for which to create accessor routines.

Uses Safer::IVar.symbol_names to determine the symbol names of the accessor routines. For example, the listing:

class MyClass
  class SubClass
    Safer::IVar.instance_variable(self, :foo, :bar)
  end
end

is equivalent to the following code:

class MyClass
  class SubClass
    attr_accessor :myclass_subclass__foo
    attr_accessor :myclass_subclass__bar
  end
end

The name-mangling signals that these instance variables are, for all intents and purposes, private to klass.



166
167
168
169
170
171
172
# File 'lib/safer/ivar.rb', line 166

def self.instance_variable(klass, *nmlist)
  self.symbol_names(klass, *nmlist).each do |symnm|
    klass.class_eval do
      attr_accessor symnm
    end
  end
end

.symbol_names(klass, *nmlist) ⇒ Object

compute accessor routine symbol names, so that the names include the entire class namespace.

klass

Class object for which to generate a symbol name.

nmlist

Array of Symbol objects.

return

Array of Symbol objects generated from klass and each element of nmlist.

For example, given the following listing:

class OuterClass
  class InnerClass
    SYMNM = Safer::IVar.symbol_names(self, :foo)
    puts(SYMNM.to_s)
  end
end

the following output would be produced:

outerclass_innerclass__foo


139
140
141
# File 'lib/safer/ivar.rb', line 139

def self.symbol_names(klass, *nmlist)
  self._symbol_names(klass, nmlist)
end