分享 一次利用 byebug 和 RubyMine 刨根问底的过程

qinfanpeng · 2016年05月19日 · 最后由 adamshen 回复于 2016年05月19日 · 3081 次阅读

一次利用 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

奇怪的是第二行居然返回niluser.orders.try :pluck, :id返回nil大致有以下三种可能:

  1. user.orders返回nil,不过由于user.orders.pluck :id可以正常返回 id,排除这种可能;
  2. orders.pluck :id 返回 nil,同上,也可排除这种可能;
  3. 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。下面我们来借助byebugRubyMine看看是否如此:

  1. 光标 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
    
  2. 打开 Rails Console,运行user.orders.pluck :id,然后运行backtrace:

  3. 定位到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

反正我感觉 rubymine 也就跳转 这个功能比较舒服了。。。

#1 楼 @catherine 对重构还是有一定的支持的。

确实,rubymine 一个好用的地方就是会自动扫描项目依赖的 gem 代码

除了少数元编程定义的方法,基本都能快速找到方法的源码

需要 登录 后方可回复, 如果你还没有账号请 注册新账号