Skip to content

shuber/queryable_array

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

86 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

<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

About

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

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages