2

I'm new to Rails and was wondering about the following:

Is there a way to simplify this further?

  animal = Animal.find_by(name:name, type:type)
  if !animal
    animal = Animal.create(name:name, type:type)

I was thinking of using a ternary expression but i'm wondering how I would write that without repeating code or if this is the correct way of doing it.

animal = Animal.find_by(name:name, type:type) ? Animal.find_by(name:name, type:type) :  Animal.create(name:name, type:type);
lost9123193
  • 10,460
  • 26
  • 73
  • 113

3 Answers3

7

Try find_or_create_by

animal = Animal.find_or_create_by(name: name, type: type)
3limin4t0r
  • 19,353
  • 2
  • 31
  • 52
Mark
  • 6,112
  • 4
  • 21
  • 46
  • awesome! i'm guessing this works with multiple attributes? I'll double check if this is compatible with rails v5 – lost9123193 Nov 04 '19 at 20:59
  • 1
    If you are using rails 6 or higher then use `Animal.create_or_find_by(name:name, type:type)` to avoid possible race conditions. It might never become an issue in your current application. But it will save you from some grey hair in the future :) You can read why this method was introduced here: https://blog.bigbinary.com/2019/03/25/rails-6-adds-create_or_find_by.html – Ivan Denysov Nov 04 '19 at 21:12
  • @IvanDenysov if I'm using rails 5 do you know what's the best way that I can avoid the race conditions? – lost9123193 Nov 04 '19 at 21:24
  • 1
    @lost9123193 here's a great answer I found: https://stackoverflow.com/a/5917936/2886945 Looks a lot messier than built-in solution from rails 6 :) – Ivan Denysov Nov 04 '19 at 21:27
  • 2
    @IvanDenysov `create_or_find_by` (cf) has different behaviour than `find_or_create_by` (fc). cf first tries to create a record, if it fails due to a unique constraint the record is fetched instead. fc first tries to find the record, if it can't be found it is created. If there is no unique constraint over the *name*, *type* or *name/type* combo your suggestion will always create a record. – 3limin4t0r Nov 04 '19 at 22:12
1

In your specific scenario the answer of Mark is the better answer. However I'd like to offer a clean solution of what you currently have.

Ruby allows an inline modifier-if/unless statement. This would look like this:

animal = Animal.find_by(name: name, type: type)
animal = Animal.create(name: name, type: type) unless animal

You could also make use of the ||= operator here (which has my preference). This operator only assigns the right had value to the variable if the variable currently holds a falsy value (nil or false).

animal = Animal.find_by(name: name, type: type)
animal ||= Animal.create(name: name, type: type)

# or if you prefer the longer one-liner
animal = Animal.find_by(name: name, type: type) || Animal.create(name: name, type: type)
3limin4t0r
  • 19,353
  • 2
  • 31
  • 52
0

Just one more variation:

animal = Animal.find_by(name:name, type:type) || Animal.create(name:name, type:type)
user1934428
  • 19,864
  • 7
  • 42
  • 87