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 值,并且由于 yield
是 run_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
在堆栈跟踪中。
我一直在使用 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 值,并且由于 yield
是 run_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
在堆栈跟踪中。