queryable_array icon indicating copy to clipboard operation
queryable_array copied to clipboard

Provides a simplified DSL allowing arrays of objects to be searched by their attributes

= queryable_array - {}[http://travis-ci.org/shuber/queryable_array] {}[https://codeclimate.com/github/shuber/queryable_array] {}[https://codeclimate.com/github/shuber/queryable_array]

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[http://rubydoc.info/github/shuber/queryable_array/frames].

== Installation

gem install queryable_array

== Requirements

Ruby 1.9+

== Usage

=== Basic

Initialize the +QueryableArray+ with a collection of objects e.g. +Page+ objects from a JSON response or database query (although you should probably restrict database queries with WHERE conditions instead if you have the opportunity)

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

Try making your own classes with them

class Collection < Array include QueryableArray::Queryable end

pages = Collection.new Page.all pages.find_all(published: true) # => [#<Page @uri='/' @published=true>, #<Page @uri='/about' @published=true>]

=== Real world example

Try using it inside of your templates:

comments

== Testing

bundle exec rake