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