2015-03-14 20:12

Arelのto_sqlメソッドのソースコードを読んでみる(2)

準備は済んでる?

ここからは下記の記事で行った準備をベースとしてソースコードを読んでいきます。

準備がまだの方は、、
ソースコードリーディングの準備

また、Arelってなに?という方はこちらをご覧ください。
Arelのto_sqlメソッドのソースコードを読んでみる(1)

ブレークポイントを置く

binding.pryの挿入

ソースコードリーディングの準備で作成したarel/sample.rbを編集します。

36〜39行目を編集します。

編集前のコードは次の通りです。

arel/sample.rb(編集前)
             
             
35
36     ##################################################
37     ###このスペースに読みたいソースを呼び出す為のトリガーを記述する
38     puts song.project('*').where(song[:id].eq(1)).to_sql
39     ##################################################
40
             
             

これを次のように書き換えましょう。

arel/sample.rb(編集後)
             
             
35
36     ##################################################
37     a_song = song.project('*').where(song[:id].eq(1))
38     binding.pry
39     a_song.to_sql
40     ##################################################
41
             
             

38行目にbinding.pryを記述し、ブレークポイントを設置しています。

この状態でsample.rbを実行すればここで一時停止するハズです。

今回はto_sqlメソッドの実装を確認したいので、37行目でto_sqlを行う直前の状態を変数に格納しておきます。

そして、binding.pryの次の行で実際にto_sqlメソッドを使用しています。

デバッグの開始

デバッグをしながら処理の流れを追いかける

では、早速sample.rbを実行してみましょう。

terminal.app
$ bundle exec ruby sample.rb

すると次のような結果が返ってくると思います。

From: /Users/yae/arel/sample.rb @ line 38 :

       33: Arel::Table.engine = ActiveRecord::Base
       34: song = Arel::Table.new :songs
       35: 
       36: a_song = song.project('*').where(song[:id].eq(1))
       37: binding.pry
 => 38: a_song.to_sql
       39: 
       40: #DBの切断
       41: ActiveRecord::Base.connection.disconnect!

[1] pry(main)> 

予想通りbinding.pryの直後で処理が止まっています。

to_sqlメソッドの内部へ処理を進めましょう。

terminal.app
[1] pry(main)> step

From: /Users/yae/arel/vendor/bundle/ruby/2.1.0/gems/arel-6.0.0/lib/arel/tree_manager.rb @ line 28 Arel::TreeManager#to_sql:

       27: def to_sql
 => 28:   collector = Arel::Collectors::SQLString.new
       29:   collector = visitor.accept @ast, collector
       30:   collector.value
       31: end

[1] pry(#<Arel::SelectManager>)> 

このto_sqlメソッドの1行目では次のことが行われています。

terminal.app
[1] pry(#<Arel::SelectManager>)> step

From: /Users/yae/arel/vendor/bundle/ruby/2.1.0/gems/arel-6.0.0/lib/arel/collectors/sql_string.rb @ line 9 Arel::Collectors::SQLString#initialize:

         8: def initialize(*)
 =>   9:   super
       10:   @bind_index = 1
       11: end

[1] pry(#<Arel::Collectors::SQLString>)> step

From: /Users/yae/arel/vendor/bundle/ruby/2.1.0/gems/arel-6.0.0/lib/arel/collectors/plain_string.rb @ line 5 Arel::Collectors::PlainString#initialize:

       4: def initialize
 => 5:   @str = ''
       6: end

[1] pry(#<Arel::Collectors::SQLString>)> step

From: /Users/yae/arel/vendor/bundle/ruby/2.1.0/gems/arel-6.0.0/lib/arel/collectors/sql_string.rb @ line 10 Arel::Collectors::SQLString#initialize:

         8: def initialize(*)
         9:   super
 => 10:   @bind_index = 1
       11: end

[1] pry(#<Arel::Collectors::SQLString>)> step

From: /Users/yae/arel/vendor/bundle/ruby/2.1.0/gems/arel-6.0.0/lib/arel/tree_manager.rb @ line 29 Arel::TreeManager#to_sql:

       27: def to_sql
       28:   collector = Arel::Collectors::SQLString.new
 => 29:   collector = visitor.accept @ast, collector
       30:   collector.value
       31: end

[1] pry(#<Arel::SelectManager>)> 

だいたい予想はできていましたが、どうやらArel::Collectors::SQLStringクラスのインスタンスが変数collectorに格納されたようです。

試しに変数collectorの中身を確認してみましょう。

terminal.app
[1] pry(#<Arel::SelectManager>)> collector
=> #<Arel::Collectors::SQLString:0x007fa330ea0440 @bind_index=1, @str="">
[2] pry(#<Arel::SelectManager>)> 

2つのインスタンス変数がセットされている様子がわかります。

さて、次のステップですが、何かのクラスのインスタンス変数@astと、今作成した変数collectorの2つを引数にしてvisitor.acceptを行っています。

@astとvisitorは何者でしょう?

terminal.app
[2] pry(#<Arel::SelectManager>)> @ast
=> #<Arel::Nodes::SelectStatement:0x007fa33113dfe0
 @cores=
  [#<Arel::Nodes::SelectCore:0x007fa33113df90
    @groups=[],
    @having=nil,
    @projections=["*"],
    @set_quantifier=nil,
    @source=
     #<Arel::Nodes::JoinSource:0x007fa33113df68
      @left=#<Arel::Table:0x007fa33113e148 @aliases=[], @columns=nil, @engine=ActiveRecord::Base, @name="songs", @primary_key=nil, @table_alias=nil>,
      @right=[]>,
    @top=nil,
    @wheres=
     [#<Arel::Nodes::Equality:0x007fa33113dce8
       @left=
        #<struct Arel::Attributes::Attribute
         relation=#<Arel::Table:0x007fa33113e148 @aliases=[], @columns=nil, @engine=ActiveRecord::Base, @name="songs", @primary_key=nil, @table_alias=nil>,
         name=:id>,
       @right=
        #<Arel::Nodes::Casted:0x007fa33113dd10
         @attribute=
          #<struct Arel::Attributes::Attribute
           relation=#<Arel::Table:0x007fa33113e148 @aliases=[], @columns=nil, @engine=ActiveRecord::Base, @name="songs", @primary_key=nil, @table_alias=nil>,
           name=:id>,
         @val=1>>],
    @windows=[]>],
 @limit=nil,
 @lock=nil,
 @offset=nil,
 @orders=[],
 @with=nil>
[3] pry(#<Arel::SelectManager>)> visitor
=> #<Arel::Visitors::SQLite:0x007fa331155820
 @connection=
  #<ActiveRecord::ConnectionAdapters::SQLite3Adapter:0x007fa331199e80
   @active=nil,
   @config={:adapter=>"sqlite3", :database=>"sample.sqlite3"},
   @connection=
    #<SQLite3::Database:0x007fa331199fc0
     @authorizer=nil,
     @busy_handler=nil,
     @collations={},
     @encoding=#<Encoding:UTF-8>,
     @functions={},
     @readonly=false,
     @results_as_hash=true,
     @tracefunc=nil,
     @type_translation=nil>,
   @instrumenter=
    #<ActiveSupport::Notifications::Instrumenter:0x007fa331155cf8
     @id="fccf31a9c9e841e429f2",
     @notifier=
      #<ActiveSupport::Notifications::Fanout:0x007fa3316688a8
       @_mutex=#<Mutex:0x007fa3316687e0>,
       @listeners_for=
        #<ThreadSafe::Cache:0x007fa331668858
         @backend=
          {"sql.active_record"=>
            [#<ActiveSupport::Notifications::Fanout::Subscribers::Evented:0x007fa3313067a0
              @can_publish=false,
              @delegate=
               #<ActiveRecord::LogSubscriber:0x007fa3312fd1a0
                @odd=false,
                @patterns=["render_bind.active_record", "sql.active_record", "odd?.active_record", "logger.active_record"],
                @queue_key="ActiveRecord::LogSubscriber-70169440807120">,
              @pattern="sql.active_record">,
             #<ActiveSupport::Notifications::Fanout::Subscribers::Evented:0x007fa330bb4d80
              @can_publish=false,
              @delegate=#<ActiveRecord::ExplainSubscriber:0x007fa330bb5050>,
              @pattern="sql.active_record">]},
         @default_proc=nil>,
       @subscribers=
        [#<ActiveSupport::Notifications::Fanout::Subscribers::Evented:0x007fa331307150
          @can_publish=false,
          @delegate=
           #<ActiveRecord::LogSubscriber:0x007fa3312fd1a0
            @odd=false,
            @patterns=["render_bind.active_record", "sql.active_record", "odd?.active_record", "logger.active_record"],
            @queue_key="ActiveRecord::LogSubscriber-70169440807120">,
          @pattern="render_bind.active_record">,
         #<ActiveSupport::Notifications::Fanout::Subscribers::Evented:0x007fa3313067a0
          @can_publish=false,
          @delegate=
           #<ActiveRecord::LogSubscriber:0x007fa3312fd1a0
            @odd=false,
            @patterns=["render_bind.active_record", "sql.active_record", "odd?.active_record", "logger.active_record"],
            @queue_key="ActiveRecord::LogSubscriber-70169440807120">,
                      
                      
[4] pry(#<Arel::SelectManager>)> 

どちらも初めて登場するオブジェクトなのですが、色々と設定されています。

ということは、sample.rbの33〜37行目で変数a_songの準備をした際に作られたものだと推測できます。

気になる方は、binding.pryの位置を変えてsample.rbを実行してみてください。

visitorはArel::Visitors::SQLiteクラスの、@astはArel::Nodes::SelectStatementクラスのオブジェクトのようです。

ここまで少し長くなったので、続きは次回に。
Arelのto_sqlメソッドのソースコードを読んでみる(3)