chewie 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -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__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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
@@ -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