chewy 0.5.0 → 0.5.1

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