Rubyの動的言語の強みを最大限に生かした方法で
クラスマクロを種々の方法で実装していく。
以下読み進めるにあたって、
Rubyのメタプログラミング技術は理解できていることを前提とする。
クラスマクロは知っているだろう。
クラス定義内で使える以下のようなクラスメソッドである。
class MyClass attr_accessor :my_attribute end
ここでは以下を実現するクラスメソッドを手段を変えて例示していく。
クラスメソッド内容
属性値を検証する機能もったクラスメソッドを作り、
ついでにゲッターとセッターのメソッドを作る。
◆ クラス内に直接クラスメソッドを記載方式
class Person
def self.attr_checked(attribute, &validation)
# getter
define_method attribute do
instance_variable_get "@#{attribute}"
end
# setter
define_method "#{attribute}=" do |value|
raise 'Invalid attribute' unless validation.call(value)
instance_variable_set("@#{attribute}", value)
end
end
# :ageとdo以降のブロックが2つの引数となる
attr_checked :age do |value|
value >= 20
end
end
person = Person.new
person.age = 30 # => 30
person.age = 10 # => raiseのエラーが返る
◆ extend方式
module CheckedAttributes
def attr_checked(attribute, &validation)
# getter
define_method attribute do
instance_variable_get "@#{attribute}"
end
# setter
define_method "#{attribute}=" do |value|
raise 'Invalid attribute' unless validation.call(value)
instance_variable_set("@#{attribute}", value)
end
end
end
class Person
extend CheckedAttributes
attr_checked :age do |value|
value >= 20
end
end
◆ 特異メソッド方式
module CheckedAttributes
def attr_checked(attribute, &validation)
# getter
define_method attribute do
instance_variable_get "@#{attribute}"
end
# setter
define_method "#{attribute}=" do |value|
raise 'Invalid attribute' unless validation.call(value)
instance_variable_set("@#{attribute}", value)
end
end
end
class Person
class << self
include CheckedAttributes
end
attr_checked :age do |value|
value >= 20
end
end
◆ moduleの拡張方式
module CheckedAttributes
def self.included(includer)
includer.extend ClassMethods
end
module ClassMethods
def attr_checked(attribute, &validation)
# getter
define_method attribute do
instance_variable_get "@#{attribute}"
end
# setter
define_method "#{attribute}=" do |value|
raise 'Invalid attribute' unless validation.call(value)
instance_variable_set("@#{attribute}", value)
end
end
end
end
class Person
include CheckedAttributes
attr_checked :age do |value|
value >= 20
end
end
◆ include方式
module CheckedAttributes
def self.included(includer)
includer.class_eval do
def self.attr_checked(attribute, &validation)
# getter
define_method attribute do
instance_variable_get "@#{attribute}"
end
# setter
define_method "#{attribute}=" do |value|
raise 'Invalid attribute' unless validation.call(value)
instance_variable_set("@#{attribute}", value)
end
end
end
end
class Person
include CheckedAttributes
attr_checked :age do |value|
value >= 20
end
end
◆ 全クラスオブジェクトに追加方式
module CheckedAttributes
def attr_checked(attribute, &validation)
# getter
define_method attribute do
instance_variable_get "@#{attribute}"
end
# setter
define_method "#{attribute}=" do |value|
raise 'Invalid attribute' unless validation.call(value)
instance_variable_set("@#{attribute}", value)
end
end
end
Class.send(:include, CheckedAttributes)
class Person
attr_checked :age do |value|
value >= 20
end
end
◆◆◆◆
最後は実装手段ではなく、便利そうなクラスマクロがあったので
利活用の観点で記録しておく。
とあるクラスのメソッド名を変更したい。
ただし古いメソッド名を突然使えなくするのではなく、
警告を出して新しいメソッドへの利用を促すソフトな処置をとりたい。
class Person
def initialize(age)
@age = age
end
def age
@age
end
def self.deprecate(old_method, new_method)
define_method(old_method) do |*args|
warn "Warning: #{old_method}() is deprecated. Use #{new_method}()."
send(new_method, *args)
end
end
deprecate :getAge, :age
end
person = Person.new(20)
person.getAge