为什么在ActiveRecord关系上调用`length`会改变大小?

时间:2018-08-29 06:27:58

标签: ruby-on-rails rails-activerecord relationship

在Rails 4.2.10上,我有以下模型:

class RegistrationApproval < ActiveRecord::Base
  belongs_to :registering_user, class_name: 'User', inverse_of: :registration_approvals
  belongs_to :approver, class_name: 'User', inverse_of: :approved_registrations
end

在我的User模型中,我有这个:

has_many :registration_approvals, foreign_key: 'registering_user_id', dependent: :destroy, inverse_of: :registering_user
has_many :registration_approvers, through: :registration_approvals, class_name: 'User', source: :approver, inverse_of: :registering_users
has_many :approved_registrations, class_name: 'RegistrationApproval', foreign_key: 'approver_id', dependent: :destroy, inverse_of: :approver
has_many :registering_users, through: :approved_registrations, class_name: 'User', inverse_of: :registration_approvers

def approve_registration(approver)
  registration_approvals.create(approver: approver)
  Rails.logger.debug "approvers: #{registration_approvers.length}"
  return [registration_approvers.size, registration_approvers.count]
end

approve_registration方法中,调试语句更改该方法的输出。如果没有debug语句,则用户registration_approvers.size有两个批准的情况将按您的期望评估为2,但使用debug语句,则评估为1。为什么会这样?这是Rails 4.2中的错误吗?

count评估不受影响。

似乎是对registration_approvers.length的调用导致了size

的变化

这是我失败的测试,显示了此问题:

 it 'should have size and count equal' do
    registering_user = FactoryBot.create(:user)
    _(registering_user).must_be :persisted?
    approver1 = FactoryBot.create(:user)
    _(approver1).must_be :persisted?
    approver2 = FactoryBot.create(:user)
    _(approver2).must_be :persisted?
    registering_user.approve_registration(approver1)
    size, count = registering_user.approve_registration(approver2)
    _(count).must_equal 2
    _(size).must_equal 2
  end

除了size1以外最后一行中失败的断言,测试中的所有断言都通过。从registration_approvers.length方法中删除对approve_registration的调用后,测试通过。

这对我来说是个问题,因为在approve_registration方法中,我想用registration_approvers遍历each,但是当我这样做时,它错过了集合中的最后一个,大概是因为each方法在集合上调用length,并更改size的值,然后以为已到尽头就退出循环。

如果我可以找到一种无需调用length即可遍历该集合的变通方法,那会有所帮助。

2 个答案:

答案 0 :(得分:2)

#size在加载查询结果see the doc's code snippet时使用#length值。

#length不会触发新查询,而只是在(先前获得/缓存的)结果数组上调用#length。

OTOH:#count确实触发了一个新查询(实际上是在尚未加载结果时第一次触发该查询。

因此,要修复测试,您可以主动重新加载对象,从而刷新结果的缓存:

registering_user.approve_registration(approver1)
registering_user.reload
size, count = registering_user.approve_registration(approver2)

此外,我认为最好不要依赖动作的返回值进行统计。

答案 1 :(得分:0)

请记住,registration_approvers是ActiveRecord::Association对象,但我认为该类中没有定义长度,它可以从父级继承...

我认为查询的结果是数组,所以我不认为该长度是在该类中定义的,它可以从父级继承...我相信查询的结果是arrays

这是该方法的ruby源代码

static VALUE
rb_ary_length(VALUE ary)
{
    long len = RARRAY_LEN(ary);
    return LONG2NUM(len);
}

我需要您的一些意见/反馈