【问题标题】:Rails: Update model attribute without invoking callbacksRails:更新模型属性而不调用回调
【发布时间】:2012-01-28 21:00:21
【问题描述】:

我有一个具有 :credits 属性的用户模型。我想要一个简单的按钮,通过一个名为“add”的路由为用户的信用添加 5,以便 /users/3/add 为用户 id = 3 的信用添加 5。

def add
    @user = User.find(params[:id])
    @user.credits += 5
    redirect_to root_path
end

这是我的控制器的相关部分。问题是,我不想调用@user.save,因为我有一个 before_save 回调,它根据当前的 UTC 时间重新加密用户的密码。我只想简单的给属性加5,避免回调,没想到这么简单的事情竟然这么难。

编辑:

我将回调更改为 :before_create, 这是我的新控制器代码(相关部分):

  def add
    @user = User.find(params[:id])
    @user.add_credits(5)
    @user.save
    flash[:success] = "Credits added!"
    redirect_to root_path
  end

这是我在模型中的代码:

 def add_credits(num)
    self.credits = num
 end

编辑 2:

好的,这是一个验证问题,导致“编辑”中的更改不起作用,但我仍然希望能回答原始问题的答案,即在没有回调的情况下进行更新!

【问题讨论】:

  • 我提供了一个链接,其中包含不触发回调的方法列表,Finbarr 和我都建议使用条件回调——您还在寻找哪些其他解决方案?

标签: ruby-on-rails ruby model attributes callback


【解决方案1】:

作为一般答案,在 Rails 4 中,这是一种无需触发回调即可更新属性的简单方法:

@user.update_column :credits, 5

如果需要在不触发回调的情况下更新多个属性:

@user.update_columns credits: 5, bankrupt: false  

如果您愿意,还有其他选项 here in the Rails Guides,但我发现这种方式最简单。

【讨论】:

  • user.update_column credits:5 不起作用。它应该是 user.update_column 'credits', 5
  • 我使用的是 Rails 4.2,这绝对适合我。此行生成以下查询:UPDATE "users" SET "credits" = $1, "updated_at" = $2 WHERE "users"."id" = $3 [["credits", 5], ["updated_at", "2017-04-01 21:34:52.746626"], ["id", 1]]
  • 当使用update_column时,你不能传递一个像credits: 5这样的Object。您必须传递两个参数,第一个是列名,第二个是要更新的值。所以你可以使用update_column :credits, 5,这通常比使用字符串作为列名更受欢迎。
【解决方案2】:

您可以这样做更新列

User.where(name: 'lily').update_all(age: '10')

【讨论】:

    【解决方案3】:

    要在不使用回调的情况下更新多个属性,您可以在模型中使用 update_all,如下所示:

    self.class.update_all({name: value, name: value}, self.class.primary_key => id)
    

    如果您真的想要,您甚至可以尝试使用 update_columns 方法并将其混合到您的活动记录基类中。

    要更新一个属性,您可以使用 update_column。另外还有一些具体的方法可以在rails guideshttp://guides.rubyonrails.org/active_record_callbacks.html#skipping-callbacks找到

    【讨论】:

      【解决方案4】:

      对于 mongoid,我最终使用了 http://mongoid.org/en/mongoid/docs/persistence.html 具体来说,您可以使用:

      person.set(name:"Robert Pulson")
      

      并且不会发出回调。太酷了。

      【讨论】:

      • 它的 person.set(:name, "Robert Pulson")
      • @JudeCalimbas 不,不是,ArgumentError:参数数量错误(给定 2,预期 1)
      【解决方案5】:

      您有多种选择,包括更改您使用的回调,例如,after_create

      您可以在不触发回调的情况下更新列,请参阅 AR 指南中的 Skipping Callbacks。例如,update_column 不会触发回调。上一个链接列出了非触发函数。

      您还可以在更改密码时使用任何Conditional Callback 表单(甚至是观察者)。参见ActiveModel::Dirty,例如@user.password_changed?

      【讨论】:

      • 好吧,我把它改成了“after_create”,但还是不行!请参阅我对新代码的编辑,我无法更改积分。
      • @hatboysam 好吧,它做了一些不同的事情——在你添加之前,现在你正在设置。使用save! 查看是否有异常(和/或检查日志,和/或删除回溯消音器)。
      【解决方案6】:

      【讨论】:

        【解决方案7】:

        Rails 3.1 引入了update_column,与update_attribute 相同,但不触发验证或回调:

        http://apidock.com/rails/ActiveRecord/Persistence/update_column

        【讨论】:

        • 我很想使用它,但我无法干净地升级到 Rails 3.1,或者至少我不知道如何升级。
        • 您目前运行的是哪个 Rails 版本?
        【解决方案8】:

        我认为在这种情况下你应该使用方法update_counters。在您的控制器操作中像这样使用它:

        def add
          User.update_counters params[:id], :credits => 5
          redirect_to root_path
        end
        

        【讨论】:

          【解决方案9】:

          您应该能够使用update_all 来避免触发回调。

          def add
           @user = User.find(params[:id])
           User.where(:id=>@user.id).update_all(:credits => @user.credits+5)
           redirect_to root_path
          end
          

          我更愿意将此逻辑放入模型中,但这应该可以解决您在控制器中指定的原始问题。

          【讨论】:

            【解决方案10】:

            也许你的另一个 before_save 钩子应该在再次加密之前检查用户的密码是否真的改变了。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-09-29
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多