chewy 0.8.3 → 0.8.4

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