chewy 0.8.4 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (179) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +34 -0
  3. data/.rubocop_todo.yml +44 -0
  4. data/.travis.yml +20 -60
  5. data/Appraisals +15 -40
  6. data/CHANGELOG.md +42 -0
  7. data/Gemfile +1 -0
  8. data/Guardfile +5 -5
  9. data/README.md +155 -6
  10. data/Rakefile +11 -1
  11. data/chewy.gemspec +5 -7
  12. data/gemfiles/rails.3.2.activerecord.gemfile +1 -0
  13. data/gemfiles/rails.3.2.activerecord.kaminari.gemfile +1 -0
  14. data/gemfiles/rails.3.2.activerecord.will_paginate.gemfile +1 -0
  15. data/gemfiles/rails.4.2.activerecord.gemfile +1 -0
  16. data/gemfiles/rails.4.2.activerecord.kaminari.gemfile +1 -0
  17. data/gemfiles/rails.4.2.activerecord.will_paginate.gemfile +1 -0
  18. data/gemfiles/{rails.4.2.mongoid.4.0.0.gemfile → rails.4.2.mongoid.4.0.gemfile} +2 -1
  19. data/gemfiles/{rails.4.2.mongoid.4.0.0.kaminari.gemfile → rails.4.2.mongoid.4.0.kaminari.gemfile} +2 -1
  20. data/gemfiles/{rails.4.2.mongoid.4.0.0.will_paginate.gemfile → rails.4.2.mongoid.4.0.will_paginate.gemfile} +2 -1
  21. data/gemfiles/{rails.4.2.mongoid.5.1.0.gemfile → rails.4.2.mongoid.5.1.gemfile} +2 -1
  22. data/gemfiles/{rails.4.2.mongoid.5.1.0.kaminari.gemfile → rails.4.2.mongoid.5.1.kaminari.gemfile} +2 -1
  23. data/gemfiles/{rails.4.2.mongoid.5.1.0.will_paginate.gemfile → rails.4.2.mongoid.5.1.will_paginate.gemfile} +2 -1
  24. data/gemfiles/{rails.4.0.activerecord.gemfile → rails.5.0.activerecord.gemfile} +4 -2
  25. data/gemfiles/{rails.4.0.mongoid.4.0.0.kaminari.gemfile → rails.5.0.activerecord.kaminari.gemfile} +4 -2
  26. data/gemfiles/{rails.4.0.mongoid.4.0.0.will_paginate.gemfile → rails.5.0.activerecord.will_paginate.gemfile} +4 -2
  27. data/gemfiles/{sequel.4.31.gemfile → sequel.4.38.gemfile} +3 -2
  28. data/lib/chewy.rb +24 -16
  29. data/lib/chewy/backports/deep_dup.rb +1 -1
  30. data/lib/chewy/backports/duplicable.rb +1 -0
  31. data/lib/chewy/config.rb +13 -7
  32. data/lib/chewy/errors.rb +4 -4
  33. data/lib/chewy/fields/base.rb +19 -14
  34. data/lib/chewy/fields/root.rb +11 -9
  35. data/lib/chewy/index.rb +38 -25
  36. data/lib/chewy/index/actions.rb +17 -17
  37. data/lib/chewy/index/settings.rb +3 -4
  38. data/lib/chewy/journal.rb +107 -0
  39. data/lib/chewy/journal/apply.rb +31 -0
  40. data/lib/chewy/journal/clean.rb +24 -0
  41. data/lib/chewy/journal/entry.rb +83 -0
  42. data/lib/chewy/journal/query.rb +87 -0
  43. data/lib/chewy/log_subscriber.rb +8 -8
  44. data/lib/chewy/minitest.rb +1 -0
  45. data/lib/chewy/minitest/helpers.rb +77 -0
  46. data/lib/chewy/minitest/search_index_receiver.rb +80 -0
  47. data/lib/chewy/query.rb +116 -60
  48. data/lib/chewy/query/compose.rb +5 -6
  49. data/lib/chewy/query/criteria.rb +26 -16
  50. data/lib/chewy/query/filters.rb +9 -9
  51. data/lib/chewy/query/loading.rb +2 -2
  52. data/lib/chewy/query/nodes/and.rb +3 -3
  53. data/lib/chewy/query/nodes/base.rb +1 -1
  54. data/lib/chewy/query/nodes/bool.rb +6 -4
  55. data/lib/chewy/query/nodes/equal.rb +6 -6
  56. data/lib/chewy/query/nodes/exists.rb +2 -2
  57. data/lib/chewy/query/nodes/expr.rb +2 -2
  58. data/lib/chewy/query/nodes/field.rb +35 -31
  59. data/lib/chewy/query/nodes/has_child.rb +1 -0
  60. data/lib/chewy/query/nodes/has_parent.rb +1 -0
  61. data/lib/chewy/query/nodes/has_relation.rb +11 -13
  62. data/lib/chewy/query/nodes/match_all.rb +1 -1
  63. data/lib/chewy/query/nodes/missing.rb +2 -2
  64. data/lib/chewy/query/nodes/not.rb +3 -3
  65. data/lib/chewy/query/nodes/or.rb +3 -3
  66. data/lib/chewy/query/nodes/prefix.rb +4 -3
  67. data/lib/chewy/query/nodes/query.rb +3 -3
  68. data/lib/chewy/query/nodes/range.rb +11 -11
  69. data/lib/chewy/query/nodes/raw.rb +1 -1
  70. data/lib/chewy/query/nodes/regexp.rb +15 -11
  71. data/lib/chewy/query/nodes/script.rb +6 -6
  72. data/lib/chewy/query/pagination/will_paginate.rb +2 -2
  73. data/lib/chewy/railtie.rb +3 -3
  74. data/lib/chewy/rake_helper.rb +51 -30
  75. data/lib/chewy/repository.rb +2 -2
  76. data/lib/chewy/rspec.rb +1 -1
  77. data/lib/chewy/rspec/update_index.rb +46 -47
  78. data/lib/chewy/runtime/version.rb +4 -4
  79. data/lib/chewy/search.rb +7 -5
  80. data/lib/chewy/strategy.rb +10 -8
  81. data/lib/chewy/strategy/atomic.rb +2 -2
  82. data/lib/chewy/strategy/base.rb +4 -4
  83. data/lib/chewy/strategy/bypass.rb +1 -2
  84. data/lib/chewy/strategy/sidekiq.rb +2 -0
  85. data/lib/chewy/strategy/urgent.rb +1 -1
  86. data/lib/chewy/type.rb +51 -45
  87. data/lib/chewy/type/adapter/active_record.rb +23 -12
  88. data/lib/chewy/type/adapter/base.rb +4 -4
  89. data/lib/chewy/type/adapter/mongoid.rb +6 -6
  90. data/lib/chewy/type/adapter/object.rb +15 -12
  91. data/lib/chewy/type/adapter/orm.rb +24 -15
  92. data/lib/chewy/type/adapter/sequel.rb +11 -7
  93. data/lib/chewy/type/crutch.rb +4 -3
  94. data/lib/chewy/type/import.rb +51 -32
  95. data/lib/chewy/type/mapping.rb +17 -17
  96. data/lib/chewy/type/observe.rb +9 -7
  97. data/lib/chewy/type/witchcraft.rb +62 -23
  98. data/lib/chewy/type/wrapper.rb +20 -14
  99. data/lib/chewy/version.rb +1 -1
  100. data/lib/generators/chewy/install_generator.rb +3 -3
  101. data/lib/tasks/chewy.rake +28 -23
  102. data/spec/chewy/config_spec.rb +33 -12
  103. data/spec/chewy/fields/base_spec.rb +143 -154
  104. data/spec/chewy/fields/root_spec.rb +22 -20
  105. data/spec/chewy/fields/time_fields_spec.rb +11 -9
  106. data/spec/chewy/index/actions_spec.rb +27 -4
  107. data/spec/chewy/index/aliases_spec.rb +2 -2
  108. data/spec/chewy/index/settings_spec.rb +72 -50
  109. data/spec/chewy/index_spec.rb +95 -43
  110. data/spec/chewy/journal/apply_spec.rb +120 -0
  111. data/spec/chewy/journal/entry_spec.rb +237 -0
  112. data/spec/chewy/journal_spec.rb +173 -0
  113. data/spec/chewy/minitest/helpers_spec.rb +90 -0
  114. data/spec/chewy/minitest/search_index_receiver_spec.rb +120 -0
  115. data/spec/chewy/query/criteria_spec.rb +504 -237
  116. data/spec/chewy/query/filters_spec.rb +94 -66
  117. data/spec/chewy/query/loading_spec.rb +76 -40
  118. data/spec/chewy/query/nodes/and_spec.rb +3 -7
  119. data/spec/chewy/query/nodes/bool_spec.rb +5 -13
  120. data/spec/chewy/query/nodes/equal_spec.rb +20 -20
  121. data/spec/chewy/query/nodes/exists_spec.rb +7 -7
  122. data/spec/chewy/query/nodes/has_child_spec.rb +42 -23
  123. data/spec/chewy/query/nodes/has_parent_spec.rb +42 -23
  124. data/spec/chewy/query/nodes/match_all_spec.rb +2 -2
  125. data/spec/chewy/query/nodes/missing_spec.rb +6 -5
  126. data/spec/chewy/query/nodes/not_spec.rb +3 -7
  127. data/spec/chewy/query/nodes/or_spec.rb +3 -7
  128. data/spec/chewy/query/nodes/prefix_spec.rb +6 -6
  129. data/spec/chewy/query/nodes/query_spec.rb +3 -3
  130. data/spec/chewy/query/nodes/range_spec.rb +19 -19
  131. data/spec/chewy/query/nodes/raw_spec.rb +2 -2
  132. data/spec/chewy/query/nodes/regexp_spec.rb +31 -19
  133. data/spec/chewy/query/nodes/script_spec.rb +5 -5
  134. data/spec/chewy/query/pagination/kaminari_spec.rb +2 -2
  135. data/spec/chewy/query/pagination/will_paginage_spec.rb +6 -7
  136. data/spec/chewy/query/pagination_spec.rb +2 -3
  137. data/spec/chewy/query_spec.rb +208 -145
  138. data/spec/chewy/repository_spec.rb +8 -8
  139. data/spec/chewy/rspec/update_index_spec.rb +180 -111
  140. data/spec/chewy/search_spec.rb +8 -8
  141. data/spec/chewy/strategy/active_job_spec.rb +2 -2
  142. data/spec/chewy/strategy/atomic_spec.rb +4 -1
  143. data/spec/chewy/strategy/resque_spec.rb +2 -2
  144. data/spec/chewy/strategy/sidekiq_spec.rb +2 -2
  145. data/spec/chewy/type/actions_spec.rb +1 -1
  146. data/spec/chewy/type/adapter/active_record_spec.rb +255 -149
  147. data/spec/chewy/type/adapter/mongoid_spec.rb +169 -108
  148. data/spec/chewy/type/adapter/object_spec.rb +56 -40
  149. data/spec/chewy/type/adapter/sequel_spec.rb +248 -163
  150. data/spec/chewy/type/import_spec.rb +78 -47
  151. data/spec/chewy/type/mapping_spec.rb +6 -6
  152. data/spec/chewy/type/observe_spec.rb +20 -14
  153. data/spec/chewy/type/witchcraft_spec.rb +89 -43
  154. data/spec/chewy/type_spec.rb +4 -3
  155. data/spec/chewy_spec.rb +10 -8
  156. data/spec/spec_helper.rb +3 -0
  157. data/spec/support/active_record.rb +1 -1
  158. data/spec/support/class_helpers.rb +10 -11
  159. data/spec/support/mongoid.rb +2 -2
  160. data/spec/support/sequel.rb +1 -1
  161. metadata +65 -35
  162. data/gemfiles/rails.4.0.activerecord.kaminari.gemfile +0 -14
  163. data/gemfiles/rails.4.0.activerecord.will_paginate.gemfile +0 -14
  164. data/gemfiles/rails.4.0.mongoid.4.0.0.gemfile +0 -15
  165. data/gemfiles/rails.4.0.mongoid.5.1.0.gemfile +0 -15
  166. data/gemfiles/rails.4.0.mongoid.5.1.0.kaminari.gemfile +0 -14
  167. data/gemfiles/rails.4.0.mongoid.5.1.0.will_paginate.gemfile +0 -14
  168. data/gemfiles/rails.4.1.activerecord.gemfile +0 -15
  169. data/gemfiles/rails.4.1.activerecord.kaminari.gemfile +0 -14
  170. data/gemfiles/rails.4.1.activerecord.will_paginate.gemfile +0 -14
  171. data/gemfiles/rails.4.1.mongoid.4.0.0.gemfile +0 -15
  172. data/gemfiles/rails.4.1.mongoid.4.0.0.kaminari.gemfile +0 -14
  173. data/gemfiles/rails.4.1.mongoid.4.0.0.will_paginate.gemfile +0 -14
  174. data/gemfiles/rails.4.1.mongoid.5.1.0.gemfile +0 -15
  175. data/gemfiles/rails.4.1.mongoid.5.1.0.kaminari.gemfile +0 -14
  176. data/gemfiles/rails.4.1.mongoid.5.1.0.will_paginate.gemfile +0 -14
  177. data/gemfiles/rails.5.0.0.beta3.activerecord.gemfile +0 -16
  178. data/gemfiles/rails.5.0.0.beta3.activerecord.kaminari.gemfile +0 -16
  179. data/gemfiles/rails.5.0.0.beta3.activerecord.will_paginate.gemfile +0 -15
@@ -4,7 +4,6 @@ module Chewy
4
4
  class Type
5
5
  module Adapter
6
6
  class ActiveRecord < Orm
7
-
8
7
  def self.accepts?(target)
9
8
  defined?(::ActiveRecord::Base) && (
10
9
  target.is_a?(Class) && target < ::ActiveRecord::Base ||
@@ -22,15 +21,21 @@ module Chewy
22
21
  @default_scope = @default_scope.reorder(nil).limit(nil).offset(nil)
23
22
  end
24
23
 
25
- def import_scope(scope, batch_size)
26
- scope = scope.reorder(target_id.asc).limit(batch_size)
24
+ def import_scope(scope, options)
25
+ scope = scope.reorder(target_id.asc).limit(options[:batch_size])
27
26
 
28
27
  ids = pluck_ids(scope)
29
28
  result = true
30
29
 
31
30
  while ids.present?
32
- result &= yield grouped_objects(default_scope_where_ids_in(ids))
33
- break if ids.size < batch_size
31
+ objects =
32
+ if options[:raw_import]
33
+ raw_default_scope_where_ids_in(ids, options[:raw_import])
34
+ else
35
+ default_scope_where_ids_in(ids)
36
+ end
37
+ result &= yield grouped_objects(objects)
38
+ break if ids.size < options[:batch_size]
34
39
  ids = pluck_ids(scope.where(target_id.gt(ids.last)))
35
40
  end
36
41
 
@@ -49,6 +54,11 @@ module Chewy
49
54
  scope.where(target_id.in(Array.wrap(ids)))
50
55
  end
51
56
 
57
+ def raw_default_scope_where_ids_in(ids, converter)
58
+ sql = default_scope_where_ids_in(ids).to_sql
59
+ object_class.connection.execute(sql).map(&converter)
60
+ end
61
+
52
62
  def relation_class
53
63
  ::ActiveRecord::Relation
54
64
  end
@@ -58,15 +68,16 @@ module Chewy
58
68
  end
59
69
  end
60
70
 
61
- module Rails5
62
- def pluck_ids(scope)
63
- scope.except(:includes).distinct.pluck(target.primary_key.to_sym)
71
+ ActiveSupport.on_load(:active_record) do
72
+ if ::ActiveRecord::VERSION::MAJOR >= 5
73
+ module Rails5
74
+ def pluck_ids(scope)
75
+ scope.except(:includes).distinct.pluck(target.primary_key.to_sym)
76
+ end
77
+ end
78
+ Chewy::Type::Adapter::ActiveRecord.prepend(Rails5)
64
79
  end
65
80
  end
66
-
67
- if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 5
68
- Chewy::Type::Adapter::ActiveRecord.prepend(Rails5)
69
- end
70
81
  end
71
82
  end
72
83
  end
@@ -9,7 +9,7 @@ module Chewy
9
9
 
10
10
  # Returns `true` if this adapter is applicable for the given target.
11
11
  #
12
- def self.accepts? target
12
+ def self.accepts?(_target)
13
13
  true
14
14
  end
15
15
 
@@ -32,7 +32,7 @@ module Chewy
32
32
  # For ORM/ODM it will be an array of ids for simple objects -
33
33
  # just objects themselves
34
34
  #
35
- def identify collection
35
+ def identify(_collection)
36
36
  raise NotImplementedError
37
37
  end
38
38
 
@@ -43,7 +43,7 @@ module Chewy
43
43
  #
44
44
  # Returns true id all the block call returns true and false otherwise
45
45
  #
46
- def import *args, &block
46
+ def import(*_args)
47
47
  raise NotImplementedError
48
48
  end
49
49
 
@@ -53,7 +53,7 @@ module Chewy
53
53
  # load(double(id: 1), double(id: 2), double(id: 3)) #=>
54
54
  # # [<Product id: 1>, nil, <Product id: 3>], assuming, #2 was not found
55
55
  #
56
- def load *args
56
+ def load(*_args)
57
57
  raise NotImplementedError
58
58
  end
59
59
 
@@ -4,14 +4,13 @@ module Chewy
4
4
  class Type
5
5
  module Adapter
6
6
  class Mongoid < Orm
7
-
8
7
  def self.accepts?(target)
9
8
  defined?(::Mongoid::Document) && (
10
9
  target.is_a?(Class) && target.ancestors.include?(::Mongoid::Document) ||
11
10
  target.is_a?(::Mongoid::Criteria))
12
11
  end
13
12
 
14
- def identify collection
13
+ def identify(collection)
15
14
  super(collection).map { |id| id.is_a?(BSON::ObjectId) ? id.to_s : id }
16
15
  end
17
16
 
@@ -27,10 +26,11 @@ module Chewy
27
26
  @default_scope = @default_scope.reorder(nil)
28
27
  end
29
28
 
30
- def import_scope(scope, batch_size)
31
- scope.batch_size(batch_size).no_timeout.pluck(:_id).each_slice(batch_size).map do |ids|
32
- yield grouped_objects(default_scope_where_ids_in(ids))
33
- end.all?
29
+ def import_scope(scope, options)
30
+ scope.batch_size(options[:batch_size]).no_timeout.pluck(:_id)
31
+ .each_slice(options[:batch_size]).map do |ids|
32
+ yield grouped_objects(default_scope_where_ids_in(ids))
33
+ end.all?
34
34
  end
35
35
 
36
36
  def pluck_ids(scope)
@@ -4,7 +4,7 @@ module Chewy
4
4
  class Type
5
5
  module Adapter
6
6
  class Object < Base
7
- def initialize *args
7
+ def initialize(*args)
8
8
  @options = args.extract_options!
9
9
  @target = args.first
10
10
  end
@@ -13,7 +13,7 @@ module Chewy
13
13
  @name ||= (options[:name] || @target).to_s.camelize.demodulize
14
14
  end
15
15
 
16
- def identify collection
16
+ def identify(collection)
17
17
  Array.wrap(collection)
18
18
  end
19
19
 
@@ -32,18 +32,21 @@ module Chewy
32
32
  # But to be destroyed objects need to respond to `id` method as well, so
33
33
  # ElasticSearch could know which one to delete.
34
34
  #
35
- def import *args, &block
36
- import_options = args.extract_options!
37
- batch_size = import_options.delete(:batch_size) || BATCH_SIZE
35
+ def import(*args, &block)
36
+ options = args.extract_options!
37
+ options[:batch_size] ||= BATCH_SIZE
38
38
 
39
- objects = args.empty? && @target.respond_to?(import_all_method) ?
40
- @target.send(import_all_method) : args.flatten.compact
39
+ objects = if args.empty? && @target.respond_to?(import_all_method)
40
+ @target.send(import_all_method)
41
+ else
42
+ args.flatten.compact
43
+ end
41
44
 
42
- import_objects(objects, batch_size, &block)
45
+ import_objects(objects, options, &block)
43
46
  end
44
47
 
45
- def load *args
46
- load_options = args.extract_options!
48
+ def load(*args)
49
+ args.extract_options!
47
50
  objects = args.flatten
48
51
  if target.respond_to?(load_all_method)
49
52
  target.send(load_all_method, objects)
@@ -59,8 +62,8 @@ module Chewy
59
62
 
60
63
  private
61
64
 
62
- def import_objects(objects, batch_size)
63
- objects.each_slice(batch_size).map do |group|
65
+ def import_objects(objects, options)
66
+ objects.each_slice(options[:batch_size]).map do |group|
64
67
  yield grouped_objects(group)
65
68
  end.all?
66
69
  end
@@ -6,7 +6,7 @@ module Chewy
6
6
  class Orm < Base
7
7
  attr_reader :default_scope
8
8
 
9
- def initialize *args
9
+ def initialize(*args)
10
10
  @options = args.extract_options!
11
11
  class_or_relation = args.first
12
12
  if class_or_relation.is_a?(relation_class)
@@ -23,7 +23,7 @@ module Chewy
23
23
  @name ||= (options[:name].presence || target.name).to_s.camelize.demodulize
24
24
  end
25
25
 
26
- def identify collection
26
+ def identify(collection)
27
27
  if collection.is_a?(relation_class)
28
28
  pluck_ids(collection)
29
29
  else
@@ -73,21 +73,26 @@ module Chewy
73
73
  # # or
74
74
  # UsersIndex::User.import users.map(&:id) # user ids will be deleted from index
75
75
  #
76
- def import *args, &block
77
- import_options = args.extract_options!
78
- batch_size = import_options[:batch_size] || BATCH_SIZE
79
-
80
- collection = args.empty? ? default_scope :
81
- (args.one? && args.first.is_a?(relation_class) ? args.first : args.flatten.compact)
76
+ def import(*args, &block)
77
+ options = args.extract_options!
78
+ options[:batch_size] ||= BATCH_SIZE
79
+
80
+ collection = if args.empty?
81
+ default_scope
82
+ elsif args.one? && args.first.is_a?(relation_class)
83
+ args.first
84
+ else
85
+ args.flatten.compact
86
+ end
82
87
 
83
88
  if collection.is_a?(relation_class)
84
- import_scope(collection, batch_size, &block)
89
+ import_scope(collection, options, &block)
85
90
  else
86
- import_objects(collection, batch_size, &block)
91
+ import_objects(collection, options, &block)
87
92
  end
88
93
  end
89
94
 
90
- def load *args
95
+ def load(*args)
91
96
  load_options = args.extract_options!
92
97
  objects = args.flatten
93
98
 
@@ -101,10 +106,10 @@ module Chewy
101
106
 
102
107
  private
103
108
 
104
- def import_objects(collection, batch_size)
109
+ def import_objects(collection, options)
105
110
  hash = Hash[identify(collection).zip(collection)]
106
111
 
107
- indexed = hash.keys.each_slice(batch_size).map do |ids|
112
+ indexed = hash.keys.each_slice(options[:batch_size]).map do |ids|
108
113
  batch = default_scope_where_ids_in(ids)
109
114
  if batch.empty?
110
115
  true
@@ -114,7 +119,7 @@ module Chewy
114
119
  end
115
120
  end.all?
116
121
 
117
- deleted = hash.keys.each_slice(batch_size).map do |group|
122
+ deleted = hash.keys.each_slice(options[:batch_size]).map do |group|
118
123
  yield delete: hash.values_at(*group)
119
124
  end.all?
120
125
 
@@ -137,7 +142,7 @@ module Chewy
137
142
  target.where(nil)
138
143
  end
139
144
 
140
- def model_of_relation relation
145
+ def model_of_relation(relation)
141
146
  relation.klass
142
147
  end
143
148
 
@@ -150,6 +155,10 @@ module Chewy
150
155
  scope
151
156
  end
152
157
  end
158
+
159
+ def grouped_objects(objects)
160
+ options[:delete_if] ? super : { index: objects.to_a }
161
+ end
153
162
  end
154
163
  end
155
164
  end
@@ -23,16 +23,16 @@ module Chewy
23
23
  @default_scope = @default_scope.unordered.unlimited
24
24
  end
25
25
 
26
- def import_scope(scope, batch_size)
27
- scope = scope.unordered.order(::Sequel.asc(primary_key)).limit(batch_size)
26
+ def import_scope(scope, options)
27
+ scope = scope.unordered.order(::Sequel.asc(primary_key_with_table_name)).limit(options[:batch_size])
28
28
 
29
29
  ids = pluck_ids(scope)
30
30
  result = true
31
31
 
32
32
  while ids.present?
33
33
  result &= yield grouped_objects(default_scope_where_ids_in(ids).all)
34
- break if ids.size < batch_size
35
- ids = pluck_ids(scope.where { |o| o.__send__(primary_key) > ids.last })
34
+ break if ids.size < options[:batch_size]
35
+ ids = pluck_ids(scope.where { |o| o.__send__(primary_key_with_table_name) > ids.last })
36
36
  end
37
37
 
38
38
  result
@@ -42,19 +42,23 @@ module Chewy
42
42
  target.primary_key
43
43
  end
44
44
 
45
+ def primary_key_with_table_name
46
+ "#{target.table_name}__#{primary_key}".to_sym
47
+ end
48
+
45
49
  def all_scope
46
50
  target.dataset
47
51
  end
48
52
 
49
53
  def pluck_ids(scope)
50
- scope.distinct.select_map(primary_key)
54
+ scope.distinct.select_map(primary_key_with_table_name)
51
55
  end
52
56
 
53
57
  def scope_where_ids_in(scope, ids)
54
- scope.where(primary_key => Array.wrap(ids))
58
+ scope.where(primary_key_with_table_name => Array.wrap(ids))
55
59
  end
56
60
 
57
- def model_of_relation relation
61
+ def model_of_relation(relation)
58
62
  relation.model
59
63
  end
60
64
 
@@ -9,8 +9,9 @@ module Chewy
9
9
  end
10
10
 
11
11
  class Crutches
12
- def initialize type, collection
13
- @type, @collection = type, collection
12
+ def initialize(type, collection)
13
+ @type = type
14
+ @collection = collection
14
15
  @type._crutches.keys.each do |name|
15
16
  singleton_class.class_eval <<-METHOD, __FILE__, __LINE__ + 1
16
17
  def #{name}
@@ -22,7 +23,7 @@ module Chewy
22
23
  end
23
24
 
24
25
  module ClassMethods
25
- def crutch name, &block
26
+ def crutch(name, &block)
26
27
  self._crutches = _crutches.merge(name.to_sym => block)
27
28
  end
28
29
  end
@@ -3,7 +3,7 @@ module Chewy
3
3
  module Import
4
4
  extend ActiveSupport::Concern
5
5
 
6
- BULK_OPTIONS = [:suffix, :bulk_size, :refresh, :consistency, :replication]
6
+ BULK_OPTIONS = [:suffix, :bulk_size, :refresh, :consistency, :replication].freeze
7
7
 
8
8
  module ClassMethods
9
9
  # Perform import operation for specified documents.
@@ -13,8 +13,9 @@ module Chewy
13
13
  # UsersIndex::User.import User.active # imports active users
14
14
  # UsersIndex::User.import [1, 2, 3] # imports users with specified ids
15
15
  # UsersIndex::User.import users # imports users collection
16
- # UsersIndex::User.import suffix: Time.now.to_i # imports data to index with specified suffix if such is exists
16
+ # UsersIndex::User.import suffix: Time.now.to_i # imports data to index with specified suffix if such exists
17
17
  # UsersIndex::User.import refresh: false # to disable index refreshing after import
18
+ # UsersIndex::User.import journal: true # import will record all the actions into special journal index
18
19
  # UsersIndex::User.import batch_size: 300 # import batch size
19
20
  # UsersIndex::User.import bulk_size: 10.megabytes # import ElasticSearch bulk size in bytes
20
21
  # UsersIndex::User.import consistency: :quorum # explicit write consistency setting for the operation (one, quorum, all)
@@ -22,7 +23,7 @@ module Chewy
22
23
  #
23
24
  # See adapters documentation for more details.
24
25
  #
25
- def import *args
26
+ def import(*args)
26
27
  import_options = args.extract_options!
27
28
  import_options.reverse_merge! _default_import_options
28
29
  bulk_options = import_options.reject { |k, _| !BULK_OPTIONS.include?(k) }.reverse_merge!(refresh: true)
@@ -31,10 +32,13 @@ module Chewy
31
32
 
32
33
  ActiveSupport::Notifications.instrument 'import_objects.chewy', type: self do |payload|
33
34
  adapter.import(*args, import_options) do |action_objects|
35
+ journal = Chewy::Journal.new(self)
36
+ journal.add(action_objects) if import_options.fetch(:journal) { journal? }
37
+
34
38
  indexed_objects = build_root.parent_id && fetch_indexed_objects(action_objects.values.flatten)
35
39
  body = bulk_body(action_objects, indexed_objects)
36
40
 
37
- errors = bulk(bulk_options.merge(body: body)) if body.present?
41
+ errors = bulk(bulk_options.merge(body: body, journal: journal)) if body.present?
38
42
 
39
43
  fill_payload_import payload, action_objects
40
44
  fill_payload_errors payload, errors if errors.present?
@@ -48,12 +52,12 @@ module Chewy
48
52
  # Options are completely the same as for `import` method
49
53
  # See adapters documentation for more details.
50
54
  #
51
- def import! *args
55
+ def import!(*args)
52
56
  errors = nil
53
- subscriber = ActiveSupport::Notifications.subscribe('import_objects.chewy') do |*args|
54
- errors = args.last[:errors]
57
+ subscriber = ActiveSupport::Notifications.subscribe('import_objects.chewy') do |*notification_args|
58
+ errors = notification_args.last[:errors]
55
59
  end
56
- import *args
60
+ import(*args)
57
61
  raise Chewy::ImportFailed.new(self, errors) if errors.present?
58
62
  true
59
63
  ensure
@@ -62,34 +66,42 @@ module Chewy
62
66
 
63
67
  # Wraps elasticsearch-ruby client indices bulk method.
64
68
  # Adds `:suffix` option to bulk import to index with specified suffix.
65
- def bulk options = {}
69
+ def bulk(options = {})
66
70
  suffix = options.delete(:suffix)
67
71
  bulk_size = options.delete(:bulk_size)
68
72
  body = options.delete(:body)
73
+ journal = options.delete(:journal)
69
74
  header = { index: index.build_index_name(suffix: suffix), type: type_name }
70
75
 
71
76
  bodies = if bulk_size
72
77
  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
78
+ raise ArgumentError, 'Import `:bulk_size` can\'t be less than 1 kilobyte' if bulk_size <= 0
74
79
 
75
- body.each_with_object(['']) do |entry, result|
80
+ entries = body.each_with_object(['']) do |entry, result|
76
81
  operation, meta = entry.to_a.first
77
82
  data = meta.delete(:data)
78
83
  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
84
+
85
+ raise ArgumentError, 'Import `:bulk_size` seems to be less than entry size' if entry.bytesize > bulk_size
86
+
87
+ if result.last.bytesize + entry.bytesize > bulk_size
82
88
  result.push(entry)
83
89
  else
84
90
  result[-1] = [result[-1], entry].delete_if(&:blank?).join("\n")
85
91
  end
86
92
  end
93
+ entries.map { |entry| entry + "\n" }
87
94
  else
88
95
  [body]
89
96
  end
90
97
 
91
- items = bodies.map do |body|
92
- result = client.bulk options.merge(header).merge(body: body)
98
+ if journal.any_records?
99
+ Chewy::Journal.create
100
+ bodies += [journal.bulk_body]
101
+ end
102
+
103
+ items = bodies.map do |item_body|
104
+ result = client.bulk options.merge(header).merge(body: item_body)
93
105
  result.try(:[], 'items') || []
94
106
  end.flatten
95
107
  Chewy.wait_for_status
@@ -97,6 +109,10 @@ module Chewy
97
109
  extract_errors items
98
110
  end
99
111
 
112
+ def journal?
113
+ _default_import_options.fetch(:journal) { Chewy.configuration[:journal] }
114
+ end
115
+
100
116
  private
101
117
 
102
118
  def bulk_body(action_objects, indexed_objects = nil)
@@ -107,7 +123,7 @@ module Chewy
107
123
  end
108
124
  end
109
125
 
110
- def delete_bulk_entry(object, indexed_objects = nil, crutches = nil)
126
+ def delete_bulk_entry(object, indexed_objects = nil, _crutches = nil)
111
127
  entry = {}
112
128
 
113
129
  if root_object.id
@@ -121,7 +137,8 @@ module Chewy
121
137
 
122
138
  if root_object.parent_id
123
139
  existing_object = entry[:_id].present? && indexed_objects && indexed_objects[entry[:_id].to_s]
124
- entry.merge!(parent: existing_object[:parent]) if existing_object
140
+ return [] unless existing_object
141
+ entry[:parent] = existing_object[:parent]
125
142
  end
126
143
 
127
144
  [{ delete: entry }]
@@ -153,7 +170,7 @@ module Chewy
153
170
  end
154
171
  end
155
172
 
156
- def fill_payload_import payload, action_objects
173
+ def fill_payload_import(payload, action_objects)
157
174
  imported = Hash[action_objects.map { |action, objects| [action, objects.count] }]
158
175
  imported.each do |action, count|
159
176
  payload[:import] ||= {}
@@ -162,9 +179,9 @@ module Chewy
162
179
  end
163
180
  end
164
181
 
165
- def fill_payload_errors payload, errors
166
- errors.each do |action, errors|
167
- errors.each do |error, documents|
182
+ def fill_payload_errors(payload, import_errors)
183
+ import_errors.each do |action, action_errors|
184
+ action_errors.each do |error, documents|
168
185
  payload[:errors] ||= {}
169
186
  payload[:errors][action] ||= {}
170
187
  payload[:errors][action][error] ||= []
@@ -173,26 +190,28 @@ module Chewy
173
190
  end
174
191
  end
175
192
 
176
- def object_data object, crutches = nil
193
+ def object_data(object, crutches = nil)
177
194
  if witchcraft?
178
195
  cauldron.brew(object, crutches)
179
196
  else
180
- build_root.compose(object, crutches)[type_name.to_sym]
197
+ build_root.compose(object, crutches)[type_name.to_s]
181
198
  end
182
199
  end
183
200
 
184
- def extract_errors items
185
- items.each.with_object({}) do |item, memo|
201
+ def extract_errors(items)
202
+ items = items.each.with_object({}) do |item, memo|
186
203
  action = item.keys.first.to_sym
187
204
  data = item.values.first
188
205
  if data['error']
189
206
  (memo[action] ||= []).push(action: action, id: data['_id'], error: data['error'])
190
207
  end
191
- end.map do |action, items|
192
- errors = items.group_by { |item| item[:error] }.map do |error, items|
193
- {error => items.map { |item| item[:id] }}
208
+ end
209
+
210
+ items.map do |action, action_items|
211
+ errors = action_items.group_by { |item| item[:error] }.map do |error, error_items|
212
+ { error => error_items.map { |item| item[:id] } }
194
213
  end.reduce(&:merge)
195
- {action => errors}
214
+ { action => errors }
196
215
  end.reduce(&:merge) || {}
197
216
  end
198
217
 
@@ -207,11 +226,11 @@ module Chewy
207
226
 
208
227
  indexed_objects = {}
209
228
 
210
- while result = client.scroll(scroll_id: result['_scroll_id'], scroll: '1m') do
229
+ while (result = client.scroll(scroll_id: result['_scroll_id'], scroll: '1m'))
211
230
  break if result['hits']['hits'].empty?
212
231
 
213
232
  result['hits']['hits'].map do |hit|
214
- parent = hit.has_key?('_parent') ? hit['_parent'] : hit['fields']['_parent']
233
+ parent = hit.key?('_parent') ? hit['_parent'] : hit['fields']['_parent']
215
234
  indexed_objects[hit['_id']] = { parent: parent }
216
235
  end
217
236
  end