irb 中的块和调用范围行为不一致

Blocks and call scope in irb behaving inconsistantly

我一直在使用 spring 预加载器试验 Rails 4.2.7 中的一些内部 rails 代码,发现 irb 中堆栈转储的一些奇怪行为。

考虑以下代码

def run_block
  yield
end

class Vehicle < ActiveRecord::Base
  def self.instantiate(*args)
    puts caller_locations
    super
  end

  def self.test_stack
    run_block {Vehicle.all}
  end
end

请注意,我正在扩展实例化函数 (http://apidock.com/rails/ActiveRecord/Base/instantiate/class) 的功能以打印出调用堆栈。

当我打开 rails c 控制台和 运行

run_block {puts caller_locations}

我得到堆栈跟踪

test_irb/app/models/vehicle.rb:2:in `run_block'
(irb):4:in `irb_binding'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb/workspace.rb:87:in `eval'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb/workspace.rb:87:in `evaluate'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb/context.rb:380:in `evaluate'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb.rb:489:in `block (2 levels) in eval_input'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb.rb:623:in `signal_status'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb.rb:486:in `block in eval_input'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb/ruby-lex.rb:246:in `block (2 levels) in each_top_level_statement'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb/ruby-lex.rb:232:in `loop'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb/ruby-lex.rb:232:in `block in each_top_level_statement'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb/ruby-lex.rb:231:in `catch'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb/ruby-lex.rb:231:in `each_top_level_statement'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb.rb:485:in `eval_input'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb.rb:395:in `block in start'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb.rb:394:in `catch'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb.rb:394:in `start'
...

但是 运行ning(假设我在数据库中有一些行)

run_block {Vehicle.all}

我得到堆栈跟踪

.rvm/gems/ruby-head/gems/activerecord-4.2.7/lib/active_record/querying.rb:50:in `block (2 levels) in find_by_sql'
.rvm/gems/ruby-head/gems/activerecord-4.2.7/lib/active_record/result.rb:51:in `block in each'
.rvm/gems/ruby-head/gems/activerecord-4.2.7/lib/active_record/result.rb:51:in `each'
.rvm/gems/ruby-head/gems/activerecord-4.2.7/lib/active_record/result.rb:51:in `each'
.rvm/gems/ruby-head/gems/activerecord-4.2.7/lib/active_record/querying.rb:50:in `map'
.rvm/gems/ruby-head/gems/activerecord-4.2.7/lib/active_record/querying.rb:50:in `block in find_by_sql'
.rvm/gems/ruby-head/gems/activesupport-4.2.7/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
.rvm/gems/ruby-head/gems/activerecord-4.2.7/lib/active_record/querying.rb:49:in `find_by_sql'
.rvm/gems/ruby-head/gems/activerecord-4.2.7/lib/active_record/relation.rb:639:in `exec_queries'
.rvm/gems/ruby-head/gems/activerecord-4.2.7/lib/active_record/relation.rb:515:in `load'
.rvm/gems/ruby-head/gems/activerecord-4.2.7/lib/active_record/relation.rb:243:in `to_a'
.rvm/gems/ruby-head/gems/activerecord-4.2.7/lib/active_record/relation.rb:630:in `inspect'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb/inspector.rb:109:in `block in <module:IRB>'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb/inspector.rb:102:in `inspect_value'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb/context.rb:384:in `inspect_last_value'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb.rb:661:in `output_value'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb.rb:490:in `block (2 levels) in eval_input'
.rvm/rubies/ruby-head/lib/ruby/2.4.0/irb.rb:623:in `signal_status'

这并没有表明该块嵌套在跟踪中任何地方的 run_block 方法中。 运行 run_block {puts Vehicle.all} 但是确实在堆栈跟踪中显示了 run_block 函数。同样,块中的 运行ning Vehicle.first 显示了预期的堆栈跟踪。然而,上面定义的 Vehicle.test_stack 在堆栈跟踪中既不显示 run_block 方法也不显示 test_stack 方法。任何人都可以解释这种不一致吗?这是 ruby 语言构造的结果还是 .all 方法的实现?谢谢大家!

这是一个棘手的问题。 instantiate 堆栈跟踪未显示 run_block,因为 instantiate 未从 run_block.

调用

事情是这样的:当您调用 Vehicle.all 时,它不会 return 一个包含 Vehicle 个对象的数组,每个对象都已被实例化;它只是 return 一个 ActiveRecord::Relation 对象,表示尚未成为 运行 的查询。因为这是传递给 run_block 的块中的最后一个表达式,它是块的 return 值,并且由于 yieldrun_block 中的最后一个语句,ActiveRecord::Relation 对象也是该方法的 return 值。

但是,当您从 IRB 调用方法时,它会打印 returned 的内容,以调用 Object#inspect 结果的形式显示,当您调用 inspect 在 ActiveRecord::Relation 对象上,它会尝试在输出中包含表示关系中每个对象的字符串。为了那个,它实际上必须运行查询并实例化模型对象,所以它会这样做,最后调用你的instantiate覆盖。但那是在 run_block 块已经退出之后!

确实,如果你在一行上 运行(在 IRB 中)run_block {Vehicle.all} ; nil,IRB 会打印 nil 作为评估的结果,你的实例化方法永远不会根本没有打电话。

当您使用 run_block { puts Vehicle.all } 时,Relation 对象已实际实现为放置,它发生在块内,因此在这种情况下您 do 看到 run_block 在堆栈跟踪中。