First, I was curious to see how attr_accessor was implemented in Ruby. I found the source in object.c which simply calls "rb_attr(klass, rb_to_id(argv[i]), 1, 1, Qtrue);". The "rb_attr" function is located in eval.c. If you spend a few min looking at it, you will see it has special code for handling Access Control (public, private, protected). For now, I will ignore Access Control, and just implement creating the accessor methods.
Where should attr_accessor go? Looking at the docs, you will find attr_accessor is a member of Module. To see if I had the correct location, I quickly wrote...
module Module
alias old_attr_accessor attr_accessor
def attr_accessor( *symbols )
puts "attr_accessor called with #{symbols.join(' ')}."
old_attr_accessor( *symbols )
end
end
This failed since Module is not a module, it is a class! Laughing at myself and Ruby, I corrected the definition. The code above also demonstrates how easy you can implement simple Aspect Oriented Programming in Ruby. Using alias, I give the old attr_accessor method an new name old_attr_accessor. Then overwrite attr_accessor with my own code. Any call to attr_accessor will now print out the information that it was called.
Fun so far, but I haven't really defined my own version of attr_accessor. I've simple called the old one. I found module_eval does the trick (class_eval also works since it is a synonym for Module.module_eval). The final code is...
class Module
def attr_accessor( *symbols )
symbols.each { | symbol |
module_eval( "def #{symbol}() @#{symbol}; end" )
module_eval( "def #{symbol}=(val) @#{symbol} = val; end" )
}
end
end
class Foobar
attr_accessor :foo
private
attr_accessor :bar
end
fb = Foobar.new
fb.foo = "hello"
fb.bar = "world"
puts fb.foo # >> hello
puts fb.bar # >> world
Pretty simple. Unfortunately, this demonstrates the problem where my attr_accessor doesn't know about the Access Control. Looking at the Ruby library and source, I could not find any way to query the Access Control level from within my attr_accessor. So the only solution I could find is the following hack...
class Module
alias old_public public
alias old_private private
alias old_protected protected
def public( aSymbol = nil )
if aSymbol.nil?
@__module_access_level = 'public'
old_public
else
old_public( aSymbol )
end
end
def private( aSymbol = nil )
if aSymbol.nil?
@__module_access_level = 'private'
old_private
else
old_private( aSymbol )
end
end
def protected( aSymbol = nil )
if aSymbol.nil?
@__module_access_level = 'protected'
old_private
else
old_private( aSymbol )
end
end
def attr_accessor( *symbols )
symbols.each { | symbol |
module_eval( "def #{symbol}() @#{symbol}; end" )
module_eval( "def #{symbol}=(val) @#{symbol} = val; end" )
module_eval( "#{@__module_access_level} :#{symbol}; #{@__module_access_level} :#{symbol}=" )
}
end
@__module_access_level = 'public'
end
class Foobar
attr_accessor :foo
private
attr_accessor :bar
end
fb = Foobar.new
fb.foo = "hello"
fb.bar = "world" #>> private method `bar=' called for #<Foobar:0x2855710 @foo="hello"> (NoMethodError)
puts fb.foo #
puts fb.bar #
Anyone got a better idea for handling Access Control?