chewy 0.5.0 → 0.5.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b9ce5f8bf5d9ad285ee36e1196ebd0516e7ded43
4
- data.tar.gz: 9f4bc4e97c744d020cd33529a6aeb9d8ee8a4e7a
3
+ metadata.gz: 600f8bb51476db3d8d55a156cd6c8d5a955f02b0
4
+ data.tar.gz: bad3af9a8e5038c08518b9cfa87dcacb12b6085a
5
5
  SHA512:
6
- metadata.gz: 647b9df1cc80287dc95525ac8f55a94130eb1f94e91c5d1d72c370d47047a2055d9c751be3277fe93791fdbd36f083aa4af873a31a88a758c702c603806b4dbc
7
- data.tar.gz: eddd0288baab2431ee08cb61d47e2a2bdbfc7453d4b3891f4b12d8af7c897f63415e01c56fedbc9fbb7f5eeceb48a7f79799b73cca797c6f8d876be6cee5ee2d
6
+ metadata.gz: faba93331adeff1f6cbbedff24e0d47beb76f217399954c38e68d5a944e35fc5c1a7d9f494d731c10f0d5ca59687c7d093c360cc44131e5daa96eff2b434eec1
7
+ data.tar.gz: 33c3bc5b12a6d9e5410ad826120ad982e82c7369cc68d9c87c883faa4a69eb74f0ea91a26cde400769fd9a3e786026a674e088dc244f7c9bdedee8c13aabe36d
data/.travis.yml CHANGED
@@ -9,6 +9,6 @@ gemfile:
9
9
  - gemfiles/Gemfile.rails-3.2
10
10
  - gemfiles/Gemfile.rails-4.0
11
11
  before_install:
12
- - curl -# https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.2.1.tar.gz | tar xz -C /tmp
12
+ - curl -# https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.3.0.tar.gz | tar xz -C /tmp
13
13
  before_script:
14
- - TEST_CLUSTER_COMMAND="/tmp/elasticsearch-1.2.1/bin/elasticsearch" rake elasticsearch:start
14
+ - TEST_CLUSTER_COMMAND="/tmp/elasticsearch-1.3.0/bin/elasticsearch" rake elasticsearch:start
data/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # master
2
2
 
3
+ # Version 0.5.1
4
+
5
+ ## Changes:
6
+
7
+ * `chewy.yml` Rails generator (@jirikolarik)
8
+
9
+ * Parent-child mappings feature support (@inbeom)
10
+
11
+ * `Chewy::Index.total_count` and `Chewy::Type::Base.total_count`
12
+
13
+ * `Chewy::Type::Base.reset` method. Deletes all the type documents and performs import (@jondavidford)
14
+
15
+ * Added `Chewy::Query#delete_all` scope method using delete by query ES feature (@jondavidford)
16
+
17
+ * Rspec 3 `update_index` matcher support (@jimmybaker)
18
+
19
+ * Implemented function scoring (@averell23)
20
+
21
+ ## Bugfixes:
22
+
23
+ * Indexed eager-loading fix (@leemhenson)
24
+
25
+ * Field type deriving nested type support fix (@rschellhorn)
26
+
3
27
  # Version 0.5.0
4
28
 
5
29
  ## Incompatible changes:
data/Guardfile CHANGED
@@ -1,7 +1,7 @@
1
1
  # A sample Guardfile
2
2
  # More info at https://github.com/guard/guard#readme
3
3
 
4
- guard :rspec do
4
+ guard :rspec, cmd: 'rspec' do
5
5
  watch(%r{^spec/.+_spec\.rb$})
6
6
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
7
  watch('spec/spec_helper.rb') { "spec" }
@@ -21,4 +21,3 @@ guard :rspec do
21
21
  watch(%r{^spec/acceptance/(.+)\.feature$})
22
22
  watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
23
23
  end
24
-
data/README.md CHANGED
@@ -43,6 +43,8 @@ Or install it yourself as:
43
43
 
44
44
  There are 2 ways to configure Chewy client: `Chewy.configuration` hash and `chewy.yml`
45
45
 
46
+ You can create this file manually or run `rails g chewy:install` to do that with yaml way
47
+
46
48
  ```ruby
47
49
  # config/initializers/chewy.rb
48
50
  Chewy.configuration = {host: 'localhost:9250'} # do not use environments
@@ -55,7 +57,7 @@ test:
55
57
  host: 'localhost:9250'
56
58
  prefix: 'test'
57
59
  development:
58
- host: 'localhost:9250'
60
+ host: 'localhost:9200'
59
61
  ```
60
62
 
61
63
  The result config merges both hashes. Client options are passed as is to Elasticsearch::Transport::Client except the `:prefix` - it is used internally by chewy to create prefixed index names:
@@ -265,7 +267,13 @@ This strategy is highly usable for rails actions:
265
267
 
266
268
  ```ruby
267
269
  class ApplicationController < ActionController::Base
268
- around_action { |&block| Chewy.atomic(&block) }
270
+ around_action :chewy_atomic
271
+
272
+ def chewy_atomic
273
+ Chewy.atomic do
274
+ yield
275
+ end
276
+ end
269
277
  end
270
278
  ```
271
279
 
@@ -317,6 +325,17 @@ UsersIndex::User.filter{ name == 'Fred' }.filter{ age < 42 }.filter_mode('75%')
317
325
 
318
326
  See [query.rb](lib/chewy/query.rb) for more details.
319
327
 
328
+ ### Additional query action.
329
+
330
+ You may also perform additional actions on query scope, such as deleting of all the scope documents:
331
+
332
+ ```ruby
333
+ UsersIndex.delete_all
334
+ UsersIndex::User.delete_all
335
+ UsersIndex.filter{ age < 42 }.delete_all
336
+ UsersIndex::User.filter{ age < 42 }.delete_all
337
+ ```
338
+
320
339
  ### Filters query DSL.
321
340
 
322
341
  There is a test version of filters creating DSL:
@@ -605,10 +624,10 @@ Facets are an optional sidechannel you can request from elasticsearch describing
605
624
  For instance, let's request the ```country``` field as a facet along with our users collection. We can do this with the #facets method like so:
606
625
 
607
626
  ```ruby
608
- UsersIndex.filter{ [...] }.facets({countries: {terms: {field: 'country'}}})
627
+ UsersIndex.filter{ [...] }.facets({countries: {terms: {field: 'country'}}})
609
628
  ```
610
629
 
611
- Let's look at what we asked from elasticsearch. The facets setter method accepts a hash. You can choose custom/semantic key names for this hash for your own convinience (in this case I used the plural version of the actual field), in our case: ```countries```. The following nested hash tells ES to grab and aggregate values (terms) from the ```country``` field on our indexed records.
630
+ Let's look at what we asked from elasticsearch. The facets setter method accepts a hash. You can choose custom/semantic key names for this hash for your own convinience (in this case I used the plural version of the actual field), in our case: ```countries```. The following nested hash tells ES to grab and aggregate values (terms) from the ```country``` field on our indexed records.
612
631
 
613
632
  When the response comes back, it will have the ```:facets``` sidechannel included:
614
633
 
data/chewy.gemspec CHANGED
@@ -19,7 +19,9 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ['lib']
20
20
 
21
21
  spec.add_development_dependency 'rake'
22
- spec.add_development_dependency 'rspec', '~> 2.14.0'
22
+ spec.add_development_dependency 'rspec', '~> 3.0.0'
23
+ spec.add_development_dependency 'rspec-its', '~> 1.0.1'
24
+ spec.add_development_dependency 'rspec-collection_matchers'
23
25
  spec.add_development_dependency 'sqlite3'
24
26
  spec.add_development_dependency 'activerecord', '>= 3.2'
25
27
  spec.add_development_dependency 'database_cleaner'
data/lib/chewy.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'active_support'
2
+ require 'active_support/deprecation'
2
3
  require 'active_support/core_ext'
3
4
  require 'active_support/concern'
4
5
  require 'active_support/json'
@@ -13,7 +13,7 @@ module Chewy
13
13
  end
14
14
 
15
15
  def object_field?
16
- (nested.any? && options[:type].blank?) || options[:type].to_s == 'object'
16
+ (nested.any? && options[:type].blank?) || ['object', 'nested'].include?(options[:type].to_s)
17
17
  end
18
18
 
19
19
  def root_field?
@@ -2,8 +2,12 @@ module Chewy
2
2
  module Fields
3
3
  class Root < Chewy::Fields::Base
4
4
  attr_reader :dynamic_templates
5
+ attr_reader :parent
6
+ attr_reader :parent_id
5
7
 
6
8
  def initialize(name, options = {})
9
+ @parent = options.delete(:parent) || options.delete(:_parent)
10
+ @parent_id = options.delete(:parent_id)
7
11
  options.reverse_merge!(value: ->(_){_})
8
12
  super(name, options)
9
13
  options.delete(:type)
@@ -24,10 +28,13 @@ module Chewy
24
28
 
25
29
  def mappings_hash
26
30
  mappings = super
31
+
27
32
  if dynamic_templates.any?
28
33
  mappings[name][:dynamic_templates] ||= []
29
34
  mappings[name][:dynamic_templates].concat dynamic_templates
30
35
  end
36
+
37
+ mappings[name][:_parent] = parent.is_a?(Hash) ? parent : { type: parent } if parent
31
38
  mappings
32
39
  end
33
40
 
@@ -51,6 +58,12 @@ module Chewy
51
58
  @dynamic_templates.push(options)
52
59
  end
53
60
  end
61
+
62
+ def compose_parent(object)
63
+ if parent_id
64
+ parent_id.arity == 0 ? object.instance_exec(&parent_id) : parent_id.call(object)
65
+ end
66
+ end
54
67
  end
55
68
  end
56
69
  end
@@ -6,7 +6,7 @@ module Chewy
6
6
  included do
7
7
  singleton_class.delegate :explain, :limit, :offset, :highlight, :rescore,
8
8
  :facets, :aggregations, :none, :strategy, :query, :filter, :post_filter,
9
- :order, :reorder, :only, :types, :suggest, to: :all
9
+ :order, :reorder, :only, :types, :suggest, :delete_all, :total_count, to: :all
10
10
  end
11
11
 
12
12
  module ClassMethods
data/lib/chewy/query.rb CHANGED
@@ -286,6 +286,149 @@ module Chewy
286
286
  end
287
287
  end
288
288
 
289
+ # Adds a script function to score the search request. All scores are
290
+ # added to the search request and combinded according to
291
+ # <tt>boost_mode</tt> and <tt>score_mode</tt>
292
+ #
293
+ # UsersIndex.script_score("doc['boost'].value", filter: { foo: :bar})
294
+ # # => {body:
295
+ # query: {
296
+ # function_score: {
297
+ # query: { ...},
298
+ # functions: [{
299
+ # script_score: {
300
+ # script: "doc['boost'].value"
301
+ # },
302
+ # filter: { foo: :bar }
303
+ # }
304
+ # }]
305
+ # } } }
306
+ def script_score(script, options = {})
307
+ scoring = options.merge(script_score: { script: script })
308
+ chain { criteria.update_scores scoring }
309
+ end
310
+
311
+ # Adds a boost factor to the search request. All scores are
312
+ # added to the search request and combinded according to
313
+ # <tt>boost_mode</tt> and <tt>score_mode</tt>
314
+ #
315
+ # This probably only makes sense if you specifiy a filter
316
+ # for the boost factor as well
317
+ #
318
+ # UsersIndex.boost_factor(23, filter: { foo: :bar})
319
+ # # => {body:
320
+ # query: {
321
+ # function_score: {
322
+ # query: { ...},
323
+ # functions: [{
324
+ # boost_factor: 23,
325
+ # filter: { foo: :bar }
326
+ # }]
327
+ # } } }
328
+ def boost_factor(factor, options = {})
329
+ scoring = options.merge(boost_factor: factor.to_i)
330
+ chain { criteria.update_scores scoring }
331
+ end
332
+
333
+ # Adds a random score to the search request. All scores are
334
+ # added to the search request and combinded according to
335
+ # <tt>boost_mode</tt> and <tt>score_mode</tt>
336
+ #
337
+ # This probably only makes sense if you specifiy a filter
338
+ # for the random score as well.
339
+ #
340
+ # If you do not pass in a seed value, Time.now will be used
341
+ #
342
+ # UsersIndex.random_score(23, filter: { foo: :bar})
343
+ # # => {body:
344
+ # query: {
345
+ # function_score: {
346
+ # query: { ...},
347
+ # functions: [{
348
+ # random_score: { seed: 23 },
349
+ # filter: { foo: :bar }
350
+ # }]
351
+ # } } }
352
+ def random_score(seed = Time.now, options = {})
353
+ scoring = options.merge(random_score: { seed: seed.to_i })
354
+ chain { criteria.update_scores scoring }
355
+ end
356
+
357
+ # Add a field value scoring to the search. All scores are
358
+ # added to the search request and combinded according to
359
+ # <tt>boost_mode</tt> and <tt>score_mode</tt>
360
+ #
361
+ # This function is only available in Elasticsearch 1.2 and
362
+ # greater
363
+ #
364
+ # UsersIndex.field_value_factor(
365
+ # {
366
+ # field: :boost,
367
+ # factor: 1.2,
368
+ # modifier: :sqrt
369
+ # }, filter: { foo: :bar})
370
+ # # => {body:
371
+ # query: {
372
+ # function_score: {
373
+ # query: { ...},
374
+ # functions: [{
375
+ # field_value_factor: {
376
+ # field: :boost,
377
+ # factor: 1.2,
378
+ # modifier: :sqrt
379
+ # },
380
+ # filter: { foo: :bar }
381
+ # }]
382
+ # } } }
383
+ def field_value_factor(settings, options = {})
384
+ scoring = options.merge(field_value_factor: settings)
385
+ chain { criteria.update_scores scoring }
386
+ end
387
+
388
+ # Add a decay scoring to the search. All scores are
389
+ # added to the search request and combinded according to
390
+ # <tt>boost_mode</tt> and <tt>score_mode</tt>
391
+ #
392
+ # The parameters have default values, but those may not
393
+ # be very useful for most applications.
394
+ #
395
+ # UsersIndex.decay(
396
+ # :gauss,
397
+ # :field,
398
+ # origin: '11, 12',
399
+ # scale: '2km',
400
+ # offset: '5km'
401
+ # decay: 0.4
402
+ # filter: { foo: :bar})
403
+ # # => {body:
404
+ # query: {
405
+ # gauss: {
406
+ # query: { ...},
407
+ # functions: [{
408
+ # gauss: {
409
+ # field: {
410
+ # origin: '11, 12',
411
+ # scale: '2km',
412
+ # offset: '5km',
413
+ # decay: 0.4
414
+ # }
415
+ # },
416
+ # filter: { foo: :bar }
417
+ # }]
418
+ # } } }
419
+ def decay(function, field, options = {})
420
+ field_options = {
421
+ origin: options.delete(:origin) || 0,
422
+ scale: options.delete(:scale) || 1,
423
+ offset: options.delete(:offset) || 0,
424
+ decay: options.delete(:decay) || 0.1
425
+ }
426
+ scoring = options.merge(function => {
427
+ field => field_options
428
+ })
429
+ chain { criteria.update_scores scoring }
430
+ end
431
+
289
432
  # Sets elasticsearch <tt>aggregations</tt> search request param
290
433
  #
291
434
  # UsersIndex.filter{ name == 'Johny' }.aggregations(category_id: {terms: {field: 'category_ids'}})
@@ -446,6 +589,83 @@ module Chewy
446
589
  chain { criteria.update_post_filters params }
447
590
  end
448
591
 
592
+ # Sets the boost mode for custom scoring/boosting.
593
+ # Not used if no score functions are specified
594
+ # Possible values:
595
+ #
596
+ # * <tt>:multiply</tt>
597
+ # Default value. Query score and function result are multiplied.
598
+ #
599
+ # Ex:
600
+ #
601
+ # UsersIndex.boost_mode('multiply').script_score('doc['boost'].value')
602
+ # # => {body: {query: function_score: {
603
+ # query: {...},
604
+ # boost_mode: 'multiply',
605
+ # functions: [ ... ]
606
+ # }}}
607
+ #
608
+ # * <tt>:replace</tt>
609
+ # Only function result is used, query score is ignored.
610
+ #
611
+ # * <tt>:sum</tt>
612
+ # Query score and function score are added.
613
+ #
614
+ # * <tt>:avg</tt>
615
+ # Average of query and function score.
616
+ #
617
+ # * <tt>:max</tt>
618
+ # Max of query and function score.
619
+ #
620
+ # * <tt>:min</tt>
621
+ # Min of query and function score.
622
+ #
623
+ # Default value for <tt>:boost_mode</tt> might be changed
624
+ # with <tt>Chewy.score_mode</tt> config option.
625
+ def boost_mode value
626
+ chain { criteria.update_options boost_mode: value }
627
+ end
628
+
629
+ # Sets the scoring mode for combining function scores/boosts
630
+ # Not used if no score functions are specified.
631
+ # Possible values:
632
+ #
633
+ # * <tt>:multiply</tt>
634
+ # Default value. Scores are multiplied.
635
+ #
636
+ # Ex:
637
+ #
638
+ # UsersIndex.score_mode('multiply').script_score('doc['boost'].value')
639
+ # # => {body: {query: function_score: {
640
+ # query: {...},
641
+ # score_mode: 'multiply',
642
+ # functions: [ ... ]
643
+ # }}}
644
+ #
645
+ # * <tt>:sum</tt>
646
+ # Scores are summed.
647
+ #
648
+ # * <tt>:avg</tt>
649
+ # Scores are averaged.
650
+ #
651
+ # * <tt>:first</tt>
652
+ # The first function that has a matching filter is applied.
653
+ #
654
+ # * <tt>:max</tt>
655
+ # Maximum score is used.
656
+ #
657
+ # * <tt>:min</tt>
658
+ # Minimum score is used
659
+ #
660
+ # Default value for <tt>:score_mode</tt> might be changed
661
+ # with <tt>Chewy.score_mode</tt> config option.
662
+ #
663
+ # Chewy.score_mode = :first
664
+ #
665
+ def score_mode value
666
+ chain { criteria.update_options score_mode: value }
667
+ end
668
+
449
669
  # Sets search request sorting
450
670
  #
451
671
  # UsersIndex.order(:first_name, :last_name).order(age: :desc).order(price: {order: :asc, mode: :avg})
@@ -559,6 +779,17 @@ module Chewy
559
779
  chain { criteria.merge!(other.criteria) }
560
780
  end
561
781
 
782
+ # Deletes all records matching a query.
783
+ #
784
+ # UsersIndex.delete_all
785
+ # UsersIndex.filter{ age <= 42 }.delete_all
786
+ # UsersIndex::User.delete_all
787
+ # UsersIndex::User.filter{ age <= 42 }.delete_all
788
+ #
789
+ def delete_all
790
+ _delete_all_response
791
+ end
792
+
562
793
  protected
563
794
 
564
795
  def initialize_clone other
@@ -580,19 +811,27 @@ module Chewy
580
811
  @_request ||= criteria.request_body.merge(index: index.index_name, type: types)
581
812
  end
582
813
 
814
+ def _delete_all_request
815
+ @_delete_all_request ||= criteria.delete_all_request_body.merge(index: index.index_name, type: types)
816
+ end
817
+
583
818
  def _response
584
- @_response ||= begin
585
- ActiveSupport::Notifications.instrument 'search_query.chewy', request: _request, index: index do
586
- begin
587
- index.client.search(_request)
588
- rescue Elasticsearch::Transport::Transport::Errors::NotFound => e
589
- raise e if e.message !~ /IndexMissingException/
590
- {}
591
- end
819
+ @_response ||= ActiveSupport::Notifications.instrument 'search_query.chewy', request: _request, index: index do
820
+ begin
821
+ index.client.search(_request)
822
+ rescue Elasticsearch::Transport::Transport::Errors::NotFound => e
823
+ raise e if e.message !~ /IndexMissingException/
824
+ {}
592
825
  end
593
826
  end
594
827
  end
595
828
 
829
+ def _delete_all_response
830
+ @_delete_all_response ||= ActiveSupport::Notifications.instrument 'delete_query.chewy', request: _delete_all_request, index: index do
831
+ index.client.delete_by_query(_delete_all_request)
832
+ end
833
+ end
834
+
596
835
  def _results
597
836
  @_results ||= (criteria.none? || _response == {} ? [] : _response['hits']['hits']).map do |hit|
598
837
  attributes = (hit['_source'] || {}).merge(hit['highlight'] || {}, &RESULT_MERGER)