ActiveRecord方法递减!也更新其他属性。这是为什么?

时间:2012-07-06 10:02:14

标签: ruby-on-rails activerecord

我有一个基本的身份验证系统,就像Michael Hartl的Ruby on Rails Tutorial一样。基本上,记忆令牌存储在cookie中。我从Railscast#124实现了Ryan Bate的Beta-Invitations,您可以在其中发送有限数量的邀请。在这样做时,我遇到了the current user got logged out after sending an invitation的问题。这是由邀请模型中的此代码引起的:

invitation.rb

belongs_to :sender, :class_name => 'User'
[...]
before_create :decrement_sender_count, :if => :sender
[...]
def decrement_sender_count
  sender.decrement! :invitation_limit
end

在日志中我看到了sender.decrement!不仅更新了invitation_limit,还更新了remember_token:

UPDATE "users" SET "invitation_limit" = 9982, "remember_token" = 'PYEWo_om0iaMjwltU4iRBg', "updated_at" = '2012-07-06 09:57:43.354922' WHERE "users"."id" = 1

我找到了一个ugly workaround,但我很想知道问题究竟是什么。由于我不知道从哪里开始,我将向用户控制器显示更新方法。还有什么可能相关?

users_controller.rb

def update
  @user = User.find(params[:id])
  if @user.update_attributes(params[:user])
    flash[:success] = t('success.profile_save')
    sign_in @user
    redirect_to @user
  else
    flash.now[:error] = t('error.profile_save')
    render 'edit'
  end
end

2 个答案:

答案 0 :(得分:2)

在我看来,您在ActiveRecord更新方法中遇到了常见的陷阱:

查看ActiveRecord文档here,您可以看到减量的实际实现!方法:

def decrement!(attribute, by = 1)
  decrement(attribute, by).update_attribute(attribute, self[attribute])
end

有趣的部分是在自身对象上调用的update_attribute - 虽然方法暗示ActiveRecord只会更新指定的属性,但它确实更新了自身对象的所有脏属性。

这意味着,如果在任何时候更改了对象的记忆标记属性,它将在update_attributes调用期间保存到数据库中。

如果我是对的,那就是问题 - 只需确保在运行时没有对remember_token属性进行任何更改。

除此之外,您可以考虑使用ActiveRecord's update_column方法更新数据库中的单个列,而不对对象执行保存方法

答案 1 :(得分:2)

decrement!调用save当然会触发保存回调。看起来这本书指导你做

before_save :create_remember_token
def create_remember_token
  self.remember_token = SecureRandom.urlsafe_base64
end

这意味着保存用户将始终使记忆令牌无效。我认为这是因为当用户更改密码时,记忆标记也会改变,但这意味着显然存在一些附带损害。

你可以使用decrement_counter本质上做

update users set counter_name = counter_name - 1 where id =12345

没有运行任何回调。这也避免了一些竞争条件。但是,每当用户更改时更改令牌必然会在您不期望的时候更改令牌 - 您可能只想在相关时更改令牌(可能在凭据更改时)