AttributeMethods のできること
指定した属性に対して、まるっと同じ機能を実装する事が出来ます。
例えば、こんな感じ
class Person
include ActiveModel::AttributeMethods
attr_accessor :name, :age, :address
attribute_method_prefix 'clear_'
define_attribute_methods :name, :age, :address
def initialize(name:, age:, address:)
@name = name
@age = age
@address = address
end
private
def clear_attribute(attr)
send("#{attr}=", nil)
end
end
person = Person.new
person.name = 'Bob'
person.name #=> "Bob"
person.clear_name
person.name #=> nil
person.age = 20
person.clear_age
person.age #=> nil
複数の属性に共通の処理をさせたいとき、重複なく書けていいですね。
そんな、AttributeMethods
にあるメソッドを紹介します。
attribute_method_prefix(*prefixes)
define_attribute_methods
で宣言した、各属性に対して、prefix を付加したメソッドを定義出来ます。
メソッドの定義は #{prefix}_attribute
で定義して、属性名を第一引数に取ります。
class Person
include ActiveModel::AttributeMethods
attr_accessor :name
attribute_method_prefix 'titleize_'
define_attribute_methods :name
private
def titleize_attribute(attr)
original = send(attr)
titleize_str = original.split(' ').map(&:capitalize).join(' ')
send("#{attr}=", titleize_str)
end
end
person.name = 'tanaka taro'
person.titleize_name
person.name #=> "Tanaka Taro"
attribute_method_suffix(*suffixes)
名前から分かるように上の suffix 版です。attribute_#{suffix}
を定義することが出来ます。
class Person
include ActiveModel::AttributeMethods
attr_accessor :name
attribute_method_suffix '_short?'
define_attribute_methods :name
private
def attribute_short?(attr)
send(attr).length < 5
end
end
person.name = 'Bob'
person.name_short? # => true
attribute_method_affix(*affixes)
そして、prefix と suffix の両方をつけたいときです!
class Person
include ActiveModel::AttributeMethods
attr_accessor :name
attribute_method_affix prefix: 'reset_', suffix: '_to_default!'
define_attribute_methods :name
private
def reset_attribute_to_default!(attr)
send("#{attr}=", 'Default Name')
end
end
person.name = 'Bob'
person.reset_name_to_default!
person.name #=> "Default Name"
中を見てみる
上の三つで、まるっと便利にメソッドを定義出来ますが、一体こいつらがどんな事をやっているのかを見てみると、やはりお得意の method_missing を使ってます。
def attribute_method_prefix(*prefixes)
self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new prefix: prefix }
undefine_attribute_methods
end
定義したいメソッド名を宣言する attribute_method_prefix
です。中を見ると AttributeMethodMatcher
のインスタンスを繰り返し作ってますね。
class AttributeMethodMatcher
...
def initialize(options = {})
@prefix, @suffix = options.fetch(:prefix, ''), options.fetch(:suffix, '')
@regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
@method_missing_target = "#{@prefix}attribute#{@suffix}"
@method_name = "#{prefix}%s#{suffix}"
end
...
end
AttributeMethodMatcher
が作られると @regex
に正規表現を挿入してる事がわかります。この正規表現にマッチするものを method_missing
でキャッチして、ゴリゴリとメソッドを呼び出しているんですね。
おわり
AttributeMethods
で、いろいろと DRY に実装できそうですね。んーそれにしても method_missing
は便利すぎて恐ろしいなあ。