shuber/queryable_array

View on GitHub
lib/queryable_array/dynamic_finder.rb

Summary

Maintainability
A
0 mins
Test Coverage
class QueryableArray < Array
  module DynamicFinder
    # Allows objects to be searched by dynamic finders thru +method_missing+ similar
    # to the ActiveRecord dynamic attribute-based finders e.g. +find_by_email+ or
    # +find_all_by_last_name+
    def self.included(base)
      base.send :alias_method, :find_all_by, :find_all
    end

    # Determines if the +method_name+ passed to it can be parsed into a search hash
    # for +finder+.
    #
    #   finder? :find_by_name                      # => true
    #   finder? :find_by_first_name_and_last_name  # => true
    #   finder? :find_all_by_last_name             # => true
    #   finder? :find_all_by_last_name_and_city    # => true
    #   finder? :find_by_name_or_age               # => false
    #   finder? :find_first_by_name                # => false
    #   finder? :find_name                         # => false
    #   finder? :some_method                       # => false
    def finder?(method_name)
      if match = method_name.to_s.match(/^(find_(by|all_by))_(.+?)([\?\!])?$/i)
        keys = [:method_name, :prefix, :type, :attributes, :suffix]
        Hash[keys.zip match.to_a]
      end
    end

    # If +method_name+ is a +finder?+ then it creates a search hash by zipping the
    # names of attributes parsed from +method_name+ as keys and +arguments+ as expected
    # values. The search hash is then passed to +find_all+ or +find_by+ depending
    # on what type of method was called.
    #
    #   users = QueryableArray.new User.all, :username
    #
    #   users.find_by_name 'bob'              # => #<User @name='bob'>
    #   users.find_by_name_and_age 'jim', 23  # => #<User @name='jim' @age=23>
    #   users.find_all_by_age 27              # => [#<User @age=27>, #<User @age=27>, ...]
    def method_missing(method_name, *arguments)
      if query = finder?(method_name)
        search = Hash[query[:attributes].split('_and_').zip(arguments)]
        send "find_#{query[:type].downcase}", search
      else
        super
      end
    end

    # Determines if +method_name+ is a +finder?+ and delegates the call to its
    # superclass +Array+ if it's not.
    def respond_to_missing?(method_name, include_super)
      !!finder?(method_name) || super
    end
  end
end