queryable_array

<img src=“https://secure.travis-ci.org/shuber/queryable_array.png”/> <img src=“https://gemnasium.com/shuber/queryable_array.png”/> <img src=“https://d25lcipzij17d.cloudfront.net/badge.png?v=0.0.1”/> <img src=“https://codeclimate.com/badge.png” />

A QueryableArray inherits from Array and is intended to store a group of objects which share the same attributes allowing them to be searched. It overrides [], find_all and method_missing to provide a simplified DSL for looking up objects by querying their attributes.

View the full documentation over at rubydoc.info.

Installation

gem install queryable_array

Usage

Basic

Initialize the QueryableArray with a collection of objects e.g. Page objects from a database

pages = QueryableArray.new Page.all

The pages object can then be queried by passing a search hash to the [] method

pages[uri: '/']                    # => #<Page @uri='/' @name='Home'>
pages[name: 'About']               # => #<Page @uri='/about' @name='About'>
pages[uri: '/', name: 'Home']      # => #<Page @uri='/' @name='Home'>
pages[uri: '/', name: 'Mismatch']  # => nil

Notice that it only returns the first matching object or nil if one is not found. If you’d like to find all matching objects, simply wrap your search hash in an array

pages[[published: true]]  # => [#<Page @uri='/' @name='Home' @published=true>, #<Page @uri='/about' @name='About' @published=true>, ...]
pages[[uri: '/missing']]  # => []

Attributes may also be searched by regular expressions

pages[name: /home/i]   # => #<Page @uri='/' @name='Home'>
pages[[uri: /users/]]  # => [#<Page @uri='/users/bob' @name='Bob'>, #<Page @uri='/users/steve' @name='Steve'>, ...]

The methods find_by and find_all behave as aliases for [search_hash] and [[search_hash]] respectively

pages.find_by(name: 'Home')     # => #<Page @uri='/' @name='Home'>
pages.find_by(name: 'Missing')  # => nil
pages.find_all(uri: /users/)    # => [#<Page @uri='/users/bob' @name='Bob'>, #<Page @uri='/users/steve' @name='Steve'>, ...]

The existing block form for those methods work as well

pages.find_all { |page| page.uri =~ /users/ }  # => [#<Page @uri='/users/bob' @name='Bob'>, #<Page @uri='/users/steve' @name='Steve'>, ...]

A Proc object may be passed to [] as well

pages[uri: proc { |uri| uri.split('/').size > 1 }]  # => #<Page @uri='/users/bob' @name='Bob'>
pages[proc { |page| page.uri == '/' }]              # => #<Page @uri='/' @name='Home'>

Lookups by index or ranges still behave exactly as they do in regular Array objects

pages[0]     # => #<Page @uri='/' @name='Home'>
pages[-1]    # => #<Page @uri='/zebras' @name='Zebras'>
pages[99]    # => nil
pages[0..1]  # => [#<Page @uri='/' @name='Home'>, #<Page @uri='/about' @name='About'>]

Default finders

A QueryableArray object can be initialized with a default_finder to make lookups even simpler

pages = QueryableArray.new Page.all, :uri

Now the pages object can be searched easily by uri

pages['/']         # => #<Page @uri='/' @name='Home'>
pages['/about']    # => #<Page @uri='/about' @name='About'>
pages['/missing']  # => nil

You can even specify multiple default_finders

pages = QueryableArray.new Page.all, [:uri, :name]

pages['/about']  # => #<Page @uri='/about' @name='About'>
pages['About']   # => #<Page @uri='/about' @name='About'>
pages[/home/i]   # => #<Page @uri='/' @name='Home'>

Wrapping your search inside an array still returns all matches

pages[[/users/]]  # => [#<Page @uri='/users/bob' @name='Bob'>, #<Page @uri='/users/steve' @name='Steve'>, ...]

Dynamic attribute-based finders

QueryableArray#method_missing allows you to lookup objects using a notation like the ActiveRecord dynamic finders

pages.find_by_uri('/')                   # => #<Page @uri='/' @name='Home'>
pages.find_by_uri_and_name('/', 'Home')  # => #<Page @uri='/' @name='Home'>
pages.find_by_uri('/missing')            # => nil

pages.find_all_by_uri('/')               # => [#<Page @uri='/' @name='Home'>]
pages.find_all_by_uri(/users/)           # => [#<Page @uri='/users/bob' @name='Bob'>, #<Page @uri='/users/steve' @name='Steve'>, ...]

Dot notation finders

If any default_finders are defined you may even use dot notation to lookup objects by those attributes

pages = QueryableArray.new Page.all, :name

pages.sitemap               # => #<Page @uri='/sitemap' @name='Sitemap'>
pages.missing               # => NoMethodError
QueryableArray.new.missing  # => NoMethodError

Calling pages.sitemap behaves the same as pages[/sitemap/i]

To perform a case-sensitive search, simply append a ! to the end of your method call e.g. pages.sitemap! which calls pages['sitemap']

You may also query to see if a match exists by appending a ? to your search

pages.sitemap?  # => true
pages.missing?  # => false

Composable

Functionality for QueryableArray has been separated out into individual modules containing their own features which allows you to create your own objects and only include the features you care about

  • QueryableArray::DefaultFinder - Allows objects to be searched by default_finders thru []

  • QueryableArray::DotNotation - Allows objects to be searched using dot notation thru method_missing which behaves like an alias to QueryableArray::DefaultFinder#[]

  • QueryableArray::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

  • QueryableArray::Queryable - 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

  • QueryableArray::Shorthand - Makes [search_hash] and [[search_hash]] behave as an alias for find_by and find_all respectively

Real world example

Try using it inside of your templates:

<div class="posts">
  <%- posts[[published: true]].each do |post| -%>
    <div class="post">
      <h2><a href="<%= post.url -%>"><%= post.title -%></a></h2>
      <div class="excerpt"><%= post.excerpt -%></div>
      <a href="<%= post.url -%>#comments"><%= post.comments[[approved: true]].size -%> comments</a>
    </div>
  <%- end -%>
</div>

Testing

bundle exec rake