Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails method for only returning a single record and throwing an exception when multiple are returned

In Django, the ORM has a function called get() which is used to return only a single instance of a model. Specifically, it raises an exception if multiple objects are returned when you only expected one, so it's great for certain lookups (ie "Find the one article with this slug").

I haven't been able to find an analogous function in Rails/ActiveRecord that has the same behavior. So far, I've just been writing code like:

Model.where( ... ).first

But this has lead to silent bugs where multiple instances of an object were returned -- which is really a bad, ambiguous situation -- and we just grabbed the first one and carried on like everything was okay.

The Active Record Query Interface guide lists 5 ways of retrieving a single object, with find() and find_by() looking the most promising, but neither of them raises an exception when multiple objects match the search criteria.

I know that I could just write where() and find_by() queries and check the number of objects returned myself, but this code appears everywhere in our codebase and I'd rather not add all that cruft. It also seems like such a generic, common need that I'd expect this to be baked into Rails/ActiveRecord somewhere.

Is there some function I'm missing that'd make it easier to catch these situations? We're using Rails 4 and Ruby 2.0 if it helps.

like image 230
Hartley Brody Avatar asked Nov 05 '13 17:11

Hartley Brody


2 Answers

Rails 7.0 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:

Model.find_sole_by(conditions: 'here')
like image 79
SRack Avatar answered Sep 17 '22 12:09

SRack


I'm not aware of a built in Active Record method that does what you ask but it wouldn't be hard to write your own. Something like:

class YourModel
  def self.find_only_one_by_slug(slug)
    results = YourModel.where(slug: slug)
    if results.size > 1
      raise "There should only be one return value."
    else
      return results.first
    end
  end
end

I haven't tested the above code so there will most certainly be errors but you get the idea. You could even take this a step farther and add a similar method to active record base that all of your models would have access to.

like image 36
Josh Avatar answered Sep 21 '22 12:09

Josh