I don't want the burden of error-checking the results, e.g. 'x.nil?' or
'x.size == 1', every time I issue a query of this type.
I haven't seen any good solution so far, and I'm considering overriding
'find' to take an ':exactly_one' option in addition to :first and :all,
raising RecordNotFoundException or TooManyRecordsException. Before I go
down that road I wanted to ask the mailing list if I'm missing
something.
I don't want the burden of error-checking the results, e.g. 'x.nil?' or
'x.size == 1', every time I issue a query of this type.
I haven't seen any good solution so far, and I'm considering overriding
'find' to take an ':exactly_one' option in addition to :first and :all,
raising RecordNotFoundException or TooManyRecordsException. Before I go
down that road I wanted to ask the mailing list if I'm missing
something.
Thanks!
Brian Hartin
The access you are asking for would be given by
class ActiveRecord::Base
def self.find_exactly_one(conditions)
return nil unless self.count(:conditions => conditions) == 1
self.find(:first, :conditions => conditions)
end
end
(or something close to this)
I don't know if you want to bother into one sql-statement.
Can you not put a uniqueness constraint on the column?
I don't want the burden of error-checking the results, e.g. 'x.nil?' or
'x.size == 1', every time I issue a query of this type.
I haven't seen any good solution so far, and I'm considering overriding
'find' to take an ':exactly_one' option in addition to :first and :all,
raising RecordNotFoundException or TooManyRecordsException. Before I go
down that road I wanted to ask the mailing list if I'm missing
something.
Thanks!
Brian Hartin
The access you are asking for would be given by
class ActiveRecord::Base
def self.find_exactly_one(conditions)
return nil unless self.count(:conditions => conditions) == 1
self.find(:first, :conditions => conditions)
end
end
(or something close to this)
I don't know if you want to bother into one sql-statement.
Can you not put a uniqueness constraint on the column?
Stephan
There is a uniqueness constraint, but that doesn't cover the case in
which the record doesn't exist. The behavior I'm shooting for is:
1) One and only one query is issued (your example issues two)
2) The conditions will usually reference a column(s) with a unique
index, but not necessarily (some dev shops don't use them - sigh)
3) It must raise a NoRecordFound if no record is found
4) It must raise a TooManyRecordsFound if more than one record is found
5) It must not interfere with other, expected find behavior
So far, I've got:
def self.find_exactly_one(conditions)
results = self.find(:all, :conditions => conditions)
raise RecordNotFound if results.size == 0
raise TooManyRecordsFound, "#{results.size} found." if results.size >
1
results[0] # Found just one
end
But this is only because I'm not yet ready to tackle replacing 'find'
itself. Ideally, I could do:
Patching it into find should be easy. Something like below (which is
aircoded and probably not correct) should get you started:
class ActionView::Base
# this method will become "find"
def find_with_exactly_one(*args)
options = args.extract_options!
case args.first
when :exactly_one then find_exactly_one(options)
else find_without_exactly_one(args, options)
end
end
# this makes "find_with_exactly_one" into "find"
# and turn the original "find" method into "find_without_exactly_one"
alias_method_chain :find, :exactly_one
def self.find_exactly_one(conditions)
results = self.find(:all, :conditions => conditions)
raise RecordNotFound if results.size == 0
raise TooManyRecordsFound, "#{results.size} found." if results.size
which the record doesn't exist. The behavior I'm shooting for is:
1) One and only one query is issued (your example issues two)
2) The conditions will usually reference a column(s) with a unique
index, but not necessarily (some dev shops don't use them - sigh)
3) It must raise a NoRecordFound if no record is found
4) It must raise a TooManyRecordsFound if more than one record is found
5) It must not interfere with other, expected find behavior
How something like this one:
class ActiveRecord::Base
def self.find_exactly_one(conditions)
results = self.find(:all, :condition => conditions)
raise 'None found' if results.blank? # might also be results==
raise 'Too many' if results.length > 1
results.first
end
end
I think that typically you use alias_method_chain when you want to
inject something into the call chain (ie., before/after processing,
adding callbacks) rather than simply extending the method. If you
only need to extend it, you're probably better adding an extension in
lib (often lib/core_ext when extending a core object) and then
including the functionality where you need it.
The find_exactly_on code is good (should probably be protected),
though you could simply:
def self.find
options = args.extract_options!
return find_exactly_one if args.first==:exactly_one # add your new
method when the conditions are right
super # give way to default if not
end
I think that typically you use alias_method_chain when you want to
inject something into the call chain (ie., before/after processing,
adding callbacks) rather than simply extending the method. If you
only need to extend it, you're probably better adding an extension in
lib (often lib/core_ext when extending a core object) and then
including the functionality where you need it.
The find_exactly_on code is good (should probably be protected),
though you could simply:
def self.find
options = args.extract_options!
return find_exactly_one if args.first==:exactly_one # add your new
method when the conditions are right
super # give way to default if not
end
On Apr 18, 8:04 pm, Nathan Esquenazi <rails-mailing-l...@andreas-
That implies that we either do this in a particular model or use a
common base class for all our models, no? For a monkeypatch, I'd have
to use alias_method_chain, right?
I prefer the common base-class approach. I'll give that a shot.
I think that typically you use alias_method_chain when you want to
inject something into the call chain (ie., before/after processing,
adding callbacks) rather than simply extending the method. If you
only need to extend it, you're probably better adding an extension in
lib (often lib/core_ext when extending a core object) and then
including the functionality where you need it.
The find_exactly_on code is good (should probably be protected),
though you could simply:
def self.find
options = args.extract_options!
return find_exactly_one if args.first==:exactly_one # add your new
method when the conditions are right
super # give way to default if not
end
On Apr 18, 8:04 pm, Nathan Esquenazi <rails-mailing-l...@andreas-
That implies that we either do this in a particular model or use a
common base class for all our models, no? For a monkeypatch, I'd have
to use alias_method_chain, right?
I prefer the common base-class approach. I'll give that a shot.
Thanks!
Brian
I've done this as a monkeypatch for now. Here's what I have:
# Add common behaviors to models
module ActiveRecord
class TooManyRecordsFound < ActiveRecordError
end
class Base
class << self
# Adds support for the new :exactly_one option
def find_with_exactly_one(*args)
options = args.last.is_a?(Hash) ? args.last : {}
if args.first == :exactly_one
find_exactly_one(options)
else
find_without_exactly_one(*args)
end
end
alias_method_chain :find, :exactly_one
protected
# Raises either TooManyRecordsFound or
# RecordNotFound if we don't find exactly
# one record.
def find_exactly_one(options)
results = find(:all, options)
raise RecordNotFound if results.size == 0
raise TooManyRecordsFound, "#{results.size} found." if
results.size > 1
results[0] # Found just one
end
end
end
end
I am not sure but I actually think Andy was right. I am not sure
alias_method_chain is the best method to use here BUT it works so I am
happy you at least got that worked up. Although I think Andy might have
been pointing you to a better solution (i.e included module extension)