一次利用 byebug 和 RubyMine 刨根问底的过程
先看个现象:
class User
include Mongoid::Document
has_many :orders
end
class Order
include Mongoid::Document
belongs_to :user
end
user.orders.pluck :id # 正常返回id
user.orders.try :pluck, :id # 返回 nil
奇怪的是第二行居然返回nil
。user.orders.try :pluck, :id
返回nil
大致有以下三种可能:
user.orders
返回nil
,不过由于user.orders.pluck :id
可以正常返回 id,排除这种可能;orders.pluck :id
返回nil
,同上,也可排除这种可能;user.orders.try :pluck, :id
中的user.orders
上没有pluck
方法。这看起来是最不可能的情况,但是…
为了弄清原因,再来做个试验:
user.orders.try! :pluck, :id # NoMethodError: undefined method `pluck' for #<Array:0x0...>
这次我们用的是try!
而非try
,其结果正好符合上面的第3中情况。唯有这样假设方能解释得通:user.orders.pluck :id
中的user.orders
返回的是Mongoid::Criteria
;而user.orders.try :pluck, :id
中的user.orders
返回的是Array
。下面我们来借助byebug
和RubyMine
看看是否如此:
- 光标hover至一处调用
pluck
的地方,Go to Declaration(⌘B),搜索找到mongoid
中的定义gems/mongoid-5.0.1/lib/mongoid/contextual/mongo.rb:399
,加上byebug
:
def pluck(*fields)
byebug
normalized_select = fields.inject({}) do |hash, f|
hash[klass.database_field_name(f)] = 1
hash
end
view.projection(normalized_select).reduce([]) do |plucked, doc|
values = normalized_select.keys.map do |n|
n =~ /\./ ? doc[n.partition('.')[0]] : doc[n]
end
plucked << (values.size == 1 ? values.first : values)
end
end
- 打开 Rails Console,运行
user.orders.pluck :id
,然后运行backtrace
: - 定位到
gems/mongoid-5.0.1/lib/mongoid/relations/referenced/many.rb:414
:
def method_missing(name, *args, &block)
byebug
if target.respond_to?(name)
target.send(name, *args, &block)
else
klass.send(:with_scope, criteria) do
criteria.public_send(name, *args, &block)
end
end
end
再次借助byebug
不难发现这里的target
已经是orders
数组了,其class为Mongoid::Relations::Targets::Enumerable
。
由这里的代码得知:对于target支持的方法,直接在target上调用;target不支持的方法则在criteria
调用。拿我们前面的例子来说:
user.orders.pluck :id
中的targetorders
(Array)不支持pluck
方法,所以其实是在user.orders
返回的criteria
上调用的pluck
;而user.orders.try :pluck, :id
,中的 targetuser.orders
(Array)支持try
方法,所以直接在targetuser.orders
上调用了,又因Array
上没有pluck
,故而返回nil。
所以这样?
user.orders.try :map, &:id