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