Where you finally earn Bill’s respect and the title of Master of Metaprogramming.
The following is the code that we wrote in the previous development step:
| 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 defines a Class Macro () named attr_checked. This Class Macro is an instance method of Class, so it’s available to all classes. The final step in your development plan asks you to restrict access to attr_checked: it should be available only to those classes that include a module named CheckedAttributes. The test suite for this step is pretty much the same one you used in step 4, with a single additional line:
| require 'test/unit' |
| |
| class Person |
* | include CheckedAttributes |
| |
| 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 = 18 |
| assert_equal 18, @bob.age |
| end |
| |
| def test_refuses_invalid_values |
| assert_raises RuntimeError, 'Invalid attribute' do |
| @bob.age = 17 |
| end |
| end |
| end |
Can you remove attr_checked from Class, write the CheckedAttributes module, and solve the boss’ challenge?
You can copy the trick that you learned in . CheckedAttributes defines attr_checked as a class method on its includers:
* | module CheckedAttributes |
* | def self.included(base) |
* | base.extend ClassMethods |
* | end |
* | |
* | module ClassMethods |
| 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 |
* | end |
Your boss will be delighted. These are the same Class Macro and module that she challenged you to write this morning. If you can write code like this, you’re on your way to mastering the art of metaprogramming.