chewie 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +650 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/.vscode/settings.json +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +59 -0
- data/LICENSE.txt +21 -0
- data/README.md +394 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/chewie.gemspec +38 -0
- data/lib/chewie.rb +47 -0
- data/lib/chewie/handler/reduced.rb +60 -0
- data/lib/chewie/interface/bool.rb +65 -0
- data/lib/chewie/interface/full_text.rb +47 -0
- data/lib/chewie/interface/term_level.rb +80 -0
- data/lib/chewie/query/bool.rb +51 -0
- data/lib/chewie/query/full_text.rb +60 -0
- data/lib/chewie/query/term_level.rb +49 -0
- data/lib/chewie/utils.rb +56 -0
- data/lib/chewie/version.rb +3 -0
- metadata +167 -0
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "chewie"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/chewie.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "chewie/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "chewie"
|
7
|
+
spec.version = Chewie::VERSION
|
8
|
+
spec.authors = ["mrjonesbot"]
|
9
|
+
spec.email = ["nate@mrjones.io"]
|
10
|
+
|
11
|
+
spec.summary = "A declarative interface for building Elasticsearch queries"
|
12
|
+
spec.homepage = "https://github.com/mrjonesbot/chewie"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.metadata["yard.run"] = "yri"
|
16
|
+
|
17
|
+
# spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
# spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
|
19
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
|
+
end
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
31
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
32
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
33
|
+
spec.add_development_dependency "pry"
|
34
|
+
spec.add_development_dependency "yard"
|
35
|
+
|
36
|
+
spec.add_runtime_dependency(%q<activesupport>, [">= 5.1.6"])
|
37
|
+
spec.add_runtime_dependency(%q<pry>)
|
38
|
+
end
|
data/lib/chewie.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require './lib/chewie/version'
|
2
|
+
require 'chewie/utils'
|
3
|
+
require 'chewie/interface/bool'
|
4
|
+
require 'chewie/interface/term_level'
|
5
|
+
require 'chewie/interface/full_text'
|
6
|
+
require 'chewie/handler/reduced'
|
7
|
+
require 'active_support/all'
|
8
|
+
require 'pry'
|
9
|
+
|
10
|
+
module Chewie
|
11
|
+
include Chewie::Interface::Bool
|
12
|
+
include Chewie::Interface::TermLevel
|
13
|
+
include Chewie::Interface::FullText
|
14
|
+
|
15
|
+
def handlers
|
16
|
+
@handlers ||= {
|
17
|
+
query: [],
|
18
|
+
bool: []
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def build(query: '', filters: {}, options: {})
|
23
|
+
query_data = set_query_data(query, filters)
|
24
|
+
bool_options = options[:bool] || {}
|
25
|
+
|
26
|
+
match_all_query(options) if query_data[:filters].empty?
|
27
|
+
|
28
|
+
context(:query, options) do
|
29
|
+
query_context = reduce_handlers(data: query_data)
|
30
|
+
bool_context = context(:bool, bool_options) do
|
31
|
+
reduce_handlers(data: query_data, context: :bool)
|
32
|
+
end
|
33
|
+
|
34
|
+
query_context.merge(bool_context)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
include Utils
|
41
|
+
|
42
|
+
def match_all_query(options)
|
43
|
+
context(:query, options) do
|
44
|
+
{ match_all: {} }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'chewie/query/term_level'
|
2
|
+
require 'chewie/query/bool'
|
3
|
+
require 'chewie/query/full_text'
|
4
|
+
|
5
|
+
module Chewie
|
6
|
+
module Handler
|
7
|
+
class Reduced
|
8
|
+
attr_reader :context, :query, :clause, :built_query, :clause_or_query
|
9
|
+
|
10
|
+
def initialize(context:, handler: {}, filters: {})
|
11
|
+
@context = context
|
12
|
+
@query = handler[:query]
|
13
|
+
@clause = handler[:clause]
|
14
|
+
@built_query = build_query(handler, filters)
|
15
|
+
@clause_or_query = (@clause || @query)
|
16
|
+
end
|
17
|
+
|
18
|
+
def reduce_with(handlers, hash)
|
19
|
+
has_one = handlers[query].one?
|
20
|
+
|
21
|
+
if has_one && is_top_level_query
|
22
|
+
set_in_hash(hash)
|
23
|
+
else
|
24
|
+
push_to_array(hash)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def set_in_hash(hash)
|
31
|
+
hash[clause_or_query] =
|
32
|
+
(hash[clause_or_query] || {}).merge(built_query)
|
33
|
+
end
|
34
|
+
|
35
|
+
def push_to_array(hash)
|
36
|
+
hash[clause_or_query] =
|
37
|
+
(hash[clause_or_query] || []).push(built_query)
|
38
|
+
end
|
39
|
+
|
40
|
+
def is_top_level_query
|
41
|
+
!(context == :bool && clause.present?)
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_query(handler, filters)
|
45
|
+
query_type = handler[:query_type]
|
46
|
+
|
47
|
+
case query_type
|
48
|
+
when :term_level
|
49
|
+
::Chewie::Query::TermLevel.new(handler, filters).build
|
50
|
+
when :bool
|
51
|
+
::Chewie::Query::Bool.new(handler, filters).build
|
52
|
+
when :full_text
|
53
|
+
::Chewie::Query::FullText.new(handler, filters).build
|
54
|
+
else
|
55
|
+
raise "Could not build a query for type: #{query_type}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Chewie
|
2
|
+
module Interface
|
3
|
+
module Bool
|
4
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html
|
5
|
+
#
|
6
|
+
# @param attribute [Symbol] Field you wish to search
|
7
|
+
# @param with [Symbol] Specify the term-level query [term, terms, range]
|
8
|
+
# @param combine [Array] Target additional filter values to be combined in the formatted output (optional)
|
9
|
+
# @param format [Lambda] Define custom output with :combine values at runtime (optional)
|
10
|
+
# @return [Hash] A valid bool query
|
11
|
+
# @note See README#filtering-by-associations use case for :combine and :filter options
|
12
|
+
def filter_by(attribute, with:, combine: [], format: nil)
|
13
|
+
handler = {
|
14
|
+
query: :filter,
|
15
|
+
attribute: attribute,
|
16
|
+
with: with,
|
17
|
+
combine: combine,
|
18
|
+
format: format,
|
19
|
+
query_type: :bool,
|
20
|
+
}
|
21
|
+
set_handler(context: :bool, handler: handler)
|
22
|
+
end
|
23
|
+
|
24
|
+
# (see #filter_by)
|
25
|
+
def should_include(attribute, with:, combine: [], format: nil)
|
26
|
+
handler = {
|
27
|
+
query: :should,
|
28
|
+
attribute: attribute,
|
29
|
+
with: with,
|
30
|
+
combine: combine,
|
31
|
+
format: format,
|
32
|
+
query_type: :bool,
|
33
|
+
}
|
34
|
+
set_handler(context: :bool, handler: handler)
|
35
|
+
end
|
36
|
+
|
37
|
+
# (see #filter_by)
|
38
|
+
def must_not_include(attribute, with:, combine: [], format: nil)
|
39
|
+
handler = {
|
40
|
+
query: :must_not,
|
41
|
+
attribute: attribute,
|
42
|
+
with: with,
|
43
|
+
combine: combine,
|
44
|
+
format: format,
|
45
|
+
query_type: :bool,
|
46
|
+
}
|
47
|
+
set_handler(context: :bool, handler: handler)
|
48
|
+
end
|
49
|
+
|
50
|
+
# (see #filter_by)
|
51
|
+
def must_include(attribute, with:, combine: [], format: nil)
|
52
|
+
handler = {
|
53
|
+
query: :must,
|
54
|
+
attribute: attribute,
|
55
|
+
with: with,
|
56
|
+
combine: combine,
|
57
|
+
format: format,
|
58
|
+
query_type: :bool,
|
59
|
+
}
|
60
|
+
|
61
|
+
set_handler(context: :bool, handler: handler)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Chewie
|
2
|
+
module Interface
|
3
|
+
module FullText
|
4
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html
|
5
|
+
#
|
6
|
+
# @param attribute [Symbol] Field you wish to search
|
7
|
+
# @param context [Symbol] Desired context the query should appear (see https://www.elastic.co/guide/en/elasticsearch/reference/current/compound-queries.html)
|
8
|
+
# @param clause [Symbol] Specify a nested clause, usually context dependent (optional)
|
9
|
+
# @param options [Hash] Options to augment search behavior: https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html#match-field-params
|
10
|
+
# @return [Hash] A valid "must" query
|
11
|
+
def match(attribute, context: :query, clause: nil, options: {})
|
12
|
+
handler = {
|
13
|
+
query: :match,
|
14
|
+
clause: clause,
|
15
|
+
attribute: attribute,
|
16
|
+
query_type: :full_text,
|
17
|
+
options: options,
|
18
|
+
}
|
19
|
+
|
20
|
+
set_handler(context: context, handler: handler)
|
21
|
+
end
|
22
|
+
|
23
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html
|
24
|
+
#
|
25
|
+
# @param with [Array] A collection of field symbols to match against
|
26
|
+
# @param context [Symbol] Desired context the query should appear (see https://www.elastic.co/guide/en/elasticsearch/reference/current/compound-queries.html)
|
27
|
+
# @param clause [Symbol] Specify a nested clause, usually context dependent (optional)
|
28
|
+
# @param options [Hash] Options to augment search behavior
|
29
|
+
# @return [Hash] A valid "multi-match" query
|
30
|
+
def multimatch(with: [], context: :query, clause: nil, options: {})
|
31
|
+
if context == :compound
|
32
|
+
raise 'Please include a :clause value for compound queries.'
|
33
|
+
end
|
34
|
+
|
35
|
+
handler = {
|
36
|
+
query: :multimatch,
|
37
|
+
clause: clause,
|
38
|
+
with: with,
|
39
|
+
query_type: :full_text,
|
40
|
+
options: options,
|
41
|
+
}
|
42
|
+
|
43
|
+
set_handler(context: context, handler: handler)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Chewie
|
2
|
+
module Interface
|
3
|
+
module TermLevel
|
4
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-fuzzy-query.html
|
5
|
+
#
|
6
|
+
# @param attribute [Symbol] Field you wish to search
|
7
|
+
# @param context [Symbol] Desired context the query should appear (see https://www.elastic.co/guide/en/elasticsearch/reference/current/compound-queries.html)
|
8
|
+
# @param clause [Symbol] Specify a nested clause, usually context dependent (optional)
|
9
|
+
# @param options [Lambda] Options to augment search behavior: https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-fuzzy-query.html#fuzzy-query-field-params
|
10
|
+
# @return [Hash] A valid "fuzzy" query
|
11
|
+
def fuzzy(attribute, context: :query, clause: nil, options: {})
|
12
|
+
handler = {
|
13
|
+
query: :fuzzy,
|
14
|
+
clause: clause,
|
15
|
+
attribute: attribute,
|
16
|
+
query_type: :term_level,
|
17
|
+
options: options,
|
18
|
+
}
|
19
|
+
set_handler(context: context, handler: handler)
|
20
|
+
end
|
21
|
+
|
22
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html
|
23
|
+
#
|
24
|
+
# @param attribute [Symbol] Field you wish to search
|
25
|
+
# @param context [Symbol] Desired context the query should appear (see https://www.elastic.co/guide/en/elasticsearch/reference/current/compound-queries.html)
|
26
|
+
# @param clause [Symbol] Specify a nested clause, usually context dependent (optional)
|
27
|
+
# @param options [Lambda] Options to augment search behavior: https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html#range-query-field-params
|
28
|
+
# @return [Hash] A valid "range" query
|
29
|
+
def range(attribute, context: :query, clause: nil, options: {})
|
30
|
+
handler = {
|
31
|
+
query: :range,
|
32
|
+
clause: clause,
|
33
|
+
attribute: attribute,
|
34
|
+
query_type: :term_level,
|
35
|
+
options: options
|
36
|
+
}
|
37
|
+
|
38
|
+
set_handler(context: context, handler: handler)
|
39
|
+
end
|
40
|
+
|
41
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html
|
42
|
+
#
|
43
|
+
# @param attribute [Symbol] Field you wish to search
|
44
|
+
# @param context [Symbol] Desired context the query should appear (see https://www.elastic.co/guide/en/elasticsearch/reference/current/compound-queries.html)
|
45
|
+
# @param clause [Symbol] Specify a nested clause, usually context dependent (optional)
|
46
|
+
# @param options [Lambda] Options to augment search behavior: https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html#term-field-params
|
47
|
+
# @return [Hash] A valid "term" query
|
48
|
+
def term(attribute, context: :query, clause: nil, options: {})
|
49
|
+
handler = {
|
50
|
+
query: :term,
|
51
|
+
clause: clause,
|
52
|
+
attribute: attribute,
|
53
|
+
query_type: :term_level,
|
54
|
+
options: options
|
55
|
+
}
|
56
|
+
|
57
|
+
set_handler(context: context, handler: handler)
|
58
|
+
end
|
59
|
+
|
60
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-query.html
|
61
|
+
#
|
62
|
+
# @param attribute [Symbol] Field you wish to search
|
63
|
+
# @param context [Symbol] Desired context the query should appear (see https://www.elastic.co/guide/en/elasticsearch/reference/current/compound-queries.html)
|
64
|
+
# @param clause [Symbol] Specify a nested clause, usually context dependent (optional)
|
65
|
+
# @param options [Lambda] Options to augment search behavior: https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-query.html#terms-top-level-params
|
66
|
+
# @return [Hash] A valid "terms" query
|
67
|
+
def terms(attribute, context: :query, clause: nil, options: {})
|
68
|
+
handler = {
|
69
|
+
query: :terms,
|
70
|
+
clause: clause,
|
71
|
+
attribute: attribute,
|
72
|
+
query_type: :term_level,
|
73
|
+
options: options
|
74
|
+
}
|
75
|
+
|
76
|
+
set_handler(context: context, handler: handler)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'chewie/utils'
|
2
|
+
|
3
|
+
module Chewie
|
4
|
+
module Query
|
5
|
+
class Bool
|
6
|
+
attr_reader :attribute, :query_format, :with, :value, :combine, :filters
|
7
|
+
|
8
|
+
def initialize(handler, filters)
|
9
|
+
@attribute = handler[:attribute]
|
10
|
+
@query_format = handler[:format]
|
11
|
+
@with = handler[:with]
|
12
|
+
@value = filters[attribute]
|
13
|
+
@combine = handler[:combine]
|
14
|
+
@filters = filters
|
15
|
+
end
|
16
|
+
|
17
|
+
def build
|
18
|
+
return {} if value.nil?
|
19
|
+
return [] if [value].flatten.empty?
|
20
|
+
|
21
|
+
context(with) do
|
22
|
+
{ attribute => exposed_value }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
include Utils
|
29
|
+
|
30
|
+
def should_format
|
31
|
+
query_format.present?
|
32
|
+
end
|
33
|
+
|
34
|
+
def exposed_value
|
35
|
+
expose_or_return_value(formatted_value, with, should_format)
|
36
|
+
end
|
37
|
+
|
38
|
+
def formatted_value
|
39
|
+
if should_format
|
40
|
+
format_values(value, combined, query_format)
|
41
|
+
else
|
42
|
+
value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def combined
|
47
|
+
combine_values(combine, filters)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'chewie/utils'
|
2
|
+
|
3
|
+
module Chewie
|
4
|
+
module Query
|
5
|
+
class FullText
|
6
|
+
attr_reader :attribute, :with, :query, :clause, :value, :options, :strategy
|
7
|
+
|
8
|
+
def initialize(handler, filters)
|
9
|
+
@attribute = handler[:attribute]
|
10
|
+
@with = handler[:with]
|
11
|
+
@query = handler[:query]
|
12
|
+
@clause = handler[:clause]
|
13
|
+
@value = filters[attribute] || filters[:query]
|
14
|
+
@options = handler.fetch(:options, {})
|
15
|
+
@strategy = clause.present? ? 'clause' : 'attribute'
|
16
|
+
end
|
17
|
+
|
18
|
+
def build
|
19
|
+
return {} if value.nil?
|
20
|
+
return [] if [value].flatten.empty?
|
21
|
+
|
22
|
+
send("create_with_#{strategy}")
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
include Utils
|
28
|
+
|
29
|
+
def create_with_clause
|
30
|
+
context(query) do
|
31
|
+
multimatch_query? ? multimatch_query : attribute_query
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_with_attribute
|
36
|
+
return multimatch_query if multimatch_query?
|
37
|
+
|
38
|
+
context(attribute) do
|
39
|
+
if !value.is_a? Hash
|
40
|
+
{ query: value }.merge(options)
|
41
|
+
else
|
42
|
+
value.merge(options)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def multimatch_query?
|
48
|
+
with.present?
|
49
|
+
end
|
50
|
+
|
51
|
+
def attribute_query
|
52
|
+
{ attribute => { query: value }.merge(options) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def multimatch_query
|
56
|
+
{ fields: with, query: value }.merge(options)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|