Книга: Metaprogramming Ruby 2
Назад: Kernel#eval
Дальше: Quiz: Checked Attributes (Step 2)

Quiz: Checked Attributes (Step 1)

Where you take your first step toward solving the boss’ challenge, with Bill looking over your shoulder.

You and Bill look back at the first two steps of your development plan:

  1. Write a Kernel Method () named add_checked_attribute using eval to add a super-simple validated attribute to a class.

  2. Refactor add_checked_attribute to remove eval.

Focus on the first step. The add_checked_attribute method should generate a reader method and a writer method, pretty much like attr_accessor does. However, add_checked_attribute should differ from attr_accessor in three ways. First, while attr_accessor is a Class Macro (), add_checked_attribute is supposed to be a simple Kernel Method (). Second, attr_accessor is written in C, while add_checked_attribute should use plain Ruby (and a String of Code ()). Finally, add_checked_attribute should add one basic example of validation to the attribute: the attribute will raise a runtime exception if you assign it either nil or false. (You’ll deal with flexible validation down the road.)

These requirements are expressed more clearly in a test suite:

 
require ​'test/unit'
 
 
class​ Person; ​end
 
 
class​ TestCheckedAttribute < Test::Unit::TestCase
 
def​ setup
 
add_checked_attribute(Person, :age)
 
@bob = Person.new
 
end
 
 
def​ test_accepts_valid_values
 
@bob.age = 20
 
assert_equal 20, @bob.age
 
end
 
 
def​ test_refuses_nil_values
 
assert_raises RuntimeError, ​'Invalid attribute'​ ​do
 
@bob.age = nil
 
end
 
end
 
 
def​ test_refuses_false_values
 
assert_raises RuntimeError, ​'Invalid attribute'​ ​do
 
@bob.age = false
 
end
 
end
 
end
 
 
# Here is the method that you should implement.
 
def​ add_checked_attribute(klass, attribute)
 
# ...
 
end

(The reference to the class in add_checked_attribute is called klass because class is a reserved word in Ruby.)

Can you implement add_checked_attribute and pass the test?

Before You Solve This Quiz…

You need to generate an attribute like attr_accessor does. You might appreciate a short review of attr_accessor, which we talked about first in . When you tell attr_accessor that you want an attribute named, say, :my_attr, it generates two Mimic Methods () like the following:

 
def​ my_attr
 
@my_attr
 
end
 
 
def​ my_attr=(value)
 
@my_attr = value
 
end

Quiz Solution

Here’s a solution:

 
def​ add_checked_attribute(klass, attribute)
*
eval ​"
*
class ​#{klass}
*
def ​#{attribute}​=(value)
*
raise 'Invalid attribute' unless value
*
@​#{attribute}​ = value
*
end
*
*
def ​#{attribute}​()
*
@​#{attribute}
*
end
*
end
*
"
 
end

Here’s the String of Code () that gets evaluated when you call add_checked_attribute(String, :my_attr):

 
class​ String
 
def​ my_attr=(value)
 
raise ​'Invalid attribute'​ ​unless​ value
 
@my_attr = value
 
end
 
 
def​ my_attr()
 
@my_attr
 
end
 
end

The String class is treated as an Open Class (), and it gets two new methods. These methods are almost identical to those that would be generated by attr_accessor, with an additional check that raises an exception if you call my_attr= with either nil or false.

“That was a good start,” Bill says. “But remember our plan. We only used eval to pass the unit tests quickly; we don’t want to stick with eval for the real solution. This takes us to step 2.”

Назад: Kernel#eval
Дальше: Quiz: Checked Attributes (Step 2)