Ruby on Rails のソースコードを読む Active Support編

# Ruby on Rails のソースコードを読む Active Support編

## 目的
ruby 及び rais の理解を深める為、読んだソースコードの内容をメモしていきたいと思います。

## この記事で説明するメソッド名と役割
- cattr_accessor
    - cattr_reader
    - cattr_writer

クラス変数(@@で始まる変数)のアクセサを定義。

- mattr_accessor
    - mattr_reader
    - mattr_writer

モジュール変数(@@で始まる変数)のアクセサを定義。

※アクセサとは・・・JavaやC系でいうところのgetter/setter

## ソースコード
コメントを削除したものを載せます。
全部で57行！
なんとか読めそうな気がしますね。
 
```ruby:gems/2.1.0/gems/activesupport-4.2.7.1/lib/active_support/core_ext/module/attribute_accessors.rb
require 'active_support/core_ext/array/extract_options'
class Module

  def mattr_reader(*syms)
    options = syms.extract_options!
    syms.each do |sym|
      raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
      class_eval(<<-EOS, __FILE__, __LINE__ + 1)
        @@#{sym} = nil unless defined? @@#{sym}

        def self.#{sym}
          @@#{sym}
        end
      EOS

      unless options[:instance_reader] == false || options[:instance_accessor] == false
        class_eval(<<-EOS, __FILE__, __LINE__ + 1)
          def #{sym}
            @@#{sym}
          end
        EOS
      end
      class_variable_set("@@#{sym}", yield) if block_given?
    end
  end
  alias :cattr_reader :mattr_reader

  def mattr_writer(*syms)
    options = syms.extract_options!
    syms.each do |sym|
      raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
      class_eval(<<-EOS, __FILE__, __LINE__ + 1)
        @@#{sym} = nil unless defined? @@#{sym}

        def self.#{sym}=(obj)
          @@#{sym} = obj
        end
      EOS

      unless options[:instance_writer] == false || options[:instance_accessor] == false
        class_eval(<<-EOS, __FILE__, __LINE__ + 1)
          def #{sym}=(obj)
            @@#{sym} = obj
          end
        EOS
      end
      send("#{sym}=", yield) if block_given?
    end
  end
  alias :cattr_writer :mattr_writer

  def mattr_accessor(*syms, &blk)
    mattr_reader(*syms, &blk)
    mattr_writer(*syms, &blk)
  end
  alias :cattr_accessor :mattr_accessor
end
```

ソースコードをざっと見た感じ・・・
mattr_reader と mattr_writer の内容はほぼ同じような感じがしますね。
また、mattr_accessor は mattr_reader と mattr_writer を呼び出しているようです。

## ポイント1
```ruby:
options = syms.extract_options!
```

mattr_reader と mattr_writer でそれぞれ宣言されています。
内容は以下のファイルに宣言されています。

```ruby:gems/2.1.0/gems/activesupport-4.2.7.1/lib/active_support/core_ext/array/extract_options.rb

class Hash
  def extractable_options?
    instance_of?(Hash)
  end
end

class Array
  def extract_options!
    if last.is_a?(Hash) && last.extractable_options?
      pop
    else
      {}
    end
  end
end
```

[instance_of?](http://ref.xaio.jp/ruby/classes/object/instance_of)
>レシーバのオブジェクトが引数klassクラスのインスタンスであればtrue、そうでなければfalseを返します。

[is_a?](http://ref.xaio.jp/ruby/classes/object/kind_of)
>is_a?メソッドまたはkind_of?メソッドは、レシーバのオブジェクトが引数klassクラスのインスタンスであればtrue、そうでなければfalseを返します。

つまり、引数として渡された配列の最後をチェックして、Hashだったら取り出して変数 options に格納しています。

## ポイント2

```ruby:
raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
```

アクセサとして使用可能な名前かチェックしています。

## ポイント3
このメソッドのメイン処理とも言える部分です。

```ruby:
      class_eval(<<-EOS, __FILE__, __LINE__ + 1)
        @@#{sym} = nil unless defined? @@#{sym}

        def self.#{sym}
          @@#{sym}
        end
      EOS
```

[#class_eval?](http://ref.xaio.jp/ruby/classes/module/class_eval)
>class_evalメソッドの引数に文字列codeを渡すと、その文字列をクラス定義やモジュール定義の中のコードであるように実行します。
__FILE__ は実行ファイルのファイルパスを取得し、__LINE__ は定義されているメソッドの行番号を取得します。

つまり、アクセサを指定したファイルの次の行に、アクセサを定義しています！
class_eval、とても強力なメソッドですね・・・！

```ruby:
unless options[:instance_reader] == false || options[:instance_accessor] == false
  class_eval(<<-EOS, __FILE__, __LINE__ + 1)
    def #{sym}
      @@#{sym}
    end
  EOS
end
```

ポイント1で用意した変数 options を参照しています。
readerインスタンスメソッドの生成を制御しています。

ここまでが mattr_reader(所謂getter) のソースコードになります。

## ポイント4
mattr_writer(所謂setter)も同様に、class_evalメソッドを使用してアクセサを定義しています。

```ruby:
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
  @@#{sym} = nil unless defined? @@#{sym}

  def self.#{sym}=(obj)
    @@#{sym} = obj
  end
EOS

unless options[:instance_writer] == false || options[:instance_accessor] == false
  class_eval(<<-EOS, __FILE__, __LINE__ + 1)
    def #{sym}=(obj)
      @@#{sym} = obj
    end
  EOS
end
```

## 最後に
rails でアクセサを定義した際、どのような処理が実行されているのかをつかむことができました。
また class_eval メソッドの存在を初めて知り、他のメソッドの実装も見てみたくなりました。