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 bydefault_finders
thru[]
-
QueryableArray::DotNotation
- Allows objects to be searched using dot notation thrumethod_missing
which behaves like an alias toQueryableArray::DefaultFinder#[]
-
QueryableArray::DynamicFinder
- Allows objects to be searched by dynamic finders thrumethod_missing
similar to the ActiveRecord dynamic attribute-based finders e.g.find_by_email
orfind_all_by_last_name
-
QueryableArray::Queryable
- Allowsfind_by
andfind_all
to accept search hashes which are converted intoProc
searches and passed as the block arguments forfind
andfind_all
respectively -
QueryableArray::Shorthand
- Makes[search_hash]
and[[search_hash]]
behave as an alias forfind_by
andfind_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