先看个现象:
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