co-elastic-query 0.0.1

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: 68ef853b38f5e3752ae82f245326cf99f331e1e4
4
+ data.tar.gz: 46dd06a05d563613e4ef26298c3f81488e802b48
5
+ SHA512:
6
+ metadata.gz: f009e914e70254347d8e0fea7b954fe4365176b9941cfdcf3f2288bc5054c8bd61b3de232f9ffdafa0c7dbbb931a3c4bb3e90fe2679c045c62309f7c63580ea8
7
+ data.tar.gz: 08a6998306551b5d9e51409f2e6fbbd09a90088817643064a0ffce304b6f45b3579e04b04bc4ab860fc2813d05b19834576fca86c59820f35032762af724e572
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 CoTag Media
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Elastic::Helper
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'elastic-helper'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install elastic-helper
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/[my-github-username]/elastic-helper/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,21 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'co-elastic-query/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'co-elastic-query'
8
+ spec.version = CoElasticQuery::VERSION
9
+ spec.authors = ['Stephen von Takach', 'Will Cannings']
10
+ spec.email = ['steve@cotag.me', 'me@willcannings.com']
11
+ spec.summary = 'Elasticsearch query generator'
12
+ spec.homepage = 'http://cotag.me/'
13
+
14
+ spec.files = `git ls-files -z`.split("\x0")
15
+ spec.require_paths = ['lib']
16
+
17
+ spec.add_development_dependency 'bundler', '~> 1.6'
18
+ spec.add_development_dependency 'rake'
19
+
20
+ spec.add_dependency 'elasticsearch', '~> 1.0.5'
21
+ end
@@ -0,0 +1,248 @@
1
+ require 'elasticsearch'
2
+
3
+ class Elastic
4
+ class Query
5
+ def initialize(params)
6
+ query = params.permit(:q, :limit, :offset)
7
+
8
+ @filters = nil
9
+ @search = query[:q]
10
+
11
+ @limit = query[:limit] || 20
12
+ @limit = @limit.to_i
13
+ @limit = 50 if @limit > 50
14
+
15
+ @offset = query[:offset] || 0
16
+ @offset = offset.to_i
17
+ @offset = 10000 if offset > 10000
18
+ end
19
+
20
+
21
+ attr_accessor :offset
22
+ attr_accessor :limit
23
+ attr_accessor :sort
24
+
25
+ def raw_filter(filter)
26
+ @raw_filter = filter
27
+ end
28
+
29
+
30
+ # filters is in the form {fieldname1: ['var1','var2',...], fieldname2: ['var1','var2'...]}
31
+ # NOTE:: may overwrite an existing filter in merge
32
+ def filter(filters)
33
+ @filters ||= {}
34
+ @filters.merge!(filters)
35
+ end
36
+
37
+ # Like filter however all keys are OR's instead of AND's
38
+ def or_filter(filters)
39
+ @orFilter ||= {}
40
+ @orFilter.merge!(filters)
41
+ end
42
+
43
+ def range(filter)
44
+ @rangeFilter ||= []
45
+ @rangeFilter << filter
46
+ end
47
+
48
+ # Call to add fields that should be missing
49
+ # Effectively adds a filter that ensures a field is missing
50
+ def missing(*fields)
51
+ @missing ||= Set.new
52
+ @missing.merge(fields)
53
+ end
54
+
55
+ # The opposite of filter
56
+ def not(filters)
57
+ @nots ||= {}
58
+ @nots.merge!(filters)
59
+ end
60
+
61
+ def build
62
+ if @filters
63
+ fieldfilters = []
64
+
65
+ @filters.each do |key, value|
66
+ fieldfilter = { :or => [] }
67
+ build_filter(fieldfilter[:or], key, value)
68
+
69
+ # TODO:: Discuss this - might be a security issue
70
+ unless fieldfilter[:or].empty?
71
+ fieldfilters.push(fieldfilter)
72
+ end
73
+ end
74
+ end
75
+
76
+ if @orFilter
77
+ fieldfilters ||= []
78
+ fieldfilter = { :or => [] }
79
+ orArray = fieldfilter[:or]
80
+
81
+ @orFilter.each do |key, value|
82
+ build_filter(orArray, key, value)
83
+ end
84
+
85
+ unless orArray.empty?
86
+ fieldfilters.push(fieldfilter)
87
+ end
88
+ end
89
+
90
+ if @rangeFilter
91
+ fieldfilters ||= []
92
+
93
+ @rangeFilter.each do |value|
94
+ fieldfilters.push({range: value})
95
+ end
96
+ end
97
+
98
+ if @nots
99
+ fieldfilters ||= []
100
+
101
+ @nots.each do |key, value|
102
+ fieldfilter = { :not => { :or => [] } }
103
+ build_filter(fieldfilter[:not][:or], key, value)
104
+ unless fieldfilter[:not].empty?
105
+ fieldfilters.push(fieldfilter)
106
+ end
107
+ end
108
+ end
109
+
110
+ if @missing
111
+ fieldfilters ||= []
112
+
113
+ @missing.each do |field|
114
+ fieldfilters.push({
115
+ missing: { field: field }
116
+ })
117
+ end
118
+ end
119
+
120
+ if @raw_filter
121
+ fieldfilters = @raw_filter
122
+ end
123
+
124
+ if @search.present?
125
+ # HACK:: This is such a hack.
126
+ # TODO:: join('* ') + '*' (i.e fix ES tokeniser and then match start of words)
127
+ {
128
+ query: {
129
+ query_string: {
130
+ query: '*' + @search.scan(/[a-zA-Z0-9]+/).join('* *') + '*'
131
+ }
132
+ },
133
+ filters: fieldfilters,
134
+ offset: @offset,
135
+ limit: @limit
136
+ }
137
+ else
138
+ {
139
+ sort: @sort || [{created_at: 'desc'}],
140
+ filters: fieldfilters,
141
+ query: {
142
+ match_all: {}
143
+ },
144
+ offset: @offset,
145
+ limit: @limit
146
+ }
147
+ end
148
+ end
149
+
150
+
151
+ #protected
152
+
153
+
154
+ def build_filter(filters, key, values)
155
+ values.each { |var|
156
+ if var.nil?
157
+ filters.push({
158
+ missing: { field: key }
159
+ })
160
+ else
161
+ filters.push({
162
+ :term => {
163
+ key => var
164
+ }
165
+ })
166
+ end
167
+ }
168
+ end
169
+ end
170
+
171
+
172
+ HOST = if ENV['ELASTIC']
173
+ ENV['ELASTIC'].split(' ').map {|item| "#{item}:9200"}
174
+ else
175
+ ['localhost:9200']
176
+ end
177
+
178
+ @@client ||= Elasticsearch::Client.new hosts: HOST, reload_connections: true
179
+ def self.search *args
180
+ @@client.search *args
181
+ end
182
+
183
+ HITS = 'hits'.freeze
184
+ TOTAL = 'total'.freeze
185
+ ID = '_id'.freeze
186
+ SCORE = '_score'.freeze
187
+ INDEX = (ENV['ELASTIC_INDEX'] || 'default').freeze
188
+
189
+ def initialize(klass, index = INDEX)
190
+ @klass = klass
191
+ @filter = klass.design_document
192
+ @index = index
193
+ end
194
+
195
+ # Safely build the query
196
+ def query(params, filters = nil)
197
+ builder = ::Elastic::Query.new(params)
198
+ builder.filter(filters) if filters
199
+ builder
200
+ end
201
+
202
+ def search(builder, &block)
203
+ opt = builder.build
204
+
205
+ sort = opt[:sort] || []
206
+ sort << SCORE
207
+
208
+ queries = opt[:queries] || []
209
+ queries.unshift(opt[:query])
210
+
211
+ filters = opt[:filters] || []
212
+ filters.unshift({term: {type: @filter}})
213
+
214
+ query = {
215
+ index: @index,
216
+ body: {
217
+ sort: sort,
218
+ query: {
219
+ filtered: {
220
+ query: {
221
+ bool: {
222
+ must: queries
223
+ }
224
+ },
225
+ filter: {
226
+ bool: {
227
+ must: filters
228
+ }
229
+ }
230
+ }
231
+ },
232
+ from: opt[:offset],
233
+ size: opt[:limit]
234
+ }
235
+ }
236
+
237
+ # if a formatter block is supplied, each loaded record is passed to it
238
+ # allowing annotation/conversion of records using data from the model
239
+ # and current request (e.g groups are annotated with 'admin' if the
240
+ # currently logged in user is an admin of the group)
241
+ result = Elastic.search(query)
242
+ records = @klass.find_by_id(result[HITS][HITS].map {|entry| entry[ID]}) || []
243
+ {
244
+ total: result[HITS][TOTAL] || 0,
245
+ results: block_given? ? records.map {|record| yield record} : records
246
+ }
247
+ end
248
+ end
@@ -0,0 +1,3 @@
1
+ module CoElasticQuery
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: co-elastic-query
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Stephen von Takach
8
+ - Will Cannings
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-09-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.6'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.6'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: elasticsearch
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: 1.0.5
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: 1.0.5
56
+ description:
57
+ email:
58
+ - steve@cotag.me
59
+ - me@willcannings.com
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - ".gitignore"
65
+ - Gemfile
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - co-elastic-query.gemspec
70
+ - lib/co-elastic-query.rb
71
+ - lib/co-elastic-query/version.rb
72
+ homepage: http://cotag.me/
73
+ licenses: []
74
+ metadata: {}
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubyforge_project:
91
+ rubygems_version: 2.2.2
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: Elasticsearch query generator
95
+ test_files: []
96
+ has_rdoc: