4

I issue queries like the following that retrieve exactly 0 or 1 records:

car = Car.where(vin: '1234567890abcdefg')

What's returned is of course a list of cars of length 1. So I end up adding .first at the end of the query:

car = Car.where(vin: '1234567890abcdefg').first

Seems like there should be a better way in Rails. Does something like .onlyOne exist which would return just a single Car and throw an exception if there was more than 1?

car = Car.where(vin: '1234567890abcdefg').onlyOne

or

car = Car.onlyOne(vin: '1234567890abcdefg')
at.
  • 50,922
  • 104
  • 292
  • 461
  • 2
    `car = Car.where(vin: '1234567890abcdefg').first!` will throw an exception if it woun't find anything. – Magnuss Mar 25 '14 at 08:52
  • Possible duplicate of [Best way to find a single record using ActiveRecord 3 / Arel?](https://stackoverflow.com/questions/6326440/best-way-to-find-a-single-record-using-activerecord-3-arel) – Jon Schneider Sep 26 '18 at 20:49

5 Answers5

2

Rails 4

You want find_by(vin: 123), and you can pass find_by a hash of any field/value names to find by.

eg.

Car.find_by(vin: '123')

This will return nil if no car with that vin is found, or a single car matching that vin value. If you add a bang (ie. find_by!) it will raise an exception if no car with that vin is found.

If you want to raise an error if more than one is found, that is something you'll have to write for yourself. If you should never get into the case where more than one car has the same vin, the best way to do this would be with a uniqueness validation on the Car model, eg.

class Car < ActiveRecord::Base
  validates_uniqueness_of :vin # or validates :vin, unique: true
end

Rails 3

You want find_by_vin (and you can replace vin with the name of any of your attributes.

eg.

Car.find_by_vin('123')

This will return nil if no car with that vin is found, or a single car with that vin value. If you add a bang (ie. find_by_vin!) it will raise an exception if no car with that vin is found.

For more information: http://guides.rubyonrails.org/active_record_querying.html#retrieving-objects-from-the-database

sevenseacat
  • 24,699
  • 6
  • 63
  • 88
1
car = Car.find_by_vin('1234567890abcdefg')
sevenseacat
  • 24,699
  • 6
  • 63
  • 88
Kimooz
  • 941
  • 9
  • 10
1

I don't think anything exists to throw an exception where there's more than one but in rails 4:

car = Car.find_by(vin: '1234567890abcdefg') 

Would return the only one. The best way to enforce uniqueness is to do it on a model/db level:

class Car < ActiveRecord::Base
  validates :vin, uniqueness: true
end

If, for some reason, you do need to throw an exception when there is more than one car with that vin you could write a method on the model (or monkey patch Active record):

class Car < ActiveRecord::Base
  class << self

    def only_one!(options)
       list = where(options)
       raise "Exactly one car isn't available" if list.size > 1 or list.size == 0
       list.first
    end

  end
end

Or use a concern:

#lib/active_record_only_one.rb:
module ActiveRecordOnlyOne
  extend ActiveSupport::Concern

  module ClassMethods
    def only_one!(options)
      list = where(options)
      raise "Exactly one isn't available" if list.size > 1 or list.size == 0
      list.first
    end
  end
end

ActiveRecord::Base.send(:include, ActiveRecordOnlyOne)

.

#config/initializers/only_one.rb
require "active_record_only_one"

And then call:

Car.only_one!(vuid: "1234567890abcdefg")
Yule
  • 9,668
  • 3
  • 51
  • 72
  • I just realized I accidentally used lower camel case for `onlyOne` instead of the Ruby convention of snake case for `only_one`. – at. Mar 25 '14 at 18:50
0

You can use take and take!

car = Car.where(vin: '1234567890abcdefg').take

It returns nil if there is no record matching the given string.

car = Car.where(vin: '1234567890abcdefg').take!

It returns ActiveRecord::RecordNotFound if there is no record matching the given string

From the Guides(1.1.2,1.1.5 and 1.1.6)

car = Car.take  is equivalent to

SELECT * FROM cars LIMIT 1
Pavan
  • 33,316
  • 7
  • 50
  • 76
0

Rails 6.1 introduces find_sole_by (or where(...).sole) for this.

Seems to fit the bill perfectly:

Finds the sole matching record. Raises ActiveRecord::RecordNotFound if no record is found. Raises ActiveRecord::SoleRecordExceeded if more than one record is found.

Use case is simple:

Car.find_sole_by(vin: '1234567890abcdefg')
SRack
  • 11,495
  • 5
  • 47
  • 60