Ruby metaprogramming and own custom attr_accessor
Ruby is a dynamic language, that’s why you won’t get compile time type warnings/errors as you get in languages like C#.
Ruby has a lot of metaprogramming features allowing you to create your own custom methods on the fly which can be used the same as att_accessor.
dynamic method definition – define_method
Use the following code to define a new method in your class on the fly:
class Myclass
define_method("your_method_name_here") do
# your code here
end
end
Custom attr_accessor method accessible from all classes
To create a method that will be accesible from all classes you need to add code to the Class class. In Ruby a class is simply an object of class Class. Ruby provides a method class_eval that takes a string and evaluates it in the context of the current class, that is, the class from which you’re calling your attr_accessor.
class Class
def my_custom_attr_method(attr_name)
# your code here
end
end
# you can use this method in any of your classes
class Myclass
my_custom_attr_method :attr1
end
attr_accessor
Ruby’s method attr_accessor uses metaprogramming to create getters and setters for object attributes on the fly.
It creates the code like this:
class Class
def my_attr_accessor(*args)
# iterate through each passed in argument...
args.each do |arg|
# getter
self.class_eval("def #{arg};@#{arg};end")
# setter
self.class_eval("def #{arg}=(val);@#{arg}=val;end")
end
end
end
Examples
Below is some examples of using metaprogramming.
Create custom attr_accessor to track the history of values
Define a method attr_accessor_with_history that provides the same functionality as attr accessor but also tracks every value the attribute has ever had.
This method was taken from the home work task of the class ‘Software as a service’ – https://www.coursera.org/saas/auth/welcome.
class Class
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s
attr_hist_name = attr_name+'_history'
#getter
self.class_eval("def #{attr_name};@#{attr_name};end")
#setter
self.class_eval %Q{
def #{attr_name}=(val)
# add to history
       @#{attr_hist_name} = [nil] if @#{attr_hist_name}.nil?
       @#{attr_hist_name} << val
 # set the value itself
@#{attr_name}=val
end
def #{attr_hist_name};@#{attr_hist_name};end
}
end
end
Usage of this accessor:
class Foo
attr_accessor_with_history :bar
def initialize(some_bar_value)
self.bar = some_bar_value
end
end
f = Foo.new
f.bar = 3
f.bar = :wowzo
f.bar = 'boo!'
puts f.bar_history # => [nil, 3, :wowzo, 'boo!']
Note! You should use self.bar in the initializer method so it calls our custom setter to track the history.
If you write bar=some_bar_value then it will create a local variable ‘bar’ that doesn’t do what is intended.
Strongly typed attr_accessor
class Myclass
def self.typesafe_accessor(name, type)
define_method(name) do
instance_variable_get("@#{name}")
end
define_method("#{name}=") do |value|
if value.is_a? type
instance_variable_set("@#{name}", value)
else
raise ArgumentError.new("Invalid Type")
end
end
end
typesafe_accessor :foo, Integer
end
# usage
f = Foo.new
f.foo = 1
f.foo = "bar" # an exception thrown here!You can modify this code to create an accessor so it can be used in all classes. To do this – move this code to class Class and modify it a little like in the first example.
This code is based on this explanation: http://stackoverflow.com/questions/7988410/attr-accessor-strongly-typed-ruby-on-rails
Read more:
-Â Meta-programming in Ruby – http://learnbysoft.blogspot.com/2010/10/meta-programming-in-ruby-part-1-alias.html
- Screencasts on The Ruby Object Model and Metaprogramming – http://pragprog.com/screencasts/v-dtrubyom/the-ruby-object-model-and-metaprogramming
Comments
-
Josephquigley
-
Anonymous
-
Josephquigley
-
Anonymous
-
orlandodelaguila
-
Anonymous
-
Sarah H
-
Anonymous
-
Randy Fay



