chewie 0.2.4

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.
@@ -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,64 @@
1
+ module Chewie
2
+ module Interface
3
+ module Bool
4
+
5
+ # @param attribute [Symbol] Field you wish to search
6
+ # @param with [Symbol] Specify the term-level query [term, terms, range]
7
+ # @param combine [Array] Target additional filter values to be combined in the formatted output (optional)
8
+ # @param format [Lambda] Define custom output with :combine values at runtime (optional)
9
+ # @return [Hash] A valid bool query
10
+ # @note See README#filtering-by-associations use case for :combine and :filter options
11
+ def filter_by(attribute, with:, combine: [], format: nil)
12
+ handler = {
13
+ query: :filter,
14
+ attribute: attribute,
15
+ with: with,
16
+ combine: combine,
17
+ format: format,
18
+ query_type: :bool,
19
+ }
20
+ set_handler(context: :bool, handler: handler)
21
+ end
22
+
23
+ # (see #filter_by)
24
+ def should_include(attribute, with:, combine: [], format: nil)
25
+ handler = {
26
+ query: :should,
27
+ attribute: attribute,
28
+ with: with,
29
+ combine: combine,
30
+ format: format,
31
+ query_type: :bool,
32
+ }
33
+ set_handler(context: :bool, handler: handler)
34
+ end
35
+
36
+ # (see #filter_by)
37
+ def must_not_include(attribute, with:, combine: [], format: nil)
38
+ handler = {
39
+ query: :must_not,
40
+ attribute: attribute,
41
+ with: with,
42
+ combine: combine,
43
+ format: format,
44
+ query_type: :bool,
45
+ }
46
+ set_handler(context: :bool, handler: handler)
47
+ end
48
+
49
+ # (see #filter_by)
50
+ def must_include(attribute, with:, combine: [], format: nil)
51
+ handler = {
52
+ query: :must,
53
+ attribute: attribute,
54
+ with: with,
55
+ combine: combine,
56
+ format: format,
57
+ query_type: :bool,
58
+ }
59
+
60
+ set_handler(context: :bool, handler: handler)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,44 @@
1
+ module Chewie
2
+ module Interface
3
+ module FullText
4
+
5
+ # @param attribute [Symbol] Field you wish to search
6
+ # @param context [Symbol] Desired context the query should appear (see https://www.elastic.co/guide/en/elasticsearch/reference/current/compound-queries.html)
7
+ # @param clause [Symbol] Specify a nested clause, usually context dependent (optional)
8
+ # @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
9
+ # @return [Hash] A valid "must" query
10
+ def match(attribute, context: :query, clause: nil, options: {})
11
+ handler = {
12
+ query: :match,
13
+ clause: clause,
14
+ attribute: attribute,
15
+ query_type: :full_text,
16
+ options: options,
17
+ }
18
+
19
+ set_handler(context: context, handler: handler)
20
+ end
21
+
22
+ # @param with [Array] A collection of field symbols to match against
23
+ # @param context [Symbol] Desired context the query should appear (see https://www.elastic.co/guide/en/elasticsearch/reference/current/compound-queries.html)
24
+ # @param clause [Symbol] Specify a nested clause, usually context dependent (optional)
25
+ # @param options [Hash] Options to augment search behavior
26
+ # @return [Hash] A valid "multi-match" query
27
+ def multimatch(with: [], context: :query, clause: nil, options: {})
28
+ if context == :compound
29
+ raise 'Please include a :clause value for compound queries.'
30
+ end
31
+
32
+ handler = {
33
+ query: :multimatch,
34
+ clause: clause,
35
+ with: with,
36
+ query_type: :full_text,
37
+ options: options,
38
+ }
39
+
40
+ set_handler(context: context, handler: handler)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,73 @@
1
+ module Chewie
2
+ module Interface
3
+ module TermLevel
4
+
5
+ # @param attribute [Symbol] Field you wish to search
6
+ # @param context [Symbol] Desired context the query should appear (see https://www.elastic.co/guide/en/elasticsearch/reference/current/compound-queries.html)
7
+ # @param clause [Symbol] Specify a nested clause, usually context dependent (optional)
8
+ # @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
9
+ # @return [Hash] A valid "fuzzy" query
10
+ def fuzzy(attribute, context: :query, clause: nil, options: {})
11
+ handler = {
12
+ query: :fuzzy,
13
+ clause: clause,
14
+ attribute: attribute,
15
+ query_type: :term_level,
16
+ options: options,
17
+ }
18
+ set_handler(context: context, handler: handler)
19
+ end
20
+
21
+ # @param attribute [Symbol] Field you wish to search
22
+ # @param context [Symbol] Desired context the query should appear (see https://www.elastic.co/guide/en/elasticsearch/reference/current/compound-queries.html)
23
+ # @param clause [Symbol] Specify a nested clause, usually context dependent (optional)
24
+ # @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
25
+ # @return [Hash] A valid "range" query
26
+ def range(attribute, context: :query, clause: nil, options: {})
27
+ handler = {
28
+ query: :range,
29
+ clause: clause,
30
+ attribute: attribute,
31
+ query_type: :term_level,
32
+ options: options
33
+ }
34
+
35
+ set_handler(context: context, handler: handler)
36
+ end
37
+
38
+ # @param attribute [Symbol] Field you wish to search
39
+ # @param context [Symbol] Desired context the query should appear (see https://www.elastic.co/guide/en/elasticsearch/reference/current/compound-queries.html)
40
+ # @param clause [Symbol] Specify a nested clause, usually context dependent (optional)
41
+ # @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
42
+ # @return [Hash] A valid "term" query
43
+ def term(attribute, context: :query, clause: nil, options: {})
44
+ handler = {
45
+ query: :term,
46
+ clause: clause,
47
+ attribute: attribute,
48
+ query_type: :term_level,
49
+ options: options
50
+ }
51
+
52
+ set_handler(context: context, handler: handler)
53
+ end
54
+
55
+ # @param attribute [Symbol] Field you wish to search
56
+ # @param context [Symbol] Desired context the query should appear (see https://www.elastic.co/guide/en/elasticsearch/reference/current/compound-queries.html)
57
+ # @param clause [Symbol] Specify a nested clause, usually context dependent (optional)
58
+ # @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
59
+ # @return [Hash] A valid "terms" query
60
+ def terms(attribute, context: :query, clause: nil, options: {})
61
+ handler = {
62
+ query: :terms,
63
+ clause: clause,
64
+ attribute: attribute,
65
+ query_type: :term_level,
66
+ options: options
67
+ }
68
+
69
+ set_handler(context: context, handler: handler)
70
+ end
71
+ end
72
+ end
73
+ 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