47

Trying to implement an create if not exists else update record in Active Record.

Currently using:

@student = Student.where(:user_id => current_user.id).first

if @student
    Student.destroy_all(:user_id => current_user.id)
end

Student = Student.new(:user_id => current_user.id, :department => 1
)
Student.save!

What would be the correct way of updating the record if it exists or else create it?

7 Answers 7

84

In Rails 4

Student.
  find_or_initialize_by(:user_id => current_user.id).
  update_attributes!(:department => 1)
Sign up to request clarification or add additional context in comments.

5 Comments

find_or_initialize_by + update_attributes! will do the trick
The return value from update_attributes! is a boolean and not the actual record. You can use Student.find_or_initialize_by(...).tap { |e| e.update!(...) }
There's a couple of problems - first, you've got no way to "only set values on create", and second, it looks at table columns rather than model methods. For example, User.where(email: email).find_or_initialize_by(password: password) would give you an error of columns users.password does not exist (assuming you're sensible and not storing plain passwords in the database).
Nice solution. In Rails 4, I would only change .update_attributes to .update. It's the idiomatic way of doing it and the source code reveal it's the same as .update_attributes! (it will simply assign the attributes and call .save ... check apidock.com/rails/ActiveRecord/Persistence/update and apidock.com/rails/v3.2.3/ActiveRecord/Persistence/…).
Keep in mind that find_or_initialize_by may not trigger any validation
17

You may be looking for first_or_create or something similar:

http://guides.rubyonrails.org/v3.2.17/active_record_querying.html#first_or_create

5 Comments

first_or_create does not update the record, so it doesn't make code any nicer.
But it does return the found or created record, so you can chain an update call onto it. See my answer on this page.
This causes problems with validations on create and causes 2 separate db transactions. The answer from @Zorayr is preferable as it respects validations.
That's true. To get through validations, I think you can use first_or_initialize in exactly the same way as first_or_create; but you must call save or update on the result for it to be persisted.
@rune-schjellerup-philosof but you can update it after that .first_or_create(user_id: current_user.id, department: 1).update(department: 1)
15

::first_or_create (available since v3.2.1) does what it says on the box.

Model.where(find: 'find_value').
  first_or_create(create: 'create_value')

# If a record with {find: 'find_value'} already exists:
before #=> #<Model id: 1, find: "find_value", create: nil>
after  #=> #<Model id: 1, find: "find_value", create: nil>

# Otherwise:
before #=> nil
after  #=> #<Model id: 2, find: "find_value", create: "create_value">

If you also want it to update an already-existing record, try:

Model.where(find: 'find_value').
  first_or_create(create: 'create_value').
  update(update: 'update_value')

# If one already exists:
before #=> #<Model id: 1, find: "find_value", create: nil, update: nil>
after  #=> #<Model id: 1, find: "find_value", create: nil, update: "update_value">

# If it already matches, no UPDATE statement will be run:
before #=> #<Model id: 1, find: "find_value", create: nil, update: "update_value">
after  #=> #<Model id: 1, find: "find_value", create: nil, update: "update_value">

# Otherwise:
before #=> nil
after  #=> #<Model id: 2, find: "find_value", create: 'create_value', update: "update_value">

EDIT 2016-03-08: As per Doug's comment, if your validations fail between the #create and #update calls, or you want to minimise database calls, you can use ::first_or_initialize instead, to avoid persisting the record on the first call. However, you must make sure you call #save or #update afterwards in order to persist the record (and I'm not sure if #update works on records that haven't been persisted yet):

Model.validates :update, presence: true # The create call would fail this

Model.where(find: 'find_value').
  first_or_initialize(create: 'create_value'). # doesn't call validations
  update(update: 'update_value')

(NB. There is a method called #create_or_update, but don't be fooled by any documentation you may find on Google; that's just a private method used by #save.)

Comments

7
@student = Student.where(user_id: current_user.id).first
@student ||= Student.new(user_id: current_user.id)
@student.department_id = 1
@student.save

This is prettier if you have an association between a user and a student. Something along the lines of

@student = current_user.student || current_user.build_student
@student.department_id = 1
@student.save

EDIT:

You can also use http://guides.rubyonrails.org/active_record_querying.html#first_or_create as answered by sevenseacat but you still have to deal with different scenarios like update a student's department id.

UPDATE:

You can use find_or_create_by

@student = Student.find_or_create_by(user_id: current_user.id) do |student|
  student.department_id = 1
end

Comments

6

In Rails 5

Student.
  find_or_initialize_by(:user_id => current_user.id).
  update(:department => 1)

(with all credit taken from @Zorayr answer).

Comments

0

Unfortunately I think the cleanest way to do this is:

Student.where(user_id: id).first_or_create(age: 16).update_attribute(:age, 16)

Comments

-4

It's find_or_create_by not first_or_create.
Ex: Client.find_or_create_by(first_name: 'Andy')

2 Comments

first_or_create does exist, (even a year ago), I'm not sure what you were trying to say with this answer, but it sounds like it was meant as a comment on another post.
The two also work slightly differently. Model.where(find: 'find_value').first_or_create(create: 'create_value') is equivalent to Model.find_or_create_by(find: 'find_value') { |r| r.create = 'create_value' }.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.