12

I really like the first_or_create method:

# Find the first user named Scarlett or create a new one with a particular last name.
User.where(:first_name => 'Scarlett').first_or_create(:last_name => 'Johansson')
# => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>

I was wondering how I could also have this update the User with the last_name 'Johannson' if it's not present or different. Looking for the briefest way of doing this. A one liner similar to the above would be ideal.

One possible approach is using first_or_initialize combined with update_attributes. The only concern I have with this approach is that it would run an update even if there were a 100% match on the fields provided.

Community
  • 1
  • 1
Ben G
  • 26,091
  • 34
  • 103
  • 170

2 Answers2

23

Zaid is VERY close to correct.

User.where(first_name: 'Scarlett').first_or_create.update(last_name: 'Johansson')

Detailed differences here. (Note: this is for Rails 3+ and Rails 4+)

  1. The difference between first_or_create vs. first_or_initialize is that _initialize uses the .new method and does not save the record vs .create and auto saves it.

  2. The difference between .update vs. .update_attributes, .update_attributes is what you use when you have a hash of values, typically coming from a form submit like params. On the other hand,.update lets you easily specify each attribute as shown above (field_column: value, field_column2: value2), etc.

And just like Zaid said, but it applies to both .update and .update_attributes, rails database updates "...only hits the database if there are changes to be made..."

Eric Wanchic
  • 2,046
  • 1
  • 23
  • 26
  • update is a private method. Calling it in this context gives "NoMethodError: private method `update' called..." You need to use update_attributes if you want to do it all in one line. – Mike T Sep 21 '14 at 11:42
  • I'm not sure how you coded this, but you shouldn't have any problems either way. I am currently using this in a view and controller in this format: ` Session.where(user_id: @user, legal_case_id: @case).first_or_create.update(last_active: now)` Update has always been a public method. (http://apidock.com/rails/ActiveRecord/Relation/update). Even if you are using .update_attributes, it's an alias to .update after Rails 3+ (http://apidock.com/rails/ActiveRecord/Persistence/update_attributes). So it "almost" should not matter either way. – Eric Wanchic Sep 21 '14 at 20:27
  • 2
    `update_attributes` will be remove in Rails 6.1: `DEPRECATION WARNING: update_attributes is deprecated and will be removed from Rails 6.1 (please, use update instead)` – Cris R Feb 11 '20 at 11:31
6

first_or_initialize with update_attributes should be fine. Rails is smart enough that update_attributes only hits the database if there are changes to be made (which you should be able to confirm for yourself using the console/logs).

zkcro
  • 4,344
  • 1
  • 24
  • 22
  • `update_attributes` will be remove in Rails 6.1: `DEPRECATION WARNING: update_attributes is deprecated and will be removed from Rails 6.1 (please, use update instead)` – Cris R Feb 11 '20 at 11:31