Railsのソースコード読んでみる | Active Support alias_attribute編

f:id:sktktk1230:20190921180106p:plain

普段仕事で使っているRuby on Railsですが、ソースコードを読む機会もなかなかないので、試しにやってみることにしました

読めるようにするまで

以前書いた記事で読めるようにするまでの設定を画像キャプチャ付きで解説しましたので、よろしければこちらをご参照下さい
shitake4.hatenablog.com

読んだ箇所

alias_attribute を今日は読んでみようと思います

どんな使い方だっけ?

読んでみる前にまずは使い方を調べてみます
RAILS GUIDESを見てみると

3.2 属性

3.2.1 alias_attribute

モデルの属性には、リーダー (reader)、ライター (writer)、述語 (predicate) があります。上に対応する3つのメソッドを持つ、モデルの属性の別名 (alias) を一度に作成することができます。他の別名作成メソッドと同様、1つ目の引数には新しい名前、2つ目の引数には元の名前を指定します (変数に代入するときと同じ順序、と覚えておく手もあります)。

class User < ActiveRecord::Base
  # emailカラムを"login"という名前でも参照したい
  # そうすることで認証のコードがわかりやすくなる
  alias_attribute :login, :email
end

引用:RAILS GUIDES

ソースコードを読んでみる

1. railsプロジェクトのactivesupportにある機能ですので、activesupportディレクトリのlib配下で def alias_attribute を探してみます

f:id:sktktk1230:20180423192006p:plain

2. 該当箇所が1箇所あったので、みてみます

1. activesupport > lib > active_support > core_ext > moudle > aliasing.rb
# frozen_string_literal: true

class Module
  # Allows you to make aliases for attributes, which includes
  # getter, setter, and a predicate.
  #
  #   class Content < ActiveRecord::Base
  #     # has a title attribute
  #   end
  #
  #   class Email < Content
  #     alias_attribute :subject, :title
  #   end
  #
  #   e = Email.find(1)
  #   e.title    # => "Superstars"
  #   e.subject  # => "Superstars"
  #   e.subject? # => true
  #   e.subject = "Megastars"
  #   e.title    # => "Megastars"
  def alias_attribute(new_name, old_name)
    # The following reader methods use an explicit `self` receiver in order to
    # support aliases that start with an uppercase letter. Otherwise, they would
    # be resolved as constants instead.
    module_eval <<-STR, __FILE__, __LINE__ + 1
      def #{new_name}; self.#{old_name}; end          # def subject; self.title; end
      def #{new_name}?; self.#{old_name}?; end        # def subject?; self.title?; end
      def #{new_name}=(v); self.#{old_name} = v; end  # def subject=(v); self.title = v; end
    STR
  end
end

module_eval メソッドに対して ヒアドキュメントで文字列(def〜STRの上の行まで)、__FILE____LINE__ + 1を渡しています

※ヒアドキュメントとは?
Rubyのヒアドキュメント 4パターンのまとめ -- ぺけみさお

第2引数、第3引数に記述されている __FILE__, __LINE__ +1 がわからなかったので、調べてみました

__FILE__

現在のソースファイル名

フルパスとは限らないため、フルパスが必要な場合は File.expand_path(__FILE__) とする必要があります。


__LINE__

現在のソースファイル中の行番号

フルパスとは限らないため、フルパスが必要な場合は File.expand_path(__FILE__) とする必要があります。

引用:#疑似変数

疑似変数と呼ばれる特殊な変数のようです。

実は nil,true,false も疑似変数でそれぞれのクラス(NilClass, TrueClass, FalseClass)の唯一のインスタンスが格納されているようです

module_evalを調べてみると

モジュールのコンテキストで文字列 expr またはモジュール自身をブロックパラメータとするブロックを 評価してその結果を返します。

モジュールのコンテキストで評価するとは、実行中そのモジュールが self になるということです。 つまり、そのモジュールの定義式の中にあるかのように実行されます。

ただし、ローカル変数は module_eval/class_eval の外側のスコープと共有します。

文字列が与えられた場合には、定数とクラス変数のスコープは自身のモジュール定義式内と同じスコープになります。 ブロックが与えられた場合には、定数とクラス変数のスコープはブロックの外側のスコープになります。

[PARAM] expr:

評価される文字列。

[PARAM] fname:

文字列を指定します。ファイル fname に文字列 expr が書かれているかのように実行されます。 スタックトレースの表示などを差し替えることができます。

[PARAM] lineno:

文字列を指定します。行番号 lineno から文字列 expr が書かれているかのように実行されます。 スタックトレースの表示などを差し替えることができます。

例:

class C
end
a = 1
C.class_eval %Q{
  def m                   # メソッドを動的に定義できる。
    return :m, #{a}
  end
}

p C.new.m        #=> [:m, 1]

引用:Ruby 2.5.0 リファレンスマニュアル#class_eval

つまり、moduleに動的にでセッターメソッド、ゲッターメソッドと真偽値を返すメソッドを定義しているということになります

qiita.com

読んでみて

__FILE____LINE__ はコード読んでいるとちょくちょく出てきてたので、そこは気にせず、読んでましたが、ちゃんと調べてみると新たな発見があったので、 ソースコードをしっかりと読み込むことは大切だと感じました