Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

Add find :unique to ActiveRecord

#2974

The current method of getting the result of a query is find :fisrt.
This will essentially do a query with max 1 row.

However, sometimes a developer creates a query that doesn't work the way he thought it would.

My suggestion is to add a new find :unique

This should do the basically same as find :first, except that it should not limit the number of records.
If more than 1 was found, it should raise an exception stating that there are too many records (and also the number of records found, just to inform the dev)

find :first should be deprecated for the use of getting a unique row from a query.

:first and :last have the same type of use. Finding the first or last in a list.

For example:

Person.find :unique, :conditions => { :ssid => ssid }
vs
Person.find :first, :conditions => { :ssid => ssid }

Also, the method_missing way of finding should use the unique.

Reported by Joris · July 30th, 2009 @ 03:46 PM

State: wontfix
Milestone: none
Assigned to: nobody
Importance: Low

Activity

  1. Joris
    Joris

    So, I read the bar at the right :)
    So, I didn't test this, I just typed it here, so I'm not sure if my manual diff is ok. anyway.. here's the code:

    In base.rb from activerecord:

    in find(*args):

        case args.first
          when :first  then find_initial(options)
          when :all    then find_every(options)
    
    •   when :unique then find_unique(options)
      

    add method:

        def find_initial(options)
          options.update(:limit => 1) unless options[:include]
          find_every(options).first
        end
    
    •  def find_unique(options)
      
    •    records = find_every(options)
      
    •    if records.size > 0 then
      
    •      raise ActiveRecordError, "Too many records found: #{records.size}"
      
    •    end
      
    •    records.first
      
    •  end
      

    Also, some calls to find_initial should be replaced by find_unique, but I can imagine that would break some existing apps. So that might be for later.

    July 30th, 2009 @ 03:57 PM

  2. Joris
    Joris

    So, I read the bar at the right :)
    So, I didn't test this, I just typed it here, so I'm not sure if my manual diff is ok. anyway.. here's the code:

    In base.rb from activerecord:

    in find(*args):

            case args.first

          when :first  then find_initial(options)
          when :all    then find_every(options)
    
    
    
    
    •   when :unique then find_unique(options)</code>
      
    
    
    
    
    add method:
            def find_initial(options)

          options.update(:limit =&gt; 1) unless options[:include]
          find_every(options).first
        end
    
    
    
    
    •  def find_unique(options)
      
    •    records = find_every(options)
      
    •    if records.size &gt; 0 then
      
    •      raise ActiveRecordError, &quot;Too many records found: #{records.size}&quot;
      
    •    end
      
    •    records.first
      
    •  end</code>
      
    
    
    
    
    Also, some calls to find_initial should be replaced by find_unique, but I can imagine that would break some existing apps. So that might be for later.

    July 30th, 2009 @ 04:01 PM

  3. Pratik
    Pratik

    Hey Joris,

    I think you should discuss this change in the ML first. Also, your patch should have tests. Check http://guides.rubyonrails.org/contributing_to_rails.html for more details.

    Thanks!

    July 30th, 2009 @ 04:58 PM

  4. Pratik
    Pratik
    • State changed from new to incomplete

    July 30th, 2009 @ 05:01 PM

  5. MatthewRudy
    MatthewRudy

    is this really the proposed implementation?

    def find_unique(options)
      records = find_every(options)
      if records.size > 0 then

    raise ActiveRecordError, &quot;Too many records found: #{records.size}&quot;
    
    
    
    
    end records.first end
    Seems a bit crazy,
    I'd rather have the two db calls (count, and find_first)

    Also, I don't get the use-case.
    Joris, do you have some examples of how you are using this feature?

    The only reason I've needed something similar is this case;

    class SomeSortOfSettings < ActiveRecord::Base
      def self.instance
        self.first || self.new
      end
    end
    

    namely a "Singleton" ActiveRecord,
    but I think there's a better way to deal with this
    (an ActiveRecord::Singleton class, perhaps)

    User.find(:unique, :conditions => {:username => "ralph"})
    

    is silly,
    you should just have a unique key on :username

    July 31st, 2009 @ 11:53 AM

  6. Joris
    Joris

    Having 2 queries would be a waste of db calls. If you expect 1 row, there's no need to do a count.

    All queries for something with a unique index / natural key is where I'd do this. Just because it's contextual more correct, and it has no performance drawbacks except for a single check if there's another row in the resultset.

    The implementation was just something I typed. I can imagine that there would be some other things that would need to happen, but basically that's how about what I had in mind.

    Hibernate has a similar feature: query.uniqueResult

    July 31st, 2009 @ 12:00 PM

  7. Pratik
    Pratik
    • State changed from incomplete to wontfix

    While I don't think this is gonna make it to the core, but the best implementation would probably be the one using :limit => 2.

    This seem very project specific, hence you should just keep it in /lib or maybe make a plugin and see if many people find it useful or not. We can always reconsider the feature if more people are using it.

    Thanks.

    July 31st, 2009 @ 12:01 PM

  8. Ryan Bigg
    Ryan Bigg
    • Tag cleared.
    • Importance changed from to Low

    Automatic cleanup of spam.

    October 11th, 2010 @ 10:55 AM