Where you pull a Class Macro () from your bag of tricks.
The fourth step in your development plan asks you to change the Kernel Method to a Class Macro () that’s available to all classes.
What this means is that instead of an add_checked_attribute method, you want an attr_checked method that the boss can use in a class definition. Also, instead of taking a class and an attribute name, this new method should take only the attribute name, because the class is already available as self.
Bill updates the test case:
| require 'test/unit' |
| |
| class Person |
* | attr_checked :age do |v| |
* | v >= 18 |
* | end |
| end |
| |
| class TestCheckedAttributes < Test::Unit::TestCase |
| def setup |
* | @bob = Person.new |
| end |
| |
| def test_accepts_valid_values |
| @bob.age = 20 |
| assert_equal 20, @bob.age |
| end |
| |
| def test_refuses_invalid_values |
| assert_raises RuntimeError, 'Invalid attribute' do |
| @bob.age = 17 |
| end |
| end |
| end |
Can you write the attr_checked method and pass the tests?
Think back to the discussion of class definitions in . If you want to make attr_checked available to any class definition, you can simply make it an instance method of either Class or Module. Let’s go for the first option:
* | class Class |
* | def attr_checked(attribute, &validation) |
| define_method "#{attribute}=" do |value| |
| raise 'Invalid attribute' unless validation.call(value) |
| instance_variable_set("@#{attribute}", value) |
| end |
| define_method attribute do |
| instance_variable_get "@#{attribute}" |
| end |
* | end |
* | end |
This code doesn’t even need to call to class_eval, because when the method executes, the class is already taking the role of self.
“That’s great,” Bill says. “One more step, and we’ll be done.” For this last step, however, you need to learn about a feature that we haven’t talked about yet: Hook Methods.