queryable_array
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:
== Testing
bundle exec rake