自连接关联nil的未定义方法:NilClass

时间:2014-09-03 01:40:51

标签: ruby-on-rails ruby rspec jqgrid associations

我有一个模型,Item,有三种类型,使用Single Table Inheritance实现。 Item具有树层次结构,使用has_many:through关系表示父项(称为组)和子项(称为sub_items)。其中一个子类(我们称之为ItemA)必须始终只有一个父类(但其他子类可以有0个或更多个父类)。我无法弄清楚如何实施强制执行树层次结构规则的验证,所以我把它们排除了。

ItemA有一个辅助方法parent来获取其父级。有时这种方法会引发undefined method 'first' for nil:NilClass。它并不总是发生,但在某些情况下确实如此。

情况1

我正在使用jqGrid列出所有ItemAs。我对某些列使用排序功能,包括父列。网格最初会成功加载ItemAs,表明他们都有父母应该这样做。但是,当我尝试对父列进行排序时,我会得到错误,好像它们突然消失了一样。当我从排序方法中删除includes(:groups)时,它就消失了。我不明白为什么会有所帮助,但我认为问题已经解决了。直到...

情况2

我正在使用Rspec,Factory Girl和Selenium测试我的应用程序。在我的一个测试中,我在使用Factory Girl创建的ItemA实例上调用parent方法。它引发了错误。我使用parent方法的测试间接不会失败。例如,在ItemA的索引页面上调用parent方法,并且许多测试都会毫无问题地访问该页面。这种情况尚未解决。我的应用程序不再对includes进行任何调用,因此这次不是其中的一部分。

相关代码

意达

class ItemA < Item
  # This method is called in Situation 1
  def self.sort_by_parent sort_order
    all.sort_by(&:parent_name).tap do |i|
      i.reverse! if sort_order == :desc
    end
  end

  def parent
    groups.take
  end

  def parent_name
    parent.component_name
  end
end

物品

class Item < ActiveRecord::Base
  has_many :item_groups, foreign_key: 'sub_item_id', dependent: :destroy
  has_many :groups, through: :item_groups
  has_many :group_items, class_name: 'ItemGroup', foreign_key: 'group_id', dependent: :destroy
  has_many :sub_items, through: :group_items
end

update_spec

feature 'ItemA editing', js: true do
  given!(:item_a) { create(:item_a) }
  given!(:parent) { create(:item_b) }

  scenario 'when parent', focus: true do
    itemas_page = ItemAsPage.visit # This is a custom page object

    # Situation 2 occurs here
    itemas_page.edit_parent item_a.parent_name, parent.component_name

    expect(itemas_page).to have_item_a_with parent.component_name
  end
end

为什么parent方法有时读为零,如何让它始终生成值?

编辑:当我更改我的代码时,我遇到了导致此错误的更多情况。我检查了来源。这是ActiveRecord::FinderMethods的快照:

module ActiveRecord::FinderMethods
  def take(limit = nil)
    limit ? limit(limit).to_a : find_take
  end

  private
  def find_take
    if loaded?
      @records.first
    else
      @take ||= limit(1).to_a.first
    end
  end
end

出于调试目的,我将parent方法修改为如下所示:

def parent
  groups.tap {|g| puts '@records: ' + g.instance_variable_get(:@records).inspect }.take
end

@records是零。我尝试将其更改为groups.reload.take以加载@records,但它无效。我现在正在使用groups.limit(1).to_a.first,这是有效的,但我很想知道我的应用中出现了什么类型的错误导致了这个问题。

1 个答案:

答案 0 :(得分:0)

事实证明,尽管调用reload,Rails仍然使用查询的缓存版本。 here描述了同样的问题。这解决了它:

class ItemA < Item
  def parent
    ActiveRecord::Base.uncached { groups.take }
  end
end