421

Is there an alternative to update_attributes that does not save the record?

So I could do something like:

@car = Car.new(:make => 'GMC')
#other processing
@car.update_attributes(:model => 'Sierra', :year => "2012", :looks => "Super Sexy, wanna make love to it")
#other processing
@car.save

BTW, I know I can @car.model = 'Sierra', but I want to update them all on one line.

Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
tybro0103
  • 48,327
  • 33
  • 144
  • 170
  • what do you mean "not save the record"? – Anatoly Jul 21 '11 at 01:38
  • update_attributes saves the model the DB. I'm wondering if there's a similar method that doesn't. – tybro0103 Jul 21 '11 at 01:42
  • 3
    **attributes** non-destructive method. See [API](http://api.rubyonrails.org/classes/ActiveRecord/Base.html#method-i-attributes-3D) for details – Anatoly Jul 21 '11 at 01:47
  • 10
    For 3.1+, use `assign_attributes` http://apidock.com/rails/ActiveRecord/Base/assign_attributes – elado Jun 13 '12 at 08:34
  • 4
    You can use update_column(name, value) Updates a single attribute of an object, without calling save. 1. Validation is skipped. 2. Callbacks are skipped. 3. updated_at/updated_on column is not updated if that column is available. http://apidock.com/rails/ActiveRecord/Persistence/update_column – Alpha Beta Charlie May 09 '12 at 22:37

4 Answers4

647

I believe what you are looking for is assign_attributes.

It's basically the same as update_attributes but it doesn't save the record:

class User < ActiveRecord::Base
  attr_accessible :name
  attr_accessible :name, :is_admin, :as => :admin
end

user = User.new
user.assign_attributes({ :name => 'Josh', :is_admin => true }) # Raises an ActiveModel::MassAssignmentSecurity::Error
user.assign_attributes({ :name => 'Bob'})
user.name        # => "Bob"
user.is_admin?   # => false
user.new_record? # => true
Arel
  • 3,888
  • 6
  • 37
  • 91
Ajedi32
  • 45,670
  • 22
  • 127
  • 172
  • Your example is a little bit misleading since you haven't pasted this line from the model: `attr_accessible :is_admin, :as => :admin` ;) – Robin Sep 05 '12 at 16:12
  • @Robin Or simply: `attr_protected :is_admin`. Or: `attr_accessible :name` The point being that in this example, :is_admin is protected. I should also note that attempting to mass assign a protected attribute with `.assign_attributes` does indeed raise an `ActiveModel::MassAssignmentSecurity::Error`, even though that isn't shown in the example. – Ajedi32 Sep 05 '12 at 19:42
  • Yeah but my line is from the doc you linked to. I'm just saying you should have copied/pasted the whole example. But yes, you can just say that it's protected. – Robin Sep 05 '12 at 19:46
  • @Robin I'll update the example to be a bit more specific. The example in the docs is also a bit misleading, as it doesn't mention that `user.assign_attributes({ :name => 'Josh', :is_admin => true })` raises an error message and doesn't actually set the user's name property. – Ajedi32 Sep 05 '12 at 19:50
  • You're right, it does throw an error... strange that they don't mention it. – Robin Sep 05 '12 at 20:01
  • 7
    assign_attributes is available from Rails 3.1 onwards, so you can't use it if you're still running an old version of Rails. – Haegin Dec 06 '12 at 11:55
  • I have to look this up every time – stevenspiel Jul 21 '15 at 15:27
  • @mr.musicman Yeah, that's actually why I initially wrote this answer. I found myself looking it up way too often, and the StackOverflow question about it didn't have `assign_attributes` as an answer. – Ajedi32 Jul 21 '15 at 15:31
  • i am using assign attributes but getting a problem. i have a model templates that have many stages(nested model). when i can assign_attributes on templates, it save nested stages which is causing problem for me. – wasipeer Oct 29 '16 at 20:34
208

You can use assign_attributes or attributes= (they're the same)

Update methods cheat sheet (for Rails 6):

  • update = assign_attributes + save
  • attributes= = alias of assign_attributes
  • update_attributes = deprecated, alias of update

Source:
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/persistence.rb
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/attribute_assignment.rb

Another cheat sheet:
http://www.davidverhasselt.com/set-attributes-in-activerecord/#cheat-sheet

slhck
  • 36,575
  • 28
  • 148
  • 201
Yarin
  • 173,523
  • 149
  • 402
  • 512
  • 1
    Clear and short. Thanks. – freemanoid Oct 15 '14 at 12:47
  • 3
    in case of .attributes = val, if your model has_one and accepts_nested_attributes_for another model, passing that_model_attributes (without id) will delete the existing has_one model, even if you didn't persist (e.g. save). But assign_attributes doesn't behave like that. – ClassyPimp Jan 13 '16 at 14:05
67

You can use the 'attributes' method:

@car.attributes = {:model => 'Sierra', :years => '1990', :looks => 'Sexy'}

Source: http://api.rubyonrails.org/classes/ActiveRecord/Base.html

attributes=(new_attributes, guard_protected_attributes = true) Allows you to set all the attributes at once by passing in a hash with keys matching the attribute names (which again matches the column names).

If guard_protected_attributes is true (the default), then sensitive attributes can be protected from this form of mass-assignment by using the attr_protected macro. Or you can alternatively specify which attributes can be accessed with the attr_accessible macro. Then all the attributes not included in that won’t be allowed to be mass-assigned.

class User < ActiveRecord::Base
  attr_protected :is_admin
end

user = User.new
user.attributes = { :username => 'Phusion', :is_admin => true }
user.username   # => "Phusion"
user.is_admin?  # => false

user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
user.is_admin?  # => true
bbonamin
  • 30,042
  • 7
  • 40
  • 49
9

For mass assignment of values to an ActiveRecord model without saving, use either the assign_attributes or attributes= methods. These methods are available in Rails 3 and newer. However, there are minor differences and version-related gotchas to be aware of.

Both methods follow this usage:

@user.assign_attributes{ model: "Sierra", year: "2012", looks: "Sexy" }

@user.attributes = { model: "Sierra", year: "2012", looks: "Sexy" }

Note that neither method will perform validations or execute callbacks; callbacks and validation will happen when save is called.

Rails 3

attributes= differs slightly from assign_attributes in Rails 3. attributes= will check that the argument passed to it is a Hash, and returns immediately if it is not; assign_attributes has no such Hash check. See the ActiveRecord Attribute Assignment API documentation for attributes=.

The following invalid code will silently fail by simply returning without setting the attributes:

@user.attributes = [ { model: "Sierra" }, { year: "2012" }, { looks: "Sexy" } ]

attributes= will silently behave as though the assignments were made successfully, when really, they were not.

This invalid code will raise an exception when assign_attributes tries to stringify the hash keys of the enclosing array:

@user.assign_attributes([ { model: "Sierra" }, { year: "2012" }, { looks: "Sexy" } ])

assign_attributes will raise a NoMethodError exception for stringify_keys, indicating that the first argument is not a Hash. The exception itself is not very informative about the actual cause, but the fact that an exception does occur is very important.

The only difference between these cases is the method used for mass assignment: attributes= silently succeeds, and assign_attributes raises an exception to inform that an error has occurred.

These examples may seem contrived, and they are to a degree, but this type of error can easily occur when converting data from an API, or even just using a series of data transformation and forgetting to Hash[] the results of the final .map. Maintain some code 50 lines above and 3 functions removed from your attribute assignment, and you've got a recipe for failure.

The lesson with Rails 3 is this: always use assign_attributes instead of attributes=.

Rails 4

In Rails 4, attributes= is simply an alias to assign_attributes. See the ActiveRecord Attribute Assignment API documentation for attributes=.

With Rails 4, either method may be used interchangeably. Failure to pass a Hash as the first argument will result in a very helpful exception: ArgumentError: When assigning attributes, you must pass a hash as an argument.

Validations

If you're pre-flighting assignments in preparation to a save, you might be interested in validating before save, as well. You can use the valid? and invalid? methods for this. Both return boolean values. valid? returns true if the unsaved model passes all validations or false if it does not. invalid? is simply the inverse of valid?

valid? can be used like this:

@user.assign_attributes{ model: "Sierra", year: "2012", looks: "Sexy" }.valid?

This will give you the ability to handle any validations issues in advance of calling save.

Michael Gaskill
  • 7,913
  • 10
  • 38
  • 43