chewie 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ 2.6.4
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.3
7
+ before_install: gem install bundler -v 2.0.2
@@ -0,0 +1 @@
1
+ {}
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at nate@mrjones.io. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in es_builder.gemspec
4
+ gemspec
@@ -0,0 +1,59 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ chewie (0.2.0)
5
+ activesupport (>= 5.1.6)
6
+ pry
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport (6.0.2)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (>= 0.7, < 2)
14
+ minitest (~> 5.1)
15
+ tzinfo (~> 1.1)
16
+ zeitwerk (~> 2.2)
17
+ coderay (1.1.2)
18
+ concurrent-ruby (1.1.5)
19
+ diff-lcs (1.3)
20
+ i18n (1.7.0)
21
+ concurrent-ruby (~> 1.0)
22
+ method_source (0.9.2)
23
+ minitest (5.13.0)
24
+ pry (0.12.2)
25
+ coderay (~> 1.1.0)
26
+ method_source (~> 0.9.0)
27
+ rake (10.5.0)
28
+ rspec (3.8.0)
29
+ rspec-core (~> 3.8.0)
30
+ rspec-expectations (~> 3.8.0)
31
+ rspec-mocks (~> 3.8.0)
32
+ rspec-core (3.8.2)
33
+ rspec-support (~> 3.8.0)
34
+ rspec-expectations (3.8.4)
35
+ diff-lcs (>= 1.2.0, < 2.0)
36
+ rspec-support (~> 3.8.0)
37
+ rspec-mocks (3.8.1)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.8.0)
40
+ rspec-support (3.8.2)
41
+ thread_safe (0.3.6)
42
+ tzinfo (1.2.5)
43
+ thread_safe (~> 0.1)
44
+ yard (0.9.20)
45
+ zeitwerk (2.2.2)
46
+
47
+ PLATFORMS
48
+ ruby
49
+
50
+ DEPENDENCIES
51
+ bundler (~> 2.0)
52
+ chewie!
53
+ pry
54
+ rake (~> 10.0)
55
+ rspec (~> 3.0)
56
+ yard
57
+
58
+ BUNDLED WITH
59
+ 2.0.2
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 mrjonesbot
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all 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,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,394 @@
1
+ # Chewie
2
+
3
+ A declarative interface for building Elasticsearch queries.
4
+
5
+ Building valid Elasticsearch queries by hand is difficult, especially as search criteria and logic become more complex.
6
+
7
+ Chewie aims to reduce the cognitive complexity of building queries, so you can focus on the search experience instead of grappling Elasticsearch syntax.
8
+
9
+ NOTE: Chewie currently supports Elasticsearch 7.x.
10
+
11
+ ## Contents
12
+
13
+ * [Installation](#installation)
14
+ * [Usage](#usage)
15
+ * [Filtering by Associations](#filtering-by-associations)
16
+ * [Format](#format)
17
+ * [Combine](#combine)
18
+ * [Supported Queries (Documentation)](#supported-queries)
19
+ * [Development](#development)
20
+ * [Contributing](#contributing)
21
+ * [License](#license)
22
+ * [Code of Conduct](#code-of-conduct)
23
+
24
+ ## Installation
25
+
26
+ Add this line to your application's Gemfile:
27
+
28
+ ```ruby
29
+ gem 'chewie'
30
+ ```
31
+
32
+ And then execute:
33
+
34
+ $ bundle
35
+
36
+ Or install it yourself as:
37
+
38
+ $ gem install chewie
39
+
40
+ ## Usage
41
+
42
+ Define a `Chewie` class:
43
+
44
+ ```ruby
45
+ # app/chewies/school_chewie.rb
46
+
47
+ class SchoolChewie
48
+ extend Chewie
49
+
50
+ term :name
51
+ range :age
52
+ match :description
53
+
54
+ filter_by :governances, with: :terms
55
+ end
56
+ ```
57
+
58
+ Pass filter parameters to the `#build` method:
59
+
60
+ ```ruby
61
+ # app/**/*.rb
62
+
63
+ params = {
64
+ query: "Park School"
65
+ filters: {
66
+ age: { 'gte': 20, 'lte': 10 },
67
+ governances: ['Charter', 'Alop']
68
+ }
69
+ }
70
+
71
+ query = params[:query]
72
+ filters = params[:filters]
73
+
74
+ query = SchoolChewie.build(query: query, filters: filters)
75
+
76
+ puts query
77
+ # =>
78
+ # {
79
+ # query: {
80
+ # term: {
81
+ # name: { value: 'Park School' }
82
+ # },
83
+ # range: {
84
+ # age: { 'gte': 20, 'lte': 10 }
85
+ # },
86
+ # match: {
87
+ # message: { query: 'Park School' }
88
+ # },
89
+ # bool: {
90
+ # filter: {
91
+ # terms: {
92
+ # governances: [ 'Charter', 'Alop' ]
93
+ # }
94
+ # }
95
+ # }
96
+ # }
97
+ # }
98
+ ```
99
+
100
+ Chewie expects incoming parameter attributes to match the attributes defined in your Chewie class, in order to pull the correct value and build the query
101
+
102
+ ```ruby
103
+ # definition
104
+ filter_by :governances, with: :terms
105
+
106
+ # parameters
107
+ { governances: ['ALOP'] }
108
+
109
+ # output
110
+ { filter: { terms: { governances: ['ALOP'] } }
111
+ ```
112
+
113
+ Some queries simply take a string value, which is pulled from `:query`.
114
+
115
+ `:query` is typically a user search value (search bar).
116
+
117
+ ```ruby
118
+ # definition
119
+ term :name
120
+
121
+ # parameters
122
+ { query: 'A search value' }
123
+
124
+ # output
125
+ { query: { term: { name: { value: 'A search value' } } } }
126
+ ```
127
+
128
+ ## Filtering by Associations
129
+
130
+ Depending on how you build your index, some fields might store values from multiple attributes.
131
+
132
+ A simple case is if you'd like to filter records through an association.
133
+
134
+ ```ruby
135
+ class School
136
+ has_many :school_disciplines
137
+ has_many :disciplines, through: :school_disciplines
138
+ end
139
+
140
+ class Discipline
141
+ has_many :school_disciplines
142
+ has_many :schools, through: :school_disciplines
143
+ end
144
+
145
+ class SchoolDiscipline
146
+ belongs_to :school
147
+ belongs_to :discipline
148
+ end
149
+ ```
150
+
151
+ We might imagine a search engine that helps users find schools in their area and allow them to filter schools by various criteria. Some schools might offer discipline specific programs, therefore a school will have many disciplines. Disciplines is a standard collection that schools can associate with in our application.
152
+
153
+ In our search UI, we might provide a `disciplines` filter and allow users to filter by disciplines via dropdown.
154
+
155
+ We provide the search UI with `ids` of disciplines we'd like to filter by.
156
+
157
+ ```json
158
+ {
159
+ filters: {
160
+ disciplines: [1, 2, 3, 4]
161
+ }
162
+ }
163
+ ```
164
+
165
+ Our idex consists of school records, therefore we won't have access to every discipline each school is associated to by default.
166
+
167
+ Instead, we need to define custom index attributes for our school records to capture those relationships.
168
+
169
+ We can do that by defining model methods on `School` that collects associated id values and returns a collection of strings to be indexed.
170
+
171
+ ```ruby
172
+ class School
173
+ def disciplines_index
174
+ discipline_ids = disciplines.pluck(:id)
175
+ discipline_ids.map do |discipline_id|
176
+ "discipline_#{discipline_id}"
177
+ end
178
+ end
179
+
180
+ # Method Elasticsearch can use to populate the index
181
+ def search_data
182
+ {
183
+ name: name,
184
+ disciplines: disciplines_index
185
+ }
186
+ end
187
+ end
188
+ ```
189
+
190
+ When Elasticsearch indexes `School` records, each record will now have knowledge of which disciplines it is associated to.
191
+
192
+ ```json
193
+ {
194
+ name: 'Park School',
195
+ disciplines: [
196
+ "discipline_1",
197
+ "discipline_2",
198
+ "discipline_3"
199
+ ]
200
+ }
201
+ ```
202
+
203
+ ### Format
204
+ At this point, our index is ready to return associated `School` records when given a collection of `Discipline` ids.
205
+
206
+ The caveat is the stored values of `:disciplines` is in a format that contains both the `School` and `Discipline` id.
207
+
208
+ We'll need to do a little extra work at search time to ensure our `id` filter values are transformed into the appropriate string format.
209
+
210
+ To address this, `bool` query methods have a `:format` option that takes a lambda and exposes attribute values given.
211
+
212
+ ```ruby
213
+ class SchoolChewie
214
+ disciplines_format = lambda do |id|
215
+ "discipline_#{id}"
216
+ end
217
+
218
+ filter_by :disciplines, with: :terms, format: disciplines_format
219
+ end
220
+
221
+ params = {
222
+ query: '',
223
+ filters: {
224
+ disciplines: [1, 4]
225
+ }
226
+ }
227
+
228
+ result = SchoolChewie.build(query: params[:query], filters: params[:filters])
229
+
230
+ puts result
231
+ # =>
232
+ # {
233
+ # query: {
234
+ # bool: {
235
+ # filter: {
236
+ # terms: {
237
+ # disciplines: [
238
+ # "discipline_1",
239
+ # "discipline_4",
240
+ # ]
241
+ # }
242
+ # }
243
+ # }
244
+ # }
245
+ # }
246
+ ```
247
+
248
+ Now that our query output for `disciplines` matches values stored in the index, Elasticsearch will find `School` records where `disciplines` match to either `"discipline_1"` or `"discipline_4"`; allowing us to find schools by their associated disciplines.
249
+
250
+ ### Combine
251
+
252
+ Sometimes there are additional criteria we'd like to leverage when filtering against associated records.
253
+
254
+ Continuing with the previous example, let's say we want to filter schools by disciplines where the discipline programs are `"active"`.
255
+
256
+ `"active"` might be a boolean attribute found on `SchoolDiscipline`.
257
+
258
+ We can re-write our `discipline_index` method to pull the discipline `id` and `active` attributes from `SchoolDiscipline` join records.
259
+
260
+ ```ruby
261
+ class School
262
+ def disciplines_index
263
+ school_disciplines.map do |school_discipline|
264
+ discipline_id = school_discipline.id
265
+ active = school_discipline.active
266
+
267
+ "discipline_#{discipline_id}_active_#{active}"
268
+ end
269
+ end
270
+
271
+ # Method Elasticsearch can use to populate the index
272
+ def search_data
273
+ {
274
+ name: name,
275
+ disciplines: disciplines_index
276
+ }
277
+ end
278
+ end
279
+ ```
280
+
281
+ Which changes our index to:
282
+
283
+ ```json
284
+ {
285
+ name: 'Park School',
286
+ disciplines: [
287
+ "discipline_1_active_true",
288
+ "discipline_2_active_false",
289
+ "discipline_3_active_false"
290
+ ]
291
+ }
292
+ ```
293
+
294
+ We can now imagine there is a `active` toggle in the search UI, which expands our filter parameters.
295
+
296
+ ```ruby
297
+ params = {
298
+ query: '',
299
+ filters: {
300
+ disciplines: [1, 4],
301
+ active: true
302
+ }
303
+ }
304
+ ```
305
+
306
+ Now, at search time we not only need to format with the `disciplines` collection, but combine those values with the `active` attribute.
307
+
308
+ Let's update our Chewie to take this new criteria into account.
309
+
310
+ ```ruby
311
+ class SchoolChewie
312
+ disciplines_format = lambda do |id, combine|
313
+ "discipline_#{id}_active_#{combine.first}"
314
+ end
315
+
316
+ filter_by :disciplines, with: :terms, combine: [:active], format: disciplines_format
317
+ end
318
+ ```
319
+
320
+ `:combine` takes a collection of attribute symbols, which Chewie uses to access and pass parameter values to the format lambda at search time; the value collection is exposed as the second argument in the lambda block.
321
+
322
+ The order of the values matches the order defined in the method call.
323
+
324
+ ```ruby
325
+ combine: [:active, :governances, :age]
326
+
327
+ lambda do |id, combine|
328
+ combine[0] #=> :active value
329
+ combine[1] #=> :governances value
330
+ combine[2] #=> :age value
331
+ end
332
+ ```
333
+
334
+ The output becomes:
335
+
336
+ ```ruby
337
+ result = SchoolChewie.build(query: params[:query], filters: params[:filters])
338
+
339
+ puts result
340
+ # =>
341
+ # {
342
+ # query: {
343
+ # bool: {
344
+ # filter: {
345
+ # terms: {
346
+ # disciplines: [
347
+ # "discipline_1_active_true",
348
+ # "discipline_4_active_true",
349
+ # ]
350
+ # }
351
+ # }
352
+ # }
353
+ # }
354
+ # }
355
+ ```
356
+
357
+ ## Supported Queries
358
+ ### [Compound Queries](https://www.elastic.co/guide/en/elasticsearch/reference/current/full-text-queries.html)
359
+ #### [Bool](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html)
360
+
361
+ * filter (#filter_by)
362
+ * should (#should_include)
363
+ * must (#must_include)
364
+ * must_not (#must_not_include)
365
+
366
+ ### [Term Level Queries](https://www.elastic.co/guide/en/elasticsearch/reference/current/term-level-queries.html)
367
+
368
+ * term (#term)
369
+ * terms (#terms)
370
+ * range (#range)
371
+ * fuzzy (#fuzzy)
372
+
373
+ ### [Full Text Queries](https://www.elastic.co/guide/en/elasticsearch/reference/current/full-text-queries.html)
374
+
375
+ * match (#match)
376
+ * multi-match (#multimatch)
377
+
378
+ ## Development
379
+
380
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
381
+
382
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
383
+
384
+ ## Contributing
385
+
386
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/chewie. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
387
+
388
+ ## License
389
+
390
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
391
+
392
+ ## Code of Conduct
393
+
394
+ Everyone interacting in the Chewie project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/chewie/blob/master/CODE_OF_CONDUCT.md).