Josh Grosso
Josh Grosso

Reputation: 2124

update_attributes changes attributes even if validation fails

For example, if I run test.update_attributes prop1: 'test', prop2: 'test2' when prop1 and prop2 have validations that prevent those values, test.prop1 will still be 'test' and test.prop2 will still be 'test2'. Why is this happening and how can I fix it?

Upvotes: 0

Views: 870

Answers (3)

Matt
Matt

Reputation: 5398

This is working as designed. For example the update controller method usually looks like this:

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

The render :edit will then re-display the form with an error message and the incorrect values filled in for the user to correct. So you actually do want the incorrect values to be available in the model instance.

Upvotes: 0

Lymuel
Lymuel

Reputation: 574

Try wrapping it in an if-statement:

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

Upvotes: 0

Josh Grosso
Josh Grosso

Reputation: 2124

According to the Rails docs for update_attributes, it's an alias of update. Its source is as follows:

# 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

So, it's wrapped in a DB transaction which is why the rollback happens. However, let's check out assign_attributes. According to its source:

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

That 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

So, when you call test.update_attributes prop1: 'test', prop2: 'test', it basically boils down to:

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

If save fails the validations, our in-memory copy of test still has the modified prop1 and prop2 values. Hence, we need to use test.reload and the issue is resolved (i.e. our DB and in-memory versions are both unchanged).

tl;dr Use test.reload after the failed update_attributes call.

Upvotes: 1

Related Questions