es_tractor 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8c7ccbb02ef8a6f0bb4b970cc28f963c55952a2e
4
+ data.tar.gz: ce0bd10580ac1d4adc9d4ff0c48acf21e5bfaa0c
5
+ SHA512:
6
+ metadata.gz: 198ab52863d622b2fcbb9e8a9d752a72e8abe00f6846aebebd56680e7bebb707dcedc7b49629205cae656fdd8951279b115afe4a6ccdadc25b4b2e897a36b25b
7
+ data.tar.gz: b8e4d5d4925becc313fa08cf8eb3ebb3f42710b168ba47845646739998a6b3ac854bc2dc7f8419f99a292909e5c2a504e6a3567b2c316436dbfa4ecf51a2b7d9
data/.autotest ADDED
@@ -0,0 +1,25 @@
1
+ # -*- ruby -*-
2
+
3
+ require "autotest/restart"
4
+
5
+ # Autotest.add_hook :initialize do |at|
6
+ # at.testlib = "minitest/unit"
7
+ #
8
+ # at.extra_files << "../some/external/dependency.rb"
9
+ #
10
+ # at.libs << ":../some/external"
11
+ #
12
+ # at.add_exception "vendor"
13
+ #
14
+ # at.add_mapping(/dependency.rb/) do |f, _|
15
+ # at.files_matching(/test_.*rb$/)
16
+ # end
17
+ #
18
+ # %w(TestA TestB).each do |klass|
19
+ # at.extra_class_map[klass] = "test/test_misc.rb"
20
+ # end
21
+ # end
22
+
23
+ # Autotest.add_hook :run_command do |at|
24
+ # system "rake build"
25
+ # end
data/.rubocop.yml ADDED
@@ -0,0 +1,17 @@
1
+ Layout/EmptyLineAfterMagicComment:
2
+ Enabled: false
3
+
4
+ AllCops:
5
+ TargetRubyVersion: '2.4.1'
6
+ Metrics/AbcSize:
7
+ Max: 20
8
+ Metrics/BlockLength:
9
+ Enabled: false
10
+ Metrics/MethodLength:
11
+ Max: 20
12
+ Style/TrailingCommaInArguments:
13
+ Enabled: false
14
+ Style/TrailingCommaInLiteral:
15
+ Enabled: false
16
+ Metrics/LineLength:
17
+ Max: 78
data/History.txt ADDED
@@ -0,0 +1,12 @@
1
+ === 0.0.1 / 2017-08-31
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
7
+ === 0.0.2 / 2017-09-04
8
+
9
+ * 1 major enhancement
10
+
11
+ * Search, count, filter
12
+
data/Manifest.txt ADDED
@@ -0,0 +1,12 @@
1
+ .autotest
2
+ .rubocop.yml
3
+ History.txt
4
+ Manifest.txt
5
+ README.txt
6
+ Rakefile
7
+ bin/console
8
+ lib/es_tractor.rb
9
+ lib/es_tractor/client.rb
10
+ test/helper.rb
11
+ test/test_client.rb
12
+ test/test_es_tractor.rb
data/README.txt ADDED
@@ -0,0 +1,76 @@
1
+ # @markup markdown
2
+ # EsTractor: Simplified data extracton from Elasticsearch
3
+
4
+ [[Home]](https://github.com/utilum/es_tractor)
5
+
6
+ ## DESCRIPTION:
7
+
8
+ Sub-set of Search APIs with DRY query building.
9
+
10
+ ## FEATURES/PROBLEMS:
11
+
12
+ - Subset of Search APIs: count, search.
13
+ - Minimal DRY query builder maps root elements of a Hash argument into boolean
14
+ filters.
15
+
16
+ - TODO:
17
+ - Add Search APIs:
18
+ - Fields
19
+ - Sort
20
+ - Scroll
21
+ - Aggregations
22
+ - Report formats: CSV, JSON.
23
+ - Extraction: Aggregation to flat records (separate project?).
24
+
25
+ ## SYNOPSIS:
26
+
27
+ ```
28
+ include EsTractor
29
+ tractor = Client.new
30
+
31
+ tractor.count
32
+ # => (Integer) number of documents on the server
33
+
34
+ tractor.count(term: { my_field: 'my precise term' })
35
+ # => (Integer) number of documents where my_field == 'my precise term'
36
+ ```
37
+
38
+ ## REQUIREMENTS:
39
+
40
+ Some environment variables are expected:
41
+ - L2B_ELASTICSEARCH_HOST
42
+ - L2B_ELASTICSEARCH_INDEX
43
+
44
+ ## DEVELOPERS:
45
+
46
+ After checking out the source, run:
47
+
48
+ $ rake newb
49
+
50
+ This task will install any missing dependencies, run the tests/specs,
51
+ and generate the RDoc.
52
+
53
+ ## LICENSE:
54
+
55
+ (The MIT License)
56
+
57
+ Copyright (c) 2017 FIX
58
+
59
+ Permission is hereby granted, free of charge, to any person obtaining
60
+ a copy of this software and associated documentation files (the
61
+ 'Software'), to deal in the Software without restriction, including
62
+ without limitation the rights to use, copy, modify, merge, publish,
63
+ distribute, sublicense, and/or sell copies of the Software, and to
64
+ permit persons to whom the Software is furnished to do so, subject to
65
+ the following conditions:
66
+
67
+ The above copyright notice and this permission notice shall be
68
+ included in all copies or substantial portions of the Software.
69
+
70
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
71
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
72
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
73
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
74
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
75
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
76
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'rubygems'
5
+ require 'hoe'
6
+
7
+ Hoe.plugin :yard
8
+
9
+ Hoe.spec 'es_tractor' do
10
+ developer('utilum', 'oz@utilum.com')
11
+
12
+ license 'MIT'
13
+
14
+ extra_deps << ['elasticsearch', '~> 5.0', '>= 5.0.4']
15
+
16
+ extra_dev_deps << ['hoe-yard', '~> 0.1', '>=0.1.3']
17
+ extra_dev_deps << ['minitest', '~> 5.10', '>=5.10.3']
18
+ extra_dev_deps << ['mocha', '~> 1.3', '>=1.3.0']
19
+ extra_dev_deps << ['pry', '~> 0.10.4']
20
+ end
21
+
22
+ namespace :es_tractor do
23
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
24
+ require 'es_tractor'
25
+
26
+ include EsTractor
27
+
28
+ namespace :demo do
29
+ desc 'Count all docuements since forever'
30
+ task :count_all do
31
+ tractor = Client.new
32
+ r = tractor.count
33
+ puts "Found #{r} documents"
34
+ end
35
+ end
36
+ end
37
+
38
+ # vim: syntax=ruby
data/bin/console ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
5
+ require 'es_tractor'
6
+
7
+ begin
8
+ require 'pry'
9
+ Pry.start
10
+ rescue LoadError
11
+ require 'irb'
12
+ IRB.start
13
+ end
data/lib/es_tractor.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/es_tractor'
3
+ $LOAD_PATH.unshift File.dirname(__FILE__) \
4
+ unless $LOAD_PATH.include?(File.dirname(__FILE__))
5
+
6
+ require 'client'
7
+
8
+ ##
9
+ # EsTractor provides tools to query Elasticsearch
10
+
11
+ module EsTractor
12
+ VERSION = '0.0.2'
13
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+ require 'elasticsearch'
3
+
4
+ module EsTractor
5
+ ##
6
+ # Client provides an Elasticsearch::Client with a limited subset of
7
+ # methods and simplified arguments.
8
+ class Client
9
+ # @example
10
+ # 'my_index'
11
+ ELASTICSEARCH_INDEX = ENV['ES_TRACTOR_ELASTICSEARCH_INDEX']
12
+
13
+ # @example
14
+ # 'example.com:9200'
15
+ ELASTICSEARCH_HOST = ENV['ES_TRACTOR_ELASTICSEARCH_HOST']
16
+
17
+ # @attr_reader [Elasticsearch::Client] client to search with.
18
+ attr_reader :client
19
+
20
+ # @param [true, false] log verbose output of client-server interactions
21
+ def initialize(log = false)
22
+ @client = Elasticsearch::Client.new(
23
+ host: ELASTICSEARCH_HOST,
24
+ log: log,
25
+ )
26
+ end
27
+
28
+ # Count documents, filtered by options.
29
+ # @return [Hash] with the result in the 'count' key.
30
+ # @param [Hash] opts with the following keys:
31
+ # @option opts [String, Array<String>] :exists
32
+ # One or more field names, translated into filter boolean.
33
+ # @option opts [Hash, Array<Hash>] :match
34
+ # One or more field: match pairs, translated into must boolean.
35
+ # @option opts [String] :query_string
36
+ # Translated into must boolean.
37
+ # @option opts [Hash<Array>] :range
38
+ # A hash keyed on a field name, containing an array: [min, max],
39
+ # translated into filter boolean.
40
+ # @option opts [Hash, Array<Hash>] :term
41
+ # One or more field: term pairs, translated into filter boolean.
42
+ #
43
+ # @example
44
+ # opts = {
45
+ # match: { color: 'strawberry black' },
46
+ # exists: ['flavor', 'spicy'],
47
+ # term: [
48
+ # { flavor: 'vanilla' },
49
+ # { spicy: 3 },
50
+ # ],
51
+ # }
52
+ #
53
+ # Client.new.count(opts) # => { 'count' => 7 }
54
+ #
55
+ # # Tranforms opts into the following hash, passed to Elasticsearch:
56
+ # {
57
+ # body: {
58
+ # query: {
59
+ # bool: {
60
+ # must: {
61
+ # match: { flavor: 'vanilla' },
62
+ # },
63
+ # filter: [
64
+ # { term: { flavor: 'vanilla' } },
65
+ # { term: { spicy: 3 } },
66
+ # ],
67
+ # },
68
+ # },
69
+ # },
70
+ # }
71
+ def count(opts = {})
72
+ args = { body: body(opts) }
73
+ @client.count(args)
74
+ end
75
+
76
+ # @return [Hash] with the actual result in the 'hits'['hits'] key.
77
+ # @param (see #count)
78
+ # @option (see #count)
79
+ # @option opts [Integer] :from
80
+ # @option opts [Integer] :size
81
+ def search(opts)
82
+ args = {
83
+ from: opts[:from] ? opts[:from] : 0,
84
+ size: opts[:size] ? opts[:size] : 0,
85
+ body: body(opts),
86
+ }
87
+ @client.search(args)
88
+ end
89
+
90
+ private
91
+
92
+ def array_or_hash(name, filter)
93
+ case filter
94
+ when Array
95
+ filter.map { |f| { name => f } }
96
+ when Hash
97
+ [name => filter]
98
+ else
99
+ []
100
+ end
101
+ end
102
+
103
+ def body(opts = {})
104
+ { query: query(opts) }
105
+ end
106
+
107
+ def query(opts = {})
108
+ bool = { filter: [], must: [] }
109
+
110
+ (%i[exists match query_string range term] & opts.keys)
111
+ .each do |qualifier|
112
+ case qualifier
113
+ when :exists
114
+ bool[:filter].push(exists: { field: opts[qualifier] })
115
+ when :match
116
+ bool[:must] += array_or_hash(qualifier, opts[:match])
117
+ when :query_string
118
+ bool[:must].push(query_string: { query: opts[:query_string] })
119
+ when :range
120
+ bool[:filter].push(range: range(opts[:range]))
121
+ when :term
122
+ bool[:filter] += array_or_hash(qualifier, opts[:term])
123
+ end
124
+ end
125
+
126
+ { bool: bool }
127
+ end
128
+
129
+ def range(range_opt)
130
+ {
131
+ range_opt.keys.first => {
132
+ gte: range_opt.values.first.first,
133
+ lte: range_opt.values.first.last,
134
+ },
135
+ }
136
+ end
137
+ end
138
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ ENV['ESTRACTOR_ENV'] = 'test'
3
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
4
+
5
+ require 'es_tractor'
6
+ require 'minitest/autorun'
7
+ require 'mocha/setup'
8
+
9
+ class Test < Minitest::Test
10
+ include EsTractor
11
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+ require 'helper'
3
+
4
+ module EsTractor
5
+ class TestClient < Test
6
+ def setup
7
+ @tractor = Client.new
8
+ end
9
+
10
+ %w[count search].each do |action|
11
+ exp = action == 'search' ? { from: 0, size: 0 } : {}
12
+
13
+ define_method "test_#{action}_with_term_hash" do
14
+ opts = { term: { my_field: 'my precise term' } }
15
+ exp[:body] = { query: { bool: {
16
+ must: [],
17
+ filter: [
18
+ term: { my_field: 'my precise term' }
19
+ ],
20
+ } } }
21
+
22
+ @tractor.client.expects(action).with(exp)
23
+
24
+ @tractor.send(action, opts)
25
+ end
26
+
27
+ define_method "test_#{action}_with_term_array" do
28
+ opts = { term: [
29
+ { my_field: 'my precise term' },
30
+ { my_other_field: 'my other term' },
31
+ ] }
32
+ exp[:body] = { query: { bool: {
33
+ must: [],
34
+ filter: [
35
+ { term: { my_field: 'my precise term' } },
36
+ { term: { my_other_field: 'my other term' } },
37
+ ],
38
+ } } }
39
+
40
+ @tractor.client.expects(action).with(exp)
41
+
42
+ @tractor.send(action, opts)
43
+ end
44
+
45
+ define_method "test_#{action}_with_match_hash" do
46
+ opts = { match: { my_field: 'my match' } }
47
+ exp[:body] = { query: { bool: {
48
+ must: [{ match: { my_field: 'my match' } }],
49
+ filter: [],
50
+ } } }
51
+
52
+ @tractor.client.expects(action).with(exp)
53
+
54
+ @tractor.send(action, opts)
55
+ end
56
+
57
+ define_method "test_#{action}_with_range_hash" do
58
+ min = 1
59
+ max = 2
60
+ opts = { range: { my_field: [min, max] } }
61
+ exp[:body] = { query: {
62
+ bool: {
63
+ must: [],
64
+ filter: [
65
+ range: {
66
+ my_field: {
67
+ gte: min,
68
+ lte: max,
69
+ },
70
+ },
71
+ ],
72
+ },
73
+ } }
74
+
75
+ @tractor.client.expects(action).with(exp)
76
+
77
+ @tractor.send(action, opts)
78
+ end
79
+
80
+ define_method "test_#{action}_with_exists_fieldname_value" do
81
+ opts = { exists: 'my_field' }
82
+ exp[:body] = { query: {
83
+ bool: {
84
+ must: [],
85
+ filter: [
86
+ { exists: { field: 'my_field' } },
87
+ ],
88
+ },
89
+ } }
90
+
91
+ @tractor.client.expects(action).with(exp)
92
+
93
+ @tractor.send(action, opts)
94
+ end
95
+
96
+ define_method "test_#{action}_with_exists_fieldname_array" do
97
+ opts = { exists: %w[my_field my_other_field] }
98
+ exp[:body] = { query: {
99
+ bool: {
100
+ must: [],
101
+ filter: [
102
+ {
103
+ exists: {
104
+ field: %w[my_field my_other_field],
105
+ },
106
+ },
107
+ ],
108
+ },
109
+ } }
110
+
111
+ @tractor.client.expects(action).with(exp)
112
+
113
+ @tractor.send(action, opts)
114
+ end
115
+
116
+ define_method "test_#{action}_with_query_string" do
117
+ opts = { query_string: 'My query string' }
118
+ exp[:body] = { query: {
119
+ bool: {
120
+ must: [query_string: { query: 'My query string' }],
121
+ filter: [],
122
+ },
123
+ } }
124
+
125
+ @tractor.client.expects(action).with(exp)
126
+
127
+ @tractor.send(action, opts)
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ require 'helper'
3
+
4
+ class TestEsTractor < Test
5
+ def test_client
6
+ assert Elasticsearch::Transport::Client, Client.new
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,165 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: es_tractor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - utilum
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-09-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: elasticsearch
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 5.0.4
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '5.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 5.0.4
33
+ - !ruby/object:Gem::Dependency
34
+ name: hoe-yard
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 0.1.3
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 0.1.3
47
+ - !ruby/object:Gem::Dependency
48
+ name: minitest
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '5.10'
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 5.10.3
57
+ type: :development
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '5.10'
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 5.10.3
67
+ - !ruby/object:Gem::Dependency
68
+ name: mocha
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '1.3'
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: 1.3.0
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '1.3'
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: 1.3.0
87
+ - !ruby/object:Gem::Dependency
88
+ name: pry
89
+ requirement: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - "~>"
92
+ - !ruby/object:Gem::Version
93
+ version: 0.10.4
94
+ type: :development
95
+ prerelease: false
96
+ version_requirements: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - "~>"
99
+ - !ruby/object:Gem::Version
100
+ version: 0.10.4
101
+ - !ruby/object:Gem::Dependency
102
+ name: hoe
103
+ requirement: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - "~>"
106
+ - !ruby/object:Gem::Version
107
+ version: '3.16'
108
+ type: :development
109
+ prerelease: false
110
+ version_requirements: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - "~>"
113
+ - !ruby/object:Gem::Version
114
+ version: '3.16'
115
+ description: Sub-set of Search APIs with DRY query building.
116
+ email:
117
+ - oz@utilum.com
118
+ executables:
119
+ - console
120
+ extensions: []
121
+ extra_rdoc_files:
122
+ - History.txt
123
+ - Manifest.txt
124
+ - README.txt
125
+ files:
126
+ - ".autotest"
127
+ - ".rubocop.yml"
128
+ - History.txt
129
+ - Manifest.txt
130
+ - README.txt
131
+ - Rakefile
132
+ - bin/console
133
+ - lib/es_tractor.rb
134
+ - lib/es_tractor/client.rb
135
+ - test/helper.rb
136
+ - test/test_client.rb
137
+ - test/test_es_tractor.rb
138
+ homepage:
139
+ licenses:
140
+ - MIT
141
+ metadata: {}
142
+ post_install_message:
143
+ rdoc_options:
144
+ - "--title"
145
+ - TestEsTractor Documentation
146
+ - "--quiet"
147
+ require_paths:
148
+ - lib
149
+ required_ruby_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ required_rubygems_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ requirements: []
160
+ rubyforge_project:
161
+ rubygems_version: 2.6.13
162
+ signing_key:
163
+ specification_version: 4
164
+ summary: Sub-set of Search APIs with DRY query building.
165
+ test_files: []