ActiveModel::AttributeMethods.attribute_method_prefix(suffix)の概要と仕組みを調べたら仕組みの方がなんか怪しい

# 概要
[ActiveModel::AttributeMethods](http://api.rubyonrails.org/classes/ActiveModel/AttributeMethods/ClassMethods.html)はクラスの各attributeにprefix, suffix, affixをつけたメソッドをまとめて定義出来る便利なモジュールです。

使い方はActiveModel::AttributeMethodsをインクルードしたクラスで

* attribute_xxxxメソッドをprefix, suffixを引数にして呼び出し
* define_attribute_methodsをattribute_メソッドを定義したいattributeの名前を引数にして呼び出し
* 対応するattributeメソッドを定義
  * prefixの場合 : #{prefix}_attributeメソッド
  * suffixの場合はattribute_#{suffix}メソッド
  * affixの場合は#{prefix}_attribute_#{suffix}メソッド
 
することでprefix, suffix, affixをつけたメソッドが動的に定義されます。

```ruby
class Person
  include ActiveModel::AttributeMethods

  # attribute_xxxxメソッドをprefix, suffixを引数にして呼び出し
  attribute_method_prefix 'clear_'
  attribute_method_suffix '_contrived?'
  attribute_method_affix  prefix: 'reset_', suffix: '_to_default!'
  
  # define_attribute_methodsをattribute_メソッドを定義したいattributeの名前を引数にして呼び出し
  define_attribute_methods :name

  attr_accessor :name

  def attributes
    { 'name' => @name }
  end

  private
  # 対応するattributeメソッドを定義

  ## prefixの場合 : #{prefix}_attributeメソッド
  def clear_attribute(attr)
    send("#{attr}=", nil)
  end
  
  ## suffixの場合はattribute_#{suffix}メソッド
  def attribute_contrived?(attr)
    true
  end

  ## affixの場合は#{prefix}_attribute_#{suffix}メソッド
  def reset_attribute_to_default!(attr)
    send("#{attr}=", 'Default Name')
  end
end

# > person = Person.new
# > person.clear_name
# => nil
# > person.name_contrived?
# => true
# > person.reset_name_to_default!
# => "Default Name"
```


# 仕組み

具体例として上記の

```ruby
attribute_method_prefix 'clear_'
```

の実装を追ってみます。

ソースコードを見ると

```ruby
# Declares a method available for all attributes with the given prefix.
# Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
#
#   #{prefix}#{attr}(*args, &block)
#
# to
#
#   #{prefix}attribute(#{attr}, *args, &block)
# ~略
def attribute_method_prefix(*prefixes)
```

とありmethod_missingとrespond_to?を使ってメソッドを


```ruby
clear_name(*args, &block)
```

から

```ruby
clear_attribute(:name, *args, &block)
```

に書き換えているとあります。



実際に実装を追ってみます。
まずクラス生成時にAttributeMethods.attribute_method_prefixが呼ばれます
この例では1つしか引数を渡していませんがまとめて複数渡すことが出来るので可変長引数になっています。

```ruby:activemodel-5.0.1/lib/active_model/attribute_methods.rb
def attribute_method_prefix(*prefixes)
  self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new prefix: prefix }
  undefine_attribute_methods
end
```

1行目で各prefixをattribute_method_matchersにセットします。AttributeMethodMatcherの詳細は省きますが、結果として以下のようAttributeMethodMatcherオブジェクトがself.attribute_method_matchersにセットされます。
ここでmethod_missing_targetにmethod_missingで捕捉するメソッドを登録しています。

```ruby:activemodel-5.0.1/lib/active_model/attribute_methods.rb
def attribute_method_prefix(*prefixes)
  self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new prefix: prefix }
  #=> [ #<ActiveModel::AttributeMethods::ClassMethods::AttributeMethodMatcher:0x007f81323bd1d8
  #@method_missing_target="clear_attribute",
  #@method_name="clear_%s",
  #@prefix="clear_",
  #@regex=/^(?:clear_)(.*)(?:)$/,
  #@suffix=""]
  undefine_attribute_methods
end
```


その後、define_attribute_methods :name内でdefine_proxy_callというメソッドが呼ばれて、clear_nameが定義されます。ここがポイントなのですが、このメソッド定義はPersonクラス内に直接されるのではなく、動的に生成されたModuleに定義されてそのModuleがPersonクラスの1つ上に差し込まれます

```ruby
def define_proxy_call(include_private, mod, name, send, *extra) #:nodoc:
  defn = if name =~ NAME_COMPILABLE_REGEXP
    "def #{name}(*args)"
  else
    "define_method(:'#{name}') do |*args|"
  end

  extra = (extra.map!(&:inspect) << "*args").join(", ".freeze)

  target = if send =~ CALL_COMPILABLE_REGEXP
    "#{"self." unless include_private}#{send}(#{extra})"
  else
    "send(:'#{send}', #{extra})"
  end

  mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
    #{defn}
      #{target}
    end
  RUBY
  # def clear_name(*args)
  #   clear_attribute("name", *args)
  # end
  # がModuleに定義される
end
```

```ruby
> Person.ancestors
=> [Person,
 #<Module:0x007fcdfc463328>, #差し込まれる
 ActiveModel::AttributeMethods,
 ...
 BasicObject]
> Person.ancestors[1].instance_methods(false)
=> [:name, :clear_name] #clear_nameがmoduleに定義されている
```

これで準備は整いました　。実際にclear_nameが呼び出される時の流れを追ってみます。
まずPersonオブジェクトがclear_nameを呼び出し、階層を上にたどってModuleに定義されているclear_nameを呼び出します。

![スクリーンショット 2017-02-16 16.37.25.png](https://qiita-image-store.s3.amazonaws.com/0/71154/d857e0a2-d3d1-d6e6-4d82-45d69223b12c.png)

次にclear_name内でclear_attributeが呼ばれ継承階層を上に辿っていきますが、メソッドが見つからずMethodMissingとなります。

![スクリーンショット 2017-02-16 16.39.12.png](https://qiita-image-store.s3.amazonaws.com/0/71154/ec079217-d422-e8cc-7925-3af4fbc2d5bc.png)

MethodMissingとなったので呼び出し元のModuleに戻りmethod_missingメソッドを探します。結果MethodAttributeクラスにmethod_missingがありそのmethod_missing内で登録しておいたmethod_missing_target(clear_attribute)と一致するのでnameを引数にしてPersonObjectのclear_attributeに転送します。

![スクリーンショット 2017-02-16 16.43.27.png](https://qiita-image-store.s3.amazonaws.com/0/71154/0465cd09-c608-4a9a-3522-333e6fdc355d.png)


実際MethodAttributesのmethod_missingは以下のようになっておりmatched_attribute_method内でメソッドを探索しマッチするものがあればattribute_missingを呼びします。最後にattribute_missing内でtargetのメソッドを呼び出します。

```ruby:activemodel-5.0.1/lib/active_model/attribute_methods.rb
def method_missing(method, *args, &block)
  if respond_to_without_attributes?(method, true)
    super
  else
    match = matched_attribute_method(method.to_s)
    match ? attribute_missing(match, *args, &block) : super
  end
end

def attribute_missing(match, *args, &block)
  __send__(match.target, match.attr_name, *args, &block)
end
```


# ※ 上記の様な仕組みになっていると***思います。***

何故思いますかというといくらやってもこのmethod_missingの中に入ってこないからです。
普通に無いメソッドをcallすれば入ってくるのですが、なぜ入ってこないかわかりませんでした。

method_missingを使っているとコメントに書いてあるのでMethodAttributes#method_missingに入ってくるはずだと思ってこのように書きましたがそもそもそれが間違っているのかもしれません。

```ruby
def method_missing(method, *args, &block)
  puts "CALL METHOD MISSING!!"
  if respond_to_without_attributes?(method, true)

> Person.new.nothing_method
CALL METHOD MISSING!!

> Person.new.clear_name
Person.new.clear_name
=> nil
```

もしご存知の方いればぜひご教示ください！！！！(その他間違いもありましたら奇譚なくご指摘くださいm(_)m)

# 追記: 

上記の説明は以下のところが間違えていました。

次にclear_name内でclear_attributeが呼ばれ継承階層を上に辿っていきますが、メソッドが見つからずMethodMissingとなります。

![スクリーンショット 2017-02-16 16.39.12.png](https://qiita-image-store.s3.amazonaws.com/0/71154/ec079217-d422-e8cc-7925-3af4fbc2d5bc.png)

これは実際にはModuel起点でメソッドの探索は行われず、PersonObjectから探索が行われます。

```ruby
module A
  def hoge
    baz
  end
  def baz
    puts "call form A"
  end
end
class B
  include A
  def baz
    puts "call form B"
  end
end
> B.new.hoge
call form B
=> nil
```

なのでMethodMissingは呼び出されず、正しい図はこうなります
<img width="330" alt="スクリーンショット 2017-02-16 17.50.25.png" src="https://qiita-image-store.s3.amazonaws.com/0/71154/8439ef4c-21b5-ef1c-6379-80d528f26eb2.png">

ということはやっぱりMethodAttributeのmethod_missingはcallされてないということになります。
じゃあこのコメントの説明はどういう意味なんだろうか。。。

```ruby
# Declares a method available for all attributes with the given prefix.
# Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
#
#   #{prefix}#{attr}(*args, &block)
#
# to
#
#   #{prefix}attribute(#{attr}, *args, &block)
# ~略
```

# 追記の追記
このコメントが追加されたのは2009年でそれ以降変わってないようです。
かつこの時の実装は明らかにsendしていますね

```ruby
def method_missing(method_id, *args, &block)
  method_name = method_id.to_s
  if match = match_attribute_method?(method_name)
    guard_private_attribute_method!(method_name, args)
    return __send__("#{match.prefix}attribute#{match.suffix}", match.base, *args, &block)
  end
  super
end
```

ますます怪しい
https://github.com/rails/rails/commit/f8d3c72c39ad209abca7f3613f91fb3a03805261