chewy 0.8.3 → 0.8.4

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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +43 -11
  3. data/Appraisals +39 -12
  4. data/CHANGELOG.md +42 -0
  5. data/Gemfile +1 -1
  6. data/README.md +60 -7
  7. data/chewy.gemspec +12 -5
  8. data/gemfiles/{rails.4.0.mongoid.gemfile → rails.4.0.mongoid.4.0.0.gemfile} +1 -1
  9. data/gemfiles/{rails.4.0.mongoid.kaminari.gemfile → rails.4.0.mongoid.4.0.0.kaminari.gemfile} +1 -1
  10. data/gemfiles/{rails.4.0.mongoid.will_paginate.gemfile → rails.4.0.mongoid.4.0.0.will_paginate.gemfile} +1 -1
  11. data/gemfiles/rails.4.0.mongoid.5.1.0.gemfile +15 -0
  12. data/gemfiles/rails.4.0.mongoid.5.1.0.kaminari.gemfile +14 -0
  13. data/gemfiles/rails.4.0.mongoid.5.1.0.will_paginate.gemfile +14 -0
  14. data/gemfiles/{rails.4.1.mongoid.gemfile → rails.4.1.mongoid.4.0.0.gemfile} +1 -1
  15. data/gemfiles/{rails.4.1.mongoid.kaminari.gemfile → rails.4.1.mongoid.4.0.0.kaminari.gemfile} +1 -1
  16. data/gemfiles/{rails.4.1.mongoid.will_paginate.gemfile → rails.4.1.mongoid.4.0.0.will_paginate.gemfile} +1 -1
  17. data/gemfiles/rails.4.1.mongoid.5.1.0.gemfile +15 -0
  18. data/gemfiles/rails.4.1.mongoid.5.1.0.kaminari.gemfile +14 -0
  19. data/gemfiles/rails.4.1.mongoid.5.1.0.will_paginate.gemfile +14 -0
  20. data/gemfiles/{rails.4.2.mongoid.gemfile → rails.4.2.mongoid.4.0.0.gemfile} +1 -1
  21. data/gemfiles/{rails.4.2.mongoid.kaminari.gemfile → rails.4.2.mongoid.4.0.0.kaminari.gemfile} +1 -1
  22. data/gemfiles/{rails.4.2.mongoid.will_paginate.gemfile → rails.4.2.mongoid.4.0.0.will_paginate.gemfile} +1 -1
  23. data/gemfiles/rails.4.2.mongoid.5.1.0.gemfile +15 -0
  24. data/gemfiles/rails.4.2.mongoid.5.1.0.kaminari.gemfile +14 -0
  25. data/gemfiles/rails.4.2.mongoid.5.1.0.will_paginate.gemfile +14 -0
  26. data/gemfiles/rails.5.0.0.beta3.activerecord.gemfile +16 -0
  27. data/gemfiles/rails.5.0.0.beta3.activerecord.kaminari.gemfile +16 -0
  28. data/gemfiles/rails.5.0.0.beta3.activerecord.will_paginate.gemfile +15 -0
  29. data/gemfiles/{sequel.4.28.gemfile → sequel.4.31.gemfile} +1 -1
  30. data/lib/chewy.rb +7 -1
  31. data/lib/chewy/errors.rb +6 -0
  32. data/lib/chewy/fields/base.rb +12 -8
  33. data/lib/chewy/fields/root.rb +1 -1
  34. data/lib/chewy/index.rb +17 -8
  35. data/lib/chewy/index/actions.rb +4 -4
  36. data/lib/chewy/query.rb +8 -13
  37. data/lib/chewy/query/compose.rb +2 -2
  38. data/lib/chewy/query/criteria.rb +2 -2
  39. data/lib/chewy/query/loading.rb +1 -1
  40. data/lib/chewy/query/nodes/bool.rb +1 -1
  41. data/lib/chewy/query/nodes/regexp.rb +2 -2
  42. data/lib/chewy/railtie.rb +15 -3
  43. data/lib/chewy/rake_helper.rb +5 -2
  44. data/lib/chewy/rspec/update_index.rb +17 -6
  45. data/lib/chewy/strategy.rb +7 -3
  46. data/lib/chewy/strategy/active_job.rb +2 -2
  47. data/lib/chewy/strategy/resque.rb +2 -2
  48. data/lib/chewy/strategy/sidekiq.rb +2 -2
  49. data/lib/chewy/type.rb +14 -0
  50. data/lib/chewy/type/adapter/active_record.rb +11 -1
  51. data/lib/chewy/type/adapter/orm.rb +13 -11
  52. data/lib/chewy/type/adapter/sequel.rb +10 -12
  53. data/lib/chewy/type/import.rb +53 -22
  54. data/lib/chewy/type/witchcraft.rb +208 -0
  55. data/lib/chewy/type/wrapper.rb +25 -7
  56. data/lib/chewy/version.rb +1 -1
  57. data/lib/tasks/chewy.rake +22 -14
  58. data/spec/chewy/fields/base_spec.rb +6 -2
  59. data/spec/chewy/fields/time_fields_spec.rb +4 -4
  60. data/spec/chewy/index/actions_spec.rb +32 -18
  61. data/spec/chewy/index_spec.rb +19 -0
  62. data/spec/chewy/query/pagination_spec.rb +1 -1
  63. data/spec/chewy/query_spec.rb +77 -21
  64. data/spec/chewy/rspec/update_index_spec.rb +75 -62
  65. data/spec/chewy/runtime_spec.rb +1 -1
  66. data/spec/chewy/strategy/active_job_spec.rb +6 -1
  67. data/spec/chewy/strategy/atomic_spec.rb +5 -5
  68. data/spec/chewy/strategy/resque_spec.rb +7 -2
  69. data/spec/chewy/strategy/sidekiq_spec.rb +6 -1
  70. data/spec/chewy/strategy_spec.rb +13 -1
  71. data/spec/chewy/type/actions_spec.rb +4 -1
  72. data/spec/chewy/type/import_spec.rb +71 -2
  73. data/spec/chewy/type/observe_spec.rb +9 -9
  74. data/spec/chewy/type/witchcraft_spec.rb +154 -0
  75. data/spec/chewy/type/wrapper_spec.rb +30 -5
  76. data/spec/chewy/type_spec.rb +10 -0
  77. data/spec/chewy_spec.rb +29 -5
  78. data/spec/spec_helper.rb +2 -0
  79. data/spec/support/class_helpers.rb +15 -0
  80. data/spec/support/mongoid.rb +5 -0
  81. metadata +64 -21
@@ -30,7 +30,7 @@ module Chewy
30
30
  def _queries_join queries, logic
31
31
  queries = queries.compact
32
32
 
33
- if queries.many? || (queries.any? && logic == :must_not)
33
+ if queries.many? || (queries.present? && logic == :must_not)
34
34
  case logic
35
35
  when :dis_max
36
36
  { dis_max: { queries: queries } }
@@ -51,7 +51,7 @@ module Chewy
51
51
  def _filters_join filters, logic
52
52
  filters = filters.compact
53
53
 
54
- if filters.many? || (filters.any? && logic == :must_not)
54
+ if filters.many? || (filters.present? && logic == :must_not)
55
55
  case logic
56
56
  when :and, :or
57
57
  { logic => filters }
@@ -32,7 +32,7 @@ module Chewy
32
32
 
33
33
  STORAGES.each do |storage|
34
34
  define_method "#{storage}?" do
35
- send(storage).any?
35
+ send(storage).present?
36
36
  end
37
37
  end
38
38
 
@@ -69,7 +69,7 @@ module Chewy
69
69
  end
70
70
 
71
71
  [:filters, :queries, :post_filters].each do |storage|
72
- class_eval <<-RUBY
72
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
73
73
  def update_#{storage}(modifier)
74
74
  @#{storage} = #{storage} + Array.wrap(modifier).reject(&:blank?)
75
75
  end
@@ -93,7 +93,7 @@ module Chewy
93
93
 
94
94
  loaded_objects = Hash[_results.group_by(&:class).map do |type, objects|
95
95
  next if except.include?(type.type_name)
96
- next if only.any? && !only.include?(type.type_name)
96
+ next if only.present? && !only.include?(type.type_name)
97
97
 
98
98
  loaded = type.adapter.load(objects, options.merge(_type: type))
99
99
  [type, loaded.index_by.with_index do |loaded, i|
@@ -20,7 +20,7 @@ module Chewy
20
20
  bool = {
21
21
  bool: Hash[METHODS.map do |method|
22
22
  value = instance_variable_get("@#{method}")
23
- [method.to_sym, value.map(&:__render__)] if value.any?
23
+ [method.to_sym, value.map(&:__render__)] if value.present?
24
24
  end.compact]
25
25
  }
26
26
  bool[:bool][:_cache] = !!@options[:cache] if @options.key?(:cache)
@@ -8,8 +8,8 @@ module Chewy
8
8
  @name = name.to_s
9
9
  @regexp = regexp.respond_to?(:source) ? regexp.source : regexp.to_s
10
10
  @options = args.extract_options!
11
- if args.any? || @options[:flags].present?
12
- @options[:flags] = FLAGS & (args.any? ? args.flatten : @options[:flags]).map(&:to_s).map(&:downcase)
11
+ if args.present? || @options[:flags].present?
12
+ @options[:flags] = FLAGS & (args.present? ? args.flatten : @options[:flags]).map(&:to_s).map(&:downcase)
13
13
  end
14
14
  end
15
15
 
@@ -10,7 +10,8 @@ module Chewy
10
10
  end
11
11
 
12
12
  def call(env)
13
- if env['PATH_INFO'].start_with?(Rails.application.config.assets.prefix)
13
+ # For Rails applications in `api_only` mode, the `assets` config isn't present
14
+ if Rails.application.config.respond_to?(:assets) && env['PATH_INFO'].start_with?(Rails.application.config.assets.prefix)
14
15
  @app.call(env)
15
16
  else
16
17
  Chewy.strategy(Chewy.request_strategy) { @app.call(env) }
@@ -29,6 +30,12 @@ module Chewy
29
30
  end
30
31
  end
31
32
 
33
+ module Rails5MigrationStrategy
34
+ def migrate(*args)
35
+ Chewy.strategy(:bypass) { super }
36
+ end
37
+ end
38
+
32
39
  rake_tasks do
33
40
  load 'tasks/chewy.rake'
34
41
  end
@@ -49,8 +56,13 @@ module Chewy
49
56
 
50
57
  initializer 'chewy.migration_strategy' do
51
58
  ActiveSupport.on_load(:active_record) do
52
- ActiveRecord::Migration.send(:include, MigrationStrategy)
53
- ActiveRecord::Migrator.send(:include, MigrationStrategy) if defined? ActiveRecord::Migrator
59
+ if Rails::VERSION::MAJOR >= 5
60
+ ActiveRecord::Migration.prepend(Rails5MigrationStrategy)
61
+ ActiveRecord::Migrator.prepend(Rails5MigrationStrategy) if defined? ActiveRecord::Migrator
62
+ else
63
+ ActiveRecord::Migration.send(:include, MigrationStrategy)
64
+ ActiveRecord::Migrator.send(:include, MigrationStrategy) if defined? ActiveRecord::Migrator
65
+ end
54
66
  end
55
67
  end
56
68
 
@@ -2,8 +2,8 @@ module Chewy
2
2
  module RakeHelper
3
3
  class << self
4
4
 
5
- def subscribe_task_stats!
6
- ActiveSupport::Notifications.subscribe('import_objects.chewy') do |name, start, finish, id, payload|
5
+ def subscribed_task_stats(&block)
6
+ callback = ->(name, start, finish, id, payload) do
7
7
  duration = (finish - start).round(2)
8
8
  puts " Imported #{payload[:type]} for #{duration}s, documents total: #{payload[:import].try(:[], :index).to_i}"
9
9
  payload[:errors].each do |action, errors|
@@ -14,6 +14,9 @@ module Chewy
14
14
  end
15
15
  end if payload[:errors]
16
16
  end
17
+ ActiveSupport::Notifications.subscribed(callback, 'import_objects.chewy') do
18
+ yield
19
+ end
17
20
  end
18
21
 
19
22
  def eager_load_chewy!
@@ -100,7 +100,7 @@ RSpec::Matchers.define :update_index do |type_name, options = {}|
100
100
 
101
101
  type = Chewy.derive_type(type_name)
102
102
 
103
- instance_eval <<-RUBY
103
+ instance_eval <<-RUBY, __FILE__, __LINE__ + 1
104
104
  #{agnostic_stub} do |bulk_options|
105
105
  @updated += bulk_options[:body].map do |updated_document|
106
106
  updated_document.deep_symbolize_keys
@@ -140,7 +140,7 @@ RSpec::Matchers.define :update_index do |type_name, options = {}|
140
140
  (document[:expected_count] && document[:expected_count] == document[:real_count])
141
141
  end
142
142
 
143
- @updated.any? && @missed_reindex.none? && @missed_delete.none? &&
143
+ @updated.present? && @missed_reindex.none? && @missed_delete.none? &&
144
144
  @reindex.all? { |_, document| document[:match_count] && document[:match_attributes] } &&
145
145
  @delete.all? { |_, document| document[:match_count] }
146
146
  end
@@ -150,9 +150,20 @@ RSpec::Matchers.define :update_index do |type_name, options = {}|
150
150
 
151
151
  if @updated.none?
152
152
  output << "Expected index `#{type_name}` to be updated, but it was not\n"
153
- else
154
- output << "Expected index `#{type_name}` to update documents #{@reindex.keys} only, but #{@missed_reindex} was updated also\n" if @missed_reindex.any?
155
- output << "Expected index `#{type_name}` to delete documents #{@delete.keys} only, but #{@missed_delete} was deleted also\n" if @missed_delete.any?
153
+ elsif @missed_reindex.present? || @missed_delete.present?
154
+ message = "Expected index `#{type_name}` "
155
+ message << [
156
+ ("to update documents #{@reindex.keys}" if @reindex.present?),
157
+ ("to delete documents #{@delete.keys}" if @delete.present?)
158
+ ].compact.join(' and ')
159
+ message << ' only, but '
160
+ message << [
161
+ ("#{@missed_reindex} was updated" if @missed_reindex.present?),
162
+ ("#{@missed_delete} was deleted" if @missed_delete.present?)
163
+ ].compact.join(' and ')
164
+ message << ' also.'
165
+
166
+ output << message
156
167
  end
157
168
 
158
169
  output << @reindex.each.with_object('') do |(id, document), output|
@@ -184,7 +195,7 @@ RSpec::Matchers.define :update_index do |type_name, options = {}|
184
195
  end
185
196
 
186
197
  failure_message_when_negated do
187
- if @updated.any?
198
+ if @updated.present?
188
199
  "Expected index `#{type_name}` not to be updated, but it was with #{
189
200
  @updated.map(&:values).flatten.group_by { |documents| documents[:_id] }.map do |id, documents|
190
201
  "\n document id `#{id}` (#{documents.count} times)"
@@ -56,10 +56,10 @@ module Chewy
56
56
  end
57
57
 
58
58
  def wrap name
59
- push name
59
+ stack = push(name)
60
60
  yield
61
61
  ensure
62
- pop
62
+ pop if stack
63
63
  end
64
64
 
65
65
  private
@@ -72,7 +72,11 @@ module Chewy
72
72
  end
73
73
 
74
74
  def resolve name
75
- "Chewy::Strategy::#{name.to_s.camelize}".constantize or raise "Can't find update strategy `#{name}`"
75
+ "Chewy::Strategy::#{name.to_s.camelize}".safe_constantize or raise "Can't find update strategy `#{name}`"
76
+ rescue NameError => ex
77
+ # WORKAROUND: Strange behavior of `safe_constantize` with mongoid gem
78
+ raise "Can't find update strategy `#{name}`" if ex.name.to_s.demodulize == name.to_s.camelize
79
+ raise
76
80
  end
77
81
  end
78
82
  end
@@ -13,8 +13,8 @@ module Chewy
13
13
  class Worker < ::ActiveJob::Base
14
14
  queue_as :chewy
15
15
 
16
- def perform(type, ids)
17
- type.constantize.import!(ids)
16
+ def perform(type, ids, options = {})
17
+ type.constantize.import!(ids, options)
18
18
  end
19
19
  end
20
20
 
@@ -13,8 +13,8 @@ module Chewy
13
13
  class Worker
14
14
  @queue = :chewy
15
15
 
16
- def self.perform(type, ids)
17
- type.constantize.import!(ids)
16
+ def self.perform(type, ids, options = {})
17
+ type.constantize.import!(ids, options)
18
18
  end
19
19
  end
20
20
 
@@ -13,8 +13,8 @@ module Chewy
13
13
  class Worker
14
14
  include ::Sidekiq::Worker
15
15
 
16
- def perform(type, ids)
17
- type.constantize.import!(ids)
16
+ def perform(type, ids, options = {})
17
+ type.constantize.import!(ids, options)
18
18
  end
19
19
  end
20
20
 
@@ -5,6 +5,7 @@ require 'chewy/type/observe'
5
5
  require 'chewy/type/actions'
6
6
  require 'chewy/type/crutch'
7
7
  require 'chewy/type/import'
8
+ require 'chewy/type/witchcraft'
8
9
  require 'chewy/type/adapter/object'
9
10
  require 'chewy/type/adapter/active_record'
10
11
  require 'chewy/type/adapter/mongoid'
@@ -12,16 +13,22 @@ require 'chewy/type/adapter/sequel'
12
13
 
13
14
  module Chewy
14
15
  class Type
16
+ IMPORT_OPTIONS_KEYS = [:batch_size, :bulk_size, :refresh, :consistency, :replication]
17
+
15
18
  include Search
16
19
  include Mapping
17
20
  include Wrapper
18
21
  include Observe
19
22
  include Actions
20
23
  include Crutch
24
+ include Witchcraft
21
25
  include Import
22
26
 
23
27
  singleton_class.delegate :index_name, :client, to: :index
24
28
 
29
+ class_attribute :_default_import_options
30
+ self._default_import_options = {}
31
+
25
32
  # Chewy index current type belongs to. Defined inside `Chewy.create_type`
26
33
  #
27
34
  def self.index
@@ -47,12 +54,19 @@ module Chewy
47
54
  public_methods - Chewy::Type.public_methods
48
55
  end
49
56
 
57
+ def self.default_import_options(params)
58
+ params.assert_valid_keys(IMPORT_OPTIONS_KEYS)
59
+ self._default_import_options = _default_import_options.merge(params)
60
+ end
61
+
50
62
  def self.method_missing(method, *args, &block)
51
63
  if index.scopes.include?(method)
52
64
  define_singleton_method method do |*args, &block|
53
65
  all.scoping { index.public_send(method, *args, &block) }
54
66
  end
55
67
  send(method, *args, &block)
68
+ else
69
+ super
56
70
  end
57
71
  end
58
72
 
@@ -28,7 +28,7 @@ module Chewy
28
28
  ids = pluck_ids(scope)
29
29
  result = true
30
30
 
31
- while ids.any?
31
+ while ids.present?
32
32
  result &= yield grouped_objects(default_scope_where_ids_in(ids))
33
33
  break if ids.size < batch_size
34
34
  ids = pluck_ids(scope.where(target_id.gt(ids.last)))
@@ -57,6 +57,16 @@ module Chewy
57
57
  ::ActiveRecord::Base
58
58
  end
59
59
  end
60
+
61
+ module Rails5
62
+ def pluck_ids(scope)
63
+ scope.except(:includes).distinct.pluck(target.primary_key.to_sym)
64
+ end
65
+ end
66
+
67
+ if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 5
68
+ Chewy::Type::Adapter::ActiveRecord.prepend(Rails5)
69
+ end
60
70
  end
61
71
  end
62
72
  end
@@ -94,13 +94,7 @@ module Chewy
94
94
  additional_scope = load_options[load_options[:_type].type_name.to_sym].try(:[], :scope) || load_options[:scope]
95
95
 
96
96
  scope = all_scope_where_ids_in(objects.map(&primary_key))
97
- loaded_objects = if additional_scope.is_a?(Proc)
98
- scope.instance_exec(&additional_scope)
99
- elsif additional_scope.is_a?(relation_class) && scope.respond_to?(:merge)
100
- scope.merge(additional_scope)
101
- else
102
- scope
103
- end.index_by { |object| object.public_send(primary_key).to_s }
97
+ loaded_objects = load_scope_objects(scope, additional_scope).index_by { |object| object.public_send(primary_key).to_s }
104
98
 
105
99
  objects.map { |object| loaded_objects[object.public_send(primary_key).to_s] }
106
100
  end
@@ -108,16 +102,14 @@ module Chewy
108
102
  private
109
103
 
110
104
  def import_objects(collection, batch_size)
111
- hash = collection.index_by do |entity|
112
- entity.is_a?(object_class) ? entity.public_send(primary_key) : entity
113
- end
105
+ hash = Hash[identify(collection).zip(collection)]
114
106
 
115
107
  indexed = hash.keys.each_slice(batch_size).map do |ids|
116
108
  batch = default_scope_where_ids_in(ids)
117
109
  if batch.empty?
118
110
  true
119
111
  else
120
- batch.each { |object| hash.delete(object.public_send(primary_key)) }
112
+ identify(batch).each { |id| hash.delete(id) }
121
113
  yield grouped_objects(batch)
122
114
  end
123
115
  end.all?
@@ -148,6 +140,16 @@ module Chewy
148
140
  def model_of_relation relation
149
141
  relation.klass
150
142
  end
143
+
144
+ def load_scope_objects(scope, additional_scope = nil)
145
+ if additional_scope.is_a?(Proc)
146
+ scope.instance_exec(&additional_scope)
147
+ elsif additional_scope.is_a?(relation_class) && scope.respond_to?(:merge)
148
+ scope.merge(additional_scope)
149
+ else
150
+ scope
151
+ end
152
+ end
151
153
  end
152
154
  end
153
155
  end
@@ -13,16 +13,6 @@ module Chewy
13
13
  target.is_a?(::Sequel::Dataset))
14
14
  end
15
15
 
16
- def identify collection
17
- if collection.is_a?(relation_class)
18
- pluck_ids(collection)
19
- else
20
- Array.wrap(collection).map do |entity|
21
- entity.is_a?(object_class) ? entity.public_send(primary_key) : entity
22
- end
23
- end
24
- end
25
-
26
16
  private
27
17
 
28
18
  def cleanup_default_scope!
@@ -39,8 +29,8 @@ module Chewy
39
29
  ids = pluck_ids(scope)
40
30
  result = true
41
31
 
42
- while ids.any?
43
- result &= yield grouped_objects(default_scope_where_ids_in(ids))
32
+ while ids.present?
33
+ result &= yield grouped_objects(default_scope_where_ids_in(ids).all)
44
34
  break if ids.size < batch_size
45
35
  ids = pluck_ids(scope.where { |o| o.__send__(primary_key) > ids.last })
46
36
  end
@@ -52,6 +42,10 @@ module Chewy
52
42
  target.primary_key
53
43
  end
54
44
 
45
+ def all_scope
46
+ target.dataset
47
+ end
48
+
55
49
  def pluck_ids(scope)
56
50
  scope.distinct.select_map(primary_key)
57
51
  end
@@ -71,6 +65,10 @@ module Chewy
71
65
  def object_class
72
66
  ::Sequel::Model
73
67
  end
68
+
69
+ def load_scope_objects(*args)
70
+ super.all
71
+ end
74
72
  end
75
73
  end
76
74
  end
@@ -3,6 +3,8 @@ module Chewy
3
3
  module Import
4
4
  extend ActiveSupport::Concern
5
5
 
6
+ BULK_OPTIONS = [:suffix, :bulk_size, :refresh, :consistency, :replication]
7
+
6
8
  module ClassMethods
7
9
  # Perform import operation for specified documents.
8
10
  # Returns true or false depending on success.
@@ -11,15 +13,19 @@ module Chewy
11
13
  # UsersIndex::User.import User.active # imports active users
12
14
  # UsersIndex::User.import [1, 2, 3] # imports users with specified ids
13
15
  # UsersIndex::User.import users # imports users collection
14
- # UsersIndex::User.import refresh: false # to disable index refreshing after import
15
16
  # UsersIndex::User.import suffix: Time.now.to_i # imports data to index with specified suffix if such is exists
17
+ # UsersIndex::User.import refresh: false # to disable index refreshing after import
16
18
  # UsersIndex::User.import batch_size: 300 # import batch size
19
+ # UsersIndex::User.import bulk_size: 10.megabytes # import ElasticSearch bulk size in bytes
20
+ # UsersIndex::User.import consistency: :quorum # explicit write consistency setting for the operation (one, quorum, all)
21
+ # UsersIndex::User.import replication: :async # explicitly set the replication type (sync, async)
17
22
  #
18
23
  # See adapters documentation for more details.
19
24
  #
20
25
  def import *args
21
26
  import_options = args.extract_options!
22
- bulk_options = import_options.reject { |k, v| ![:refresh, :suffix].include?(k) }.reverse_merge!(refresh: true)
27
+ import_options.reverse_merge! _default_import_options
28
+ bulk_options = import_options.reject { |k, _| !BULK_OPTIONS.include?(k) }.reverse_merge!(refresh: true)
23
29
 
24
30
  index.create!(bulk_options.slice(:suffix)) unless index.exists?
25
31
 
@@ -28,7 +34,7 @@ module Chewy
28
34
  indexed_objects = build_root.parent_id && fetch_indexed_objects(action_objects.values.flatten)
29
35
  body = bulk_body(action_objects, indexed_objects)
30
36
 
31
- errors = bulk(bulk_options.merge(body: body)) if body.any?
37
+ errors = bulk(bulk_options.merge(body: body)) if body.present?
32
38
 
33
39
  fill_payload_import payload, action_objects
34
40
  fill_payload_errors payload, errors if errors.present?
@@ -39,15 +45,7 @@ module Chewy
39
45
 
40
46
  # Perform import operation for specified documents.
41
47
  # Raises Chewy::ImportFailed exception in case of import errors.
42
- #
43
- # UsersIndex::User.import! # imports default data set
44
- # UsersIndex::User.import! User.active # imports active users
45
- # UsersIndex::User.import! [1, 2, 3] # imports users with specified ids
46
- # UsersIndex::User.import! users # imports users collection
47
- # UsersIndex::User.import! refresh: false # to disable index refreshing after import
48
- # UsersIndex::User.import! suffix: Time.now.to_i # imports data to index with specified suffix if such is exists
49
- # UsersIndex::User.import! batch_size: 300 # import batch size
50
- #
48
+ # Options are completely the same as for `import` method
51
49
  # See adapters documentation for more details.
52
50
  #
53
51
  def import! *args
@@ -66,20 +64,46 @@ module Chewy
66
64
  # Adds `:suffix` option to bulk import to index with specified suffix.
67
65
  def bulk options = {}
68
66
  suffix = options.delete(:suffix)
67
+ bulk_size = options.delete(:bulk_size)
68
+ body = options.delete(:body)
69
+ header = { index: index.build_index_name(suffix: suffix), type: type_name }
70
+
71
+ bodies = if bulk_size
72
+ bulk_size -= 1.kilobyte # 1 kilobyte for request header and newlines
73
+ raise ArgumentError.new('Import `:bulk_size` can\'t be less then 1 kilobyte') if bulk_size <= 0
74
+
75
+ body.each_with_object(['']) do |entry, result|
76
+ operation, meta = entry.to_a.first
77
+ data = meta.delete(:data)
78
+ entry = [{ operation => meta }, data].compact.map(&:to_json).join("\n")
79
+ if entry.bytesize > bulk_size
80
+ raise ArgumentError.new('Import `:bulk_size` seems to be less then entry size')
81
+ elsif result.last.bytesize + entry.bytesize > bulk_size
82
+ result.push(entry)
83
+ else
84
+ result[-1] = [result[-1], entry].delete_if(&:blank?).join("\n")
85
+ end
86
+ end
87
+ else
88
+ [body]
89
+ end
69
90
 
70
- result = client.bulk options.merge(index: index.build_index_name(suffix: suffix), type: type_name)
91
+ items = bodies.map do |body|
92
+ result = client.bulk options.merge(header).merge(body: body)
93
+ result.try(:[], 'items') || []
94
+ end.flatten
71
95
  Chewy.wait_for_status
72
96
 
73
- extract_errors result
97
+ extract_errors items
74
98
  end
75
99
 
76
100
  private
77
101
 
78
102
  def bulk_body(action_objects, indexed_objects = nil)
79
- action_objects.inject([]) do |result, (action, objects)|
103
+ action_objects.flat_map do |action, objects|
80
104
  method = "#{action}_bulk_entry"
81
105
  crutches = Chewy::Type::Crutch::Crutches.new self, objects
82
- result.concat(objects.map { |object| send(method, object, indexed_objects, crutches) }.flatten)
106
+ objects.flat_map { |object| send(method, object, indexed_objects, crutches) }
83
107
  end
84
108
  end
85
109
 
@@ -150,15 +174,21 @@ module Chewy
150
174
  end
151
175
 
152
176
  def object_data object, crutches = nil
153
- build_root.compose(object, crutches)[type_name.to_sym]
177
+ if witchcraft?
178
+ cauldron.brew(object, crutches)
179
+ else
180
+ build_root.compose(object, crutches)[type_name.to_sym]
181
+ end
154
182
  end
155
183
 
156
- def extract_errors result
157
- result && result['items'].map do |item|
184
+ def extract_errors items
185
+ items.each.with_object({}) do |item, memo|
158
186
  action = item.keys.first.to_sym
159
187
  data = item.values.first
160
- {action: action, id: data['_id'], error: data['error']} if data['error']
161
- end.compact.group_by { |item| item[:action] }.map do |action, items|
188
+ if data['error']
189
+ (memo[action] ||= []).push(action: action, id: data['_id'], error: data['error'])
190
+ end
191
+ end.map do |action, items|
162
192
  errors = items.group_by { |item| item[:error] }.map do |error, items|
163
193
  {error => items.map { |item| item[:id] }}
164
194
  end.reduce(&:merge)
@@ -181,7 +211,8 @@ module Chewy
181
211
  break if result['hits']['hits'].empty?
182
212
 
183
213
  result['hits']['hits'].map do |hit|
184
- indexed_objects[hit['_id']] = { parent: hit['fields']['_parent'] }
214
+ parent = hit.has_key?('_parent') ? hit['_parent'] : hit['fields']['_parent']
215
+ indexed_objects[hit['_id']] = { parent: parent }
185
216
  end
186
217
  end
187
218