【问题标题】:update_attributes changes attributes even if validation fails即使验证失败,update_attributes 也会更改属性
【发布时间】:2025-12-29 20:10:17
【问题描述】:

例如,如果我在 prop1prop2 具有阻止这些值的验证时运行 test.update_attributes prop1: 'test', prop2: 'test2',则 test.prop1 仍将是 'test' 并且 test.prop2 仍将是 'test2'。为什么会发生这种情况,我该如何解决?

【问题讨论】:

    标签: ruby-on-rails database transactions update-attributes


    【解决方案1】:

    根据the Rails docs for update_attributes,它是update的别名。其来源如下:

    # File activerecord/lib/active_record/persistence.rb, line 247
    def update(attributes)
      # The following transaction covers any possible database side-effects of the
      # attributes assignment. For example, setting the IDs of a child collection.
      with_transaction_returning_status do
        assign_attributes(attributes)
        save
      end
    end
    

    所以,它被包装在一个数据库事务中,这就是发生回滚的原因。不过,让我们看看assign_attributes。根据its source

    # File activerecord/lib/active_record/attribute_assignment.rb, line 23
    def assign_attributes(new_attributes)
      ...
      _assign_attribute(k, v)
      ...
    end
    

    那个is defined as

    # File activerecord/lib/active_record/attribute_assignment.rb, line 53
    def _assign_attribute(k, v)
      public_send("#{k}=", v)
    rescue NoMethodError
      if respond_to?("#{k}=")
        raise
      else
        raise UnknownAttributeError.new(self, k)
      end
    end
    

    所以,当您拨打test.update_attributes prop1: 'test', prop2: 'test' 时,基本上可以归结为:

    test.prop1 = 'test'
    test.prop2 = 'test'
    test.save
    

    如果save 验证失败,我们的test 内存副本仍然具有修改后的prop1prop2 值。因此,我们需要使用test.reload,问题就解决了(即我们的数据库和内存版本都没有改变)。

    tl;drupdate_attributes 调用失败后使用test.reload

    【讨论】:

      【解决方案2】:

      尝试将其包装在 if 语句中:

      if test.update(test_params)
        # your code here
      else
        # your code here  
      end
      

      【讨论】:

      • 当我尝试这个时,testif 语句之后仍然有同样的问题。
      • if 声明并非总是可行的;例如,test.update! 仍然会导致同样的问题。
      【解决方案3】:

      这是按设计工作的。例如update 控制器方法通常如下所示:

      def update
        @test = Test.find(params[:id])
      
        if @test.update(test_attributes)
          # redirect to index with success messsage
        else
          render :edit
        end
      
        private
      
        def test_attributes
          # params.require here
        end
      end
      

      然后render :edit 将重新显示带有错误消息和错误值的表单供用户更正。因此,您实际上确实希望模型实例中存在不正确的值。

      【讨论】: