search_flip 1.0.0

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: 140f180d3c3e60a2ff681c46294726278de9c105
4
+ data.tar.gz: 2a39b8a053056167b3bb08a5e74a8ea868516a1a
5
+ SHA512:
6
+ metadata.gz: b287350dce24ff80a57d831bc70e58be732793315dd9c0ae5ecebc720ea57b9c91bd1240401b9ec9714eee84536169fc7492eecc1068d70fac58b388ad79dc42
7
+ data.tar.gz: 685148c002d82f19047a90e80085e0d104ae6e3c0b4b6d81dffc30c221d3a97a611f580b5a6185c97d6c9bd1d6f3e0e65aa7fe53e2ec19a499f653061258952d
data/.gitignore ADDED
@@ -0,0 +1,18 @@
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
+ gemfiles/*.lock
data/.travis.yml ADDED
@@ -0,0 +1,34 @@
1
+
2
+ rvm:
3
+ - 2.1.10
4
+ - 2.2.5
5
+ - 2.3.1
6
+
7
+ dist: trusty
8
+
9
+ jdk:
10
+ - openjdk8
11
+
12
+ env:
13
+ - ES_VERSION=1
14
+ - ES_VERSION=2
15
+ - ES_VERSION=5
16
+
17
+ install:
18
+ - travis_retry bundle install
19
+ - sh -c "if [ '$ES_VERSION' = '5' ]; then (curl -s https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.4.0.tar.gz | tar xz -C /tmp); fi"
20
+ - sh -c "if [ '$ES_VERSION' = '5' ]; then /tmp/elasticsearch-5.4.0/bin/elasticsearch -d; fi"
21
+ - sh -c "if [ '$ES_VERSION' = '2' ]; then (curl -s https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/tar/elasticsearch/2.4.1/elasticsearch-2.4.1.tar.gz | tar xz -C /tmp); fi"
22
+ - sh -c "if [ '$ES_VERSION' = '2' ]; then /tmp/elasticsearch-2.4.1/bin/plugin install delete-by-query; fi"
23
+ - sh -c "if [ '$ES_VERSION' = '2' ]; then /tmp/elasticsearch-2.4.1/bin/elasticsearch -d; fi"
24
+ - sh -c "if [ '$ES_VERSION' = '1' ]; then (curl -s https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-1.7.4.tar.gz | tar xz -C /tmp); fi"
25
+ - sh -c "if [ '$ES_VERSION' = '1' ]; then /tmp/elasticsearch-1.7.4/bin/elasticsearch -d; fi"
26
+
27
+ before_script:
28
+ - sleep 30
29
+
30
+ script:
31
+ - bundle exec rake test
32
+
33
+ sudo: false
34
+
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+
2
+ source "https://rubygems.org"
3
+
4
+ gemspec
5
+
6
+ gem "activerecord", "< 5" if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.2.2")
7
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Benjamin Vetter
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,606 @@
1
+
2
+ # SearchFlip
3
+
4
+ [![Build Status](https://secure.travis-ci.org/mrkamel/search_flip.png?branch=master)](http://travis-ci.org/mrkamel/search_flip)
5
+
6
+ Using SearchFlip it is dead-simple to create index classes that correspond to
7
+ [ElasticSearch](https://www.elastic.co/) indices and to manipulate, query and
8
+ aggregate these indices using a chainable, concise, yet powerful DSL. Finally,
9
+ SearchFlip supports ElasticSearch 1.x, 2.x, 5.x, 6.x. Check section
10
+ [Feature Support](#feature-support) for version dependent features.
11
+
12
+ ```ruby
13
+ CommentIndex.search("hello world", default_field: "title").where(visible: true).aggregate(:user_id).sort(id: "desc")
14
+
15
+ CommentIndex.aggregate(:user_id) do |aggregation|
16
+ aggregation.aggregate(histogram: { date_histogram: { field: "created_at", interval: "month" }})
17
+ end
18
+
19
+ CommentIndex.range(:created_at, gt: Date.today - 1.week, lt: Date.today).where(state: ["approved", "pending"])
20
+ ```
21
+
22
+ ## Comparison with other gems
23
+
24
+ There are great ruby gems to work with Elasticsearch like e.g. searchkick and
25
+ elasticsearch-ruby already. However, they don't have a chainable API. Compare
26
+ yourself.
27
+
28
+ ```ruby
29
+ # elasticsearch-ruby
30
+ Comment.search(
31
+ query: {
32
+ query_string: {
33
+ query: "hello world",
34
+ default_operator: "AND"
35
+ }
36
+ }
37
+ )
38
+
39
+ # searchkick
40
+ Comment.search("hello world", where: { available: true }, order: { id: "desc" }, aggs: [:username])
41
+
42
+ # search_flip
43
+ CommentIndex.where(available: true).search("hello world").sort(id: "desc").aggregate(:username)
44
+
45
+ ```
46
+
47
+ ## Reference Docs
48
+
49
+ SearchFlip has a great documentation.
50
+ Check youself at [http://www.rubydoc.info/github/mrkamel/search_flip](http://www.rubydoc.info/github/mrkamel/search_flip)
51
+
52
+ ## Install
53
+
54
+ Add this line to your application's Gemfile:
55
+
56
+ ```ruby
57
+ gem 'search_flip'
58
+ ```
59
+
60
+ and then execute
61
+
62
+ ```
63
+ $ bundle
64
+ ```
65
+
66
+ or install it via
67
+
68
+ ```
69
+ $ gem install search_flip
70
+ ```
71
+
72
+ ## Config
73
+
74
+ You can change global config options like:
75
+
76
+ ```ruby
77
+ SearchFlip::Config[:environment] = "development"
78
+ SearchFlip::Config[:base_url] = "http://127.0.0.1:9200"
79
+ ```
80
+
81
+ Available config options are:
82
+
83
+ * `index_prefix` to have a prefix added to your index names automatically. This
84
+ can be useful to separate the indices of e.g. testing and development environments.
85
+ * `base_url` to tell search_flip how to connect to your cluster
86
+ * `bulk_limit` a global limit for bulk requests
87
+ * `auto_refresh` tells search_flip to automatically refresh an index after
88
+ import, index, delete, etc operations. This is e.g. usuful for testing, etc.
89
+ Defaults to false.
90
+
91
+ ## Usage
92
+
93
+ First, create a separate class for your index and include `SearchFlip::Index`.
94
+
95
+ ```ruby
96
+ class CommentIndex
97
+ include SearchFlip::Index
98
+ end
99
+ ```
100
+
101
+ Then tell the Index about the type name, the correspoding model and how to
102
+ serialize the model for indexing.
103
+
104
+ ```ruby
105
+ class CommentIndex
106
+ include SearchFlip::Index
107
+
108
+ def self.type_name
109
+ "comments"
110
+ end
111
+
112
+ def self.model
113
+ Comment
114
+ end
115
+
116
+ def self.serialize(comment)
117
+ {
118
+ id: comment.id,
119
+ username: comment.username,
120
+ title: comment.title,
121
+ message: comment.message
122
+ }
123
+ end
124
+ end
125
+ ```
126
+
127
+ You can additionally specify an `index_scope` which will automatically be
128
+ applied to scopes, eg. ActiveRecord::Relation objects, passed to `#import`,
129
+ `#index`, etc. This can be used to preload associations that are used when
130
+ serializing records or to restrict the records you want to index.
131
+
132
+ ```ruby
133
+ class CommentIndex
134
+ # ...
135
+
136
+ def self.index_scope(scope)
137
+ scope.preload(:user)
138
+ end
139
+ end
140
+
141
+ CommentIndex.import(Comment.all) # => CommentIndex.import(Comment.all.preload(:user))
142
+ ```
143
+
144
+ Please note, ElasticSearch allows to have multiple types per index. However,
145
+ this forces to have the same mapping for fields having the same name even
146
+ though the fields live in different types of the same index. Thus, this gem is
147
+ using a different index for each type by default, but you can change that.
148
+ Simply supply a custom `index_name`.
149
+
150
+ ```ruby
151
+ class CommentIndex
152
+ # ...
153
+
154
+ def self.index_name
155
+ "custom_index_name"
156
+ end
157
+
158
+ # ...
159
+ end
160
+ ```
161
+
162
+ Optionally, specify a custom mapping:
163
+
164
+ ```ruby
165
+ class CommentIndex
166
+ # ...
167
+
168
+ def self.mapping
169
+ {
170
+ comments: {
171
+ properties: {
172
+ # ...
173
+ }
174
+ }
175
+ }
176
+ end
177
+
178
+ # ...
179
+ end
180
+ ```
181
+
182
+ or index settings:
183
+
184
+ ```ruby
185
+ def self.index_settings
186
+ {
187
+ settings: {
188
+ number_of_shards: 10,
189
+ number_of_replicas: 2
190
+ }
191
+ }
192
+ end
193
+ ```
194
+
195
+ Then you can interact with the index:
196
+
197
+ ```ruby
198
+ CommentIndex.create_index
199
+ CommentIndex.index_exists?
200
+ CommentIndex.delete_index
201
+ CommentIndex.update_mapping
202
+ ```
203
+
204
+ index records (automatically uses the bulk API):
205
+
206
+ ```ruby
207
+ CommentIndex.import(Comment.all)
208
+ CommentIndex.import(Comment.first)
209
+ CommentIndex.import([Comment.find(1), Comment.find(2)])
210
+ CommentIndex.import(Comment.where("created_at > ?", Time.now - 7.days))
211
+ ```
212
+
213
+ query records:
214
+
215
+ ```ruby
216
+ CommentIndex.total_entries
217
+ # => 2838
218
+
219
+ CommentIndex.search("title:hello").records
220
+ # => [#<Comment ...>, #<Comment ...>, ...]
221
+
222
+ CommentIndex.where(username: "mrkamel").total_entries
223
+ # => 13
224
+
225
+ CommentIndex.aggregate(:username).aggregations(:username)
226
+ # => {1=>#<SearchFlip::Result doc_count=37 ...>, 2=>... }
227
+ ...
228
+
229
+ CommentIndex.search("hello world").sort(id: "desc").aggregate(:username).request
230
+ # => {:query=>{:bool=>{:must=>[{:query_string=>{:query=>"hello world", :default_operator=>:AND}}]}}, ...}
231
+ ```
232
+
233
+ delete records:
234
+
235
+ ```ruby
236
+ # for ElasticSearch >= 2.x and < 5.x, the delete-by-query plugin is required
237
+ # for the following query:
238
+
239
+ CommentIndex.match_all.delete
240
+
241
+ # or delete manually via the bulk API:
242
+
243
+ CommentIndex.match_all.find_each do |record|
244
+ CommentIndex.bulk do |indexer|
245
+ indexer.delete record.id
246
+ end
247
+ end
248
+ ```
249
+
250
+ ## Advanced Usage
251
+
252
+ SearchFlip supports even more advanced usages, like e.g. post filters, filtered
253
+ aggregations or nested aggregations via simple to use API methods.
254
+
255
+ ### Post filters
256
+
257
+ All criteria methods (`#where`, `#where_not`, `#range`, etc.) are available
258
+ in post filter mode as well, ie. filters/queries applied after aggregations
259
+ are calculated. Checkout the ElasticSearch docs for further info.
260
+
261
+ ```ruby
262
+ query = CommentIndex.aggregate(:user_id)
263
+ query = query.post_where(reviewed: true)
264
+ query = query.post_search("username:a*")
265
+ ```
266
+
267
+ Checkout [PostFilterable](http://www.rubydoc.info/github/mrkamel/search_flip/SearchFlip/PostFilterable)
268
+ for a complete API reference.
269
+
270
+ ### Aggregations
271
+
272
+ SearchFlip allows to elegantly specify nested aggregations, no matter how deeply
273
+ nested:
274
+
275
+ ```ruby
276
+ query = OrderIndex.aggregate(:username, order: { revenue: "desc" }) do |aggregation|
277
+ aggregation.aggregate(revenue: { sum: { field: "price" }})
278
+ end
279
+ ```
280
+
281
+ Generally, aggregation results returned by ElasticSearch are wrapped in a
282
+ `SearchFlip::Result`, which wraps a `Hashie::Mash`such that you can access them
283
+ via:
284
+
285
+ ```ruby
286
+ query.aggregations(:username)["mrkamel"].revenue.value
287
+ ```
288
+
289
+ Still, if you want to get the raw aggregations returned by ElasticSearch,
290
+ access them without supplying any aggregation name to `#aggregations`:
291
+
292
+ ```ruby
293
+ query.aggregations # => returns the raw aggregation section
294
+
295
+ query.aggregations["username"]["buckets"].detect { |bucket| bucket["key"] == "mrkamel" }["revenue"]["value"] # => 238.50
296
+ ```
297
+
298
+ Once again, the criteria methods (`#where`, `#range`, etc.) are available in
299
+ aggregations as well:
300
+
301
+ ```ruby
302
+ query = OrderIndex.aggregate(average_price: {}) do |aggregation|
303
+ aggregation = aggregation.match_all
304
+ aggregation = aggregation.where(user_id: current_user.id) if current_user
305
+
306
+ aggregation.aggregate(average_price: { avg: { field: "price" }})
307
+ end
308
+
309
+ query.aggregations(:average_price).average_price.value
310
+ ```
311
+
312
+ Checkout [Aggregatable](http://www.rubydoc.info/github/mrkamel/search_flip/SearchFlip/Aggregatable)
313
+ as well as [Aggregation](http://www.rubydoc.info/github/mrkamel/search_flip/SearchFlip/Aggregation)
314
+ for a complete API reference.
315
+
316
+ ### Suggestions
317
+
318
+ ```ruby
319
+ query = CommentIndex.suggest(:suggestion, text: "helo", term: { field: "message" })
320
+ query.suggestions(:suggestion).first["text"] # => "hello"
321
+ ```
322
+
323
+ ### Highlighting
324
+
325
+ ```ruby
326
+ CommentIndex.highlight([:title, :message])
327
+ CommentIndex.highlight(:title).highlight(:description)
328
+ CommentIndex.highlight(:title, require_field_match: false)
329
+ CommentIndex.highlight(title: { type: "fvh" })
330
+ ```
331
+
332
+ ```ruby
333
+ query = CommentIndex.highlight(:title).search("hello")
334
+ query.results[0].highlight.title # => "<em>hello</em> world"
335
+ ```
336
+
337
+ ### Advanced Criteria Methods
338
+
339
+ There are even more methods to make your life easier, namely `source`,
340
+ `scroll`, `profile`, `includes`, `preload`, `find_in_batches`, `find_each`,
341
+ `failsafe` and `unscope` to name just a few:
342
+
343
+ * `source`
344
+
345
+ In case you want to restrict the returned fields, simply specify
346
+ the fields via `#source`:
347
+
348
+ ```ruby
349
+ CommentIndex.source([:id, :message]).search("hello world")
350
+ ```
351
+
352
+ * `paginate`, `page`, `per`
353
+
354
+ SearchFlip supports
355
+ [will_paginate](https://github.com/mislav/will_paginate) and
356
+ [kaminari](https://github.com/kaminari/kaminari) compatible pagination. Thus,
357
+ you can either use `#paginate` or `#page` in combination with `#per`:
358
+
359
+ ```ruby
360
+ CommentIndex.paginate(page: 3, per_page: 50)
361
+ CommentIndex.page(3).per(50)
362
+ ```
363
+
364
+ * `scroll`
365
+
366
+ You can as well use the underlying scroll API directly, ie. without using higher
367
+ level pagination:
368
+
369
+ ```ruby
370
+ query = CommentIndex.scroll(timeout: "5m")
371
+
372
+ until query.records.empty?
373
+ # ...
374
+
375
+ query = query.scroll(id: query.scroll_id, timeout: "5m")
376
+ end
377
+ ```
378
+
379
+ * `profile`
380
+
381
+ Use `#profile` To enable query profiling:
382
+
383
+ ```ruby
384
+ query = CommentIndex.profile(true)
385
+ query.raw_response["profile"] # => { "shards" => ... }
386
+ ```
387
+
388
+ * `preload`, `eager_load` and `includes`
389
+
390
+ Uses the well known methods from ActiveRecord to load
391
+ associated database records when fetching the respective
392
+ records themselves. Works with other ORMs as well, if
393
+ supported.
394
+
395
+ Using `#preload`:
396
+
397
+ ```ruby
398
+ CommentIndex.preload(:user, :post).records
399
+ PostIndex.includes(comments: :user).records
400
+ ```
401
+
402
+ or `#eager_load`
403
+
404
+ ```ruby
405
+ CommentIndex.eager_load(:user, :post).records
406
+ PostIndex.eager_load(comments: :user).records
407
+ ```
408
+
409
+ or `#includes`
410
+
411
+ ```ruby
412
+ CommentIndex.includes(:user, :post).records
413
+ PostIndex.includes(comments: :user).records
414
+ ```
415
+
416
+ * `find_in_batches`
417
+
418
+ Used to fetch and yield records in batches using the ElasicSearch scroll API.
419
+ The batch size and scroll API timeout can be specified.
420
+
421
+ ```ruby
422
+ CommentIndex.search("hello world").find_in_batches(batch_size: 100) do |batch|
423
+ # ...
424
+ end
425
+ ```
426
+
427
+ * `find_each`
428
+
429
+ Like `#find_in_batches`, use `#find_each` to fetch records in batches, but yields
430
+ one record at a time.
431
+
432
+ ```ruby
433
+ CommentIndex.search("hello world").find_each(batch_size: 100) do |record|
434
+ # ...
435
+ end
436
+ ```
437
+
438
+ * `failsafe`
439
+
440
+ Use `#failsafe` to prevent any exceptions from being raised for query string
441
+ syntax errors or ElasticSearch being unavailable, etc.
442
+
443
+ ```ruby
444
+ CommentIndex.search("invalid/request").execute
445
+ # raises SearchFlip::ResponseError
446
+
447
+ # ...
448
+
449
+ CommentIndex.search("invalid/request").failsafe(true).execute
450
+ # => #<SearchFlip::Response ...>
451
+ ```
452
+
453
+ * `merge`
454
+
455
+ You can merge criterias, ie. combine the attributes (constraints, settings,
456
+ etc) of two individual criterias:
457
+
458
+ ```ruby
459
+ CommentIndex.where(approved: true).merge(CommentIndex.search("hello"))
460
+ # equivalent to: CommentIndex.where(approved: true).search("hello")
461
+ ```
462
+
463
+ * `unscope`
464
+
465
+ You can even remove certain already added scopes via `#unscope`:
466
+
467
+ ```ruby
468
+ CommentIndex.aggregate(:username).search("hello world").unscope(:search, :aggregate)
469
+ ```
470
+
471
+ * `timeout`
472
+
473
+ Specify a timeout to limit query processing time:
474
+
475
+ ```ruby
476
+ CommentIndex.timeout("3s").execute
477
+ ```
478
+
479
+ * `terminate_after`
480
+
481
+ Activate early query termination to stop query processing after the specified
482
+ number of records has been found:
483
+
484
+ ```ruby
485
+ CommentIndex.terminate_after(10).execute
486
+ ```
487
+
488
+ For further details and a full list of methods, check out the reference docs.
489
+
490
+ ## Non-ActiveRecord models
491
+
492
+ SearchFlip ships with built-in support for ActiveRecord models, but using
493
+ non-ActiveRecord models is very easy. The model must implement a `find_each`
494
+ class method and the Index class needs to implement `Index.record_id` and
495
+ `Index.fetch_records`. The default implementations for the index class are as
496
+ follows:
497
+
498
+ ```ruby
499
+ class MyIndex
500
+ include SearchFlip::Index
501
+
502
+ def self.record_id(object)
503
+ object.id
504
+ end
505
+
506
+ def self.fetch_records(ids)
507
+ model.where(id: ids)
508
+ end
509
+ end
510
+ ```
511
+
512
+ Thus, simply add your custom implementation of those methods that work with
513
+ whatever ORM you use.
514
+
515
+ ## Date and Timestamps in JSON
516
+
517
+ ElasticSearch requires dates and timestamps to have one of the formats listed
518
+ here: [https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#strict-date-time](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#strict-date-time).
519
+
520
+ However, `JSON.generate` in ruby by default outputs something like:
521
+
522
+ ```ruby
523
+ JSON.generate(time: Time.now.utc)
524
+ # => "{\"time\":\"2018-02-22 18:19:33 UTC\"}"
525
+ ```
526
+
527
+ This format is not compatible with ElasticSearch by default. If you're on
528
+ Rails, ActiveSupport adds its own `#to_json` methods to `Time`, `Date`, etc.
529
+ However, ActiveSupport checks whether they are used in combination with
530
+ `JSON.generate` or not and adapt:
531
+
532
+ ```ruby
533
+ Time.now.utc.to_json
534
+ => "\"2018-02-22T18:18:22.088Z\""
535
+
536
+ JSON.generate(time: Time.now.utc)
537
+ => "{\"time\":\"2018-02-22 18:18:59 UTC\"}"
538
+ ```
539
+
540
+ SearchFlip is using the [Oj gem](https://github.com/ohler55/oj) to generate
541
+ JSON. More concretely, SearchFlip is using:
542
+
543
+ ```ruby
544
+ Oj.dump({ key: "value" }, mode: :custom, use_to_json: true)
545
+ ```
546
+
547
+ This mitigates the issues if you're on Rails:
548
+
549
+ ```ruby
550
+ Oj.dump(Time.now, mode: :custom, use_to_json: true)
551
+ # => "\"2018-02-22T18:21:21.064Z\""
552
+ ```
553
+
554
+ However, if you're not on Rails, you need to add `#to_json` methods to `Time`,
555
+ `Date` and `DateTime` to get proper serialization. You can either add them on
556
+ your own, via other libraries or by simply using:
557
+
558
+ ```ruby
559
+ require "search_flip/to_json"
560
+ ```
561
+
562
+ ## Feature Support
563
+
564
+ * `#post_search` and `#profile` are only supported from up to ElasticSearch
565
+ version >= 2.
566
+ * for ElasticSearch 2.x, the delete-by-query plugin is required to delete
567
+ records via queries
568
+
569
+ ## Keeping your Models and Indices in Sync
570
+
571
+ Besides the most basic approach to get you started, SarchFlip currently doesn't
572
+ ship with any means to automatically keep your models and indices in sync,
573
+ because every method is very much bound to the concrete environment and depends
574
+ on your concrete requirements. In addition, the methods to achieve model/index
575
+ consistency can get arbitrarily complex and we want to keep this bloat out of
576
+ the SearchFlip codebase.
577
+
578
+ ```ruby
579
+ class Comment < ActiveRecord::Base
580
+ include SearchFlip::Model
581
+
582
+ notifies_index(CommentIndex)
583
+ end
584
+ ```
585
+
586
+ It uses `after_commit` (if applicable, `after_save`, `after_destroy` and
587
+ `after_touch` otherwise) hooks to synchronously update the index when your
588
+ model changes.
589
+
590
+ ## Links
591
+
592
+ * ElasticSearch: [https://www.elastic.co/](https://www.elastic.co/)
593
+ * Reference Docs: [http://www.rubydoc.info/github/mrkamel/search_flip](http://www.rubydoc.info/github/mrkamel/search_flip)
594
+ * Travis: [http://travis-ci.org/mrkamel/search_flip](http://travis-ci.org/mrkamel/search_flip)
595
+ * will_paginate: [https://github.com/mislav/will_paginate](https://github.com/mislav/will_paginate)
596
+ * kaminari: [https://github.com/kaminari/kaminari](https://github.com/kaminari/kaminari)
597
+ * Oj: [https://github.com/ohler55/oj](https://github.com/ohler55/oj)
598
+
599
+ ## Contributing
600
+
601
+ 1. Fork it
602
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
603
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
604
+ 4. Push to the branch (`git push origin my-new-feature`)
605
+ 5. Create new Pull Request
606
+
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "lib"
6
+ t.pattern = "test/**/*_test.rb"
7
+ t.verbose = true
8
+ end
9
+