Daedal
This repository contains a set of Ruby classes designed to make ElasticSearch query creation simpler and easier to debug. The goal is to reproduce all components of the ElasticSearch Query DSL to aid in the construction of complex queries. Type checking and attribute coercion are handled using Virtus to make it harder to construct invalid ElasticSearch queries before sending them to the server.
The ElasticSearch Query DSL is huge! There are also a ton of different options within each component. My goal is to include as much of that functionality and flexibility as possible, but also maintain as high of test coverage as possible. That means it'll take some time for this project to reach full coverage of the Query DSL, so please feel free to contribute or be patient :)
Installation
$ gem install daedal
or in your Gemfile
gem 'daedal'
Usage
ElasticSearch Query DSL
Other Ruby packages for ElasticSearch allow you to create queries either as hashes or by constructing raw JSON:
match_query = {'match' => {'foo' => {'query' => 'bar'}}}
For more complicated queries, dealing with the resulting large nested hash can be frustrating. Inspired by ElasticSearch's built in Java API, Daedal contains Ruby classes designed to make query construction more Ruby-like.
Queries
Queries are contained within the Queries module. You can construct query components like:
require 'daedal'
# creates the basic match query
match_query = Daedal::Queries::MatchQuery.new(field: 'foo', query: 'bar')
Each query object has #to_json defined for easy conversion for use with any of the Ruby
ElasticSearch clients out there:
match_query.to_json # => "{\"match\":{\"foo\":{\"query\":\"bar\"}}}"
To date (12/8/2013), I have implemented the following queries:
- bool query
- constant score query
- dis max query
- filtered query
- match all query
- match query
- multi match query
Filters
Filters are contained within the Filters module. You can construct filter components
in the same way as queries:
require 'daedal'
term_filter = Daedal::Filters::TermFilter.new(field: 'foo', term: 'bar')
term_filter.to_json # => "{\"term\":{\"foo\":\"bar\"}}"
To date (12/8/2013), I have implemented the following filters:
Type checking and attribute coercion
When creating ElasticSearch queries via nested hashes, it is all too easy to assign an invalid value to a specific field, which would then result in an error response when sending the query to the server. For instance, the query:
constant_score_query = {'constant_score' => {'boost' => 'foo', 'query': {'match_all': {}}}}
would yield a server error, since the boost parameter must be a number.
Daedal uses Virtus to perform data-type coercions.
That way, invalid query parameters are surfaced at runtime, making debugging easier.
The previous constant_score_query example in Daedal would raise an error:
match_all_query = Daedal::Queries::MatchAllQuery.new()
constant_score_query = Daedal::Queries::ConstantScoreQuery.new(boost: 'foo', query: match_all_query)
# Virtus::CoercionError: Failed to coerce "foo" into Float
Creating your own queries
Currently, I've only made it through a fraction of the entire Query DSL, but will be working to
achieve complete coverage as quickly as possible. If you need to use a query that
I haven't gotten to yet, or if you want to create your own more specialized queries within
your own project, defining the query classes is relatively straightforward - just make
your new class a sublass of the Daedal::Queries::BaseQuery or Daedal::Filters::BaseFilter
classes, and define the to_hash method. All methods made available by including Virtus.model in your
class will be available to you, as well.
Example:
class MyQuery < Daedal::Queries::BaseQuery
# define the attributes that you need in your query
attribute :foo, String
attribute :bar, String
# define the to_hash method to convert for use in ElasticSearch
def to_hash
...
end
end
Contributing
The ElasticSearch Query DSL is pretty large and includes a ton of nuance. I'm starting with the most basic parts of the DSL (and the parts I use for work), so if you want to help out with the project to meet your needs please feel free to contribute! I just ask that you:
- Fork the project.
- Make your changes or additions.
- Add tests! My goal is complete test coverage, so please take the effort to make them pretty comprehensive.
- Send me a pull request.
Feedback or suggestions are also always welcome.
License
The MIT License (MIT)
Copyright (c) 2013 Christopher Schuch
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.