queryable_array - <img src=“https://secure.travis-ci.org/shuber/queryable_array.png”/> <img src=“https://codeclimate.com/github/shuber/queryable_array/badges/gpa.svg” /> <img src=“https://codeclimate.com/github/shuber/queryable_array/badges/coverage.svg” />¶ ↑
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
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 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
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:
<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