Class: Safer::IVarFactory

Inherits:
Object
  • Object
show all
Defined in:
lib/safer/ivarfactory.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.

Defined Under Namespace

Classes: Dsl, Prefix

Instance Method Summary collapse

Constructor Details

#initialize(prefix) ⇒ IVarFactory

Returns a new instance of IVarFactory.



93
94
95
# File 'lib/safer/ivarfactory.rb', line 93

def initialize(prefix)
  @prefix = prefix
end

Instance Method Details

#_export_accessor_internal(klass, prefix, nmlist) ⇒ Object

Used internally. See Safer::IVarFactory#export_accessor, and Safer::IVarFactory::Dsl#accessor .



240
241
242
243
# File 'lib/safer/ivarfactory.rb', line 240

def _export_accessor_internal(klass, prefix, nmlist)
  self._export_reader_internal(klass, prefix, nmlist)
  self._export_writer_internal(klass, prefix, nmlist)
end

#_export_reader_internal(klass, prefix, nmlist) ⇒ Object

Used internally. See Safer::IVarFactory#export_reader, and Safer::IVarFactory::Dsl#reader .



190
191
192
193
194
195
196
197
# File 'lib/safer/ivarfactory.rb', line 190

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

#_export_writer_internal(klass, prefix, nmlist) ⇒ Object

Used internally. See Safer::IVarFactory#export_writer, and Safer::IVarFactory::Dsl#writer .



214
215
216
217
218
219
220
221
222
223
# File 'lib/safer/ivarfactory.rb', line 214

def _export_writer_internal(klass, prefix, nmlist)
  symlist = self._symbol_names_internal(prefix, 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_internal(klass, prefix, nmlist) ⇒ Object

Used internally. See Safer::IVarFactory#instance_variable, and Safer::IVarFactory::Dsl#ivar .



151
152
153
154
155
156
157
# File 'lib/safer/ivarfactory.rb', line 151

def _instance_variable_internal(klass, prefix, nmlist)
  self._symbol_names_internal(prefix, nmlist).each do |symnm|
    klass.class_eval do
      attr_accessor symnm
    end
  end
end

#_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.



123
124
125
126
# File 'lib/safer/ivarfactory.rb', line 123

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

#_symbol_names_internal(prefix, nmlist) ⇒ Object

Used internally.

prefix

Prefix of generated symbol names.

nmlist

Array of Symbol objects.

return

Array of Symbol objects. Each element will be the concatenation of the prefix, ‘__’, and the corresponding element of nmlist



111
112
113
114
115
# File 'lib/safer/ivarfactory.rb', line 111

def _symbol_names_internal(prefix, nmlist)
  nmlist.map do |nm|
    (prefix + '__' + 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.



100
101
102
# File 'lib/safer/ivarfactory.rb', line 100

def class_symbol_prefix(klass)
  @prefix.class_symbol_prefix(klass)
end

#export_accessor(klass, *nmlist) ⇒ Object

export both reader and writer routines for instance variables in a safer 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).



253
254
255
256
# File 'lib/safer/ivarfactory.rb', line 253

def export_accessor(klass, *nmlist)
  self._export_accessor_internal(
    klass, self.class_symbol_prefix(klass), nmlist)
end

#export_reader(klass, *nmlist) ⇒ Object

export the reader routines for instance variables in a safer 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).



206
207
208
209
# File 'lib/safer/ivarfactory.rb', line 206

def export_reader(klass, *nmlist)
  self._export_reader_internal(
    klass, self.class_symbol_prefix(klass), nmlist)
end

#export_writer(klass, *nmlist) ⇒ Object

export the writer routines for instance variables in a safer 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).



232
233
234
235
# File 'lib/safer/ivarfactory.rb', line 232

def export_writer(klass, *nmlist)
  self._export_writer_internal(
    klass, self.class_symbol_prefix(klass), nmlist)
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.



182
183
184
185
# File 'lib/safer/ivarfactory.rb', line 182

def instance_variable(klass, *nmlist)
  self._instance_variable_internal(
    klass, self.class_symbol_prefix(klass), nmlist)
end

#run(klass) {|Dsl.new(self, klass)| ... } ⇒ Object

Yield DSL interface for instance variable creation to caller. See Safer::IVarFactory::Dsl for the DSL API.

klass

Class object used by DSL interface.

Yields:

  • (Dsl.new(self, klass))


262
263
264
# File 'lib/safer/ivarfactory.rb', line 262

def run(klass)
  yield(Dsl.new(self, klass))
end

#symbol_names(klass, *nmlist) ⇒ Object

compute accessor routine symbol names, where the symbol name prefix is derived from the class name.

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


144
145
146
# File 'lib/safer/ivarfactory.rb', line 144

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