lib/queryable_array/queryable.rb
class QueryableArray < Array
# Allows +find_by+ and +find_all+ to accept search hashes which are
# converted into +Proc+ searches and passed as the block arguments for
# +find+ and +find_all+ respectively
module Queryable
# Returns a dup'd +Queryable+ replaced with objects matching the +search+
# criteria. When a +block+ is specified, it behaves exactly like
# +Enumerable#find_all+. Otherwise the +search+ hash is converted into
# a +finder+ proc and passed as the block argument to +Enumerable#find_all+.
#
# users.find_all(age: 30) # => [#<User @age=30>, #<User @age=30>, ...]
# users.find_all(name: 'missing') # => []
# users.find_all { |user| user.age < 30 } # => [#<User @age=22>, #<User @age=26>, ...]
def find_all(search = {}, &block)
block = finder search unless block_given?
dup.replace super(&block)
end
# Behaves exactly like +find_all+ but only returns the first match. If no match
# is found then +nil+ is returned.
#
# users.find_by(age: 25) # => #<User @age=25>
# users.find_by(name: 'missing') # => nil
def find_by(search = {}, &block)
block = finder search unless block_given?
find(&block)
end
# Accepts a +search+ hash and returns a +Proc+ which determines if all of an
# object's searched attributes match their expected values. It can be used as
# the block arguments for +find+, +find_by+ and +find_all+.
#
# Values are compared with expected values using <tt>==</tt> or <tt>===</tt>.
# If the expected value is a +Proc+ or anything that responds to +call+ then
# it is evaluated with the +value+ as an argument and checks for a response
# other than +nil+ or +false+.
#
# Searched attributes first check if the +object+ responds to the attribute
# so +NoMethodError+ is never thrown if an attribute doesn't exist.
#
# query = finder(name: 'bob') # => proc { |user| user.name == 'bob' } # pseudo code
# query User.new(name: 'steve') # => false
# query User.new(name: 'bob') # => true
#
# users.find(&query) # => #<User @name='bob'>
#
# users.find &finder(missing: 'value') # => nil
def finder(search)
Proc.new do |object|
search.all? do |attribute, expected|
value = object.send attribute if object.respond_to?(attribute)
expected == value || expected === value || (expected.respond_to?(:call) && expected.call(value))
end
end
end
end
end