LoginSignup
13
16

More than 5 years have passed since last update.

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

Posted at

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

目的

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

この記事で説明するメソッド名と役割

  • cattr_accessor
    • cattr_reader
    • cattr_writer

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

  • mattr_accessor
    • mattr_reader
    • mattr_writer

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

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

ソースコード

コメントを削除したものを載せます。
全部で57行!
なんとか読めそうな気がしますね。

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

options = syms.extract_options!

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

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?

レシーバのオブジェクトが引数klassクラスのインスタンスであればtrue、そうでなければfalseを返します。

is_a?

is_a?メソッドまたはkind_of?メソッドは、レシーバのオブジェクトが引数klassクラスのインスタンスであればtrue、そうでなければfalseを返します。

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

ポイント2

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

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

ポイント3

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

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

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

#class_eval?

class_evalメソッドの引数に文字列codeを渡すと、その文字列をクラス定義やモジュール定義の中のコードであるように実行します。
FILE は実行ファイルのファイルパスを取得し、LINE は定義されているメソッドの行番号を取得します。

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

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メソッドを使用してアクセサを定義しています。

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 メソッドの存在を初めて知り、他のメソッドの実装も見てみたくなりました。

13
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
16