es_tractor 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: []