chewy 0.6.2 → 0.7.0

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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.travis.yml +35 -29
  4. data/Appraisals +37 -0
  5. data/CHANGELOG.md +115 -4
  6. data/Gemfile +2 -3
  7. data/README.md +135 -40
  8. data/chewy.gemspec +4 -3
  9. data/gemfiles/rails.3.2.activerecord.gemfile +13 -0
  10. data/gemfiles/rails.3.2.activerecord.kaminari.gemfile +14 -0
  11. data/gemfiles/rails.3.2.activerecord.will_paginate.gemfile +14 -0
  12. data/gemfiles/rails.4.0.activerecord.gemfile +13 -0
  13. data/gemfiles/rails.4.0.activerecord.kaminari.gemfile +14 -0
  14. data/gemfiles/rails.4.0.activerecord.will_paginate.gemfile +14 -0
  15. data/gemfiles/rails.4.0.mongoid.gemfile +13 -0
  16. data/gemfiles/rails.4.0.mongoid.kaminari.gemfile +14 -0
  17. data/gemfiles/rails.4.0.mongoid.will_paginate.gemfile +14 -0
  18. data/gemfiles/rails.4.1.activerecord.gemfile +13 -0
  19. data/gemfiles/rails.4.1.activerecord.kaminari.gemfile +14 -0
  20. data/gemfiles/rails.4.1.activerecord.will_paginate.gemfile +14 -0
  21. data/gemfiles/rails.4.1.mongoid.gemfile +13 -0
  22. data/gemfiles/rails.4.1.mongoid.kaminari.gemfile +14 -0
  23. data/gemfiles/rails.4.1.mongoid.will_paginate.gemfile +14 -0
  24. data/gemfiles/rails.4.2.activerecord.gemfile +13 -0
  25. data/gemfiles/rails.4.2.activerecord.kaminari.gemfile +14 -0
  26. data/gemfiles/rails.4.2.activerecord.will_paginate.gemfile +14 -0
  27. data/gemfiles/rails.4.2.mongoid.gemfile +13 -0
  28. data/gemfiles/rails.4.2.mongoid.kaminari.gemfile +14 -0
  29. data/gemfiles/rails.4.2.mongoid.will_paginate.gemfile +14 -0
  30. data/lib/chewy.rb +65 -0
  31. data/lib/chewy/config.rb +44 -93
  32. data/lib/chewy/errors.rb +14 -5
  33. data/lib/chewy/fields/base.rb +8 -7
  34. data/lib/chewy/fields/root.rb +2 -2
  35. data/lib/chewy/index.rb +7 -9
  36. data/lib/chewy/log_subscriber.rb +34 -0
  37. data/lib/chewy/query.rb +41 -27
  38. data/lib/chewy/query/criteria.rb +28 -23
  39. data/lib/chewy/query/scoping.rb +20 -0
  40. data/lib/chewy/railtie.rb +51 -13
  41. data/lib/chewy/repository.rb +61 -0
  42. data/lib/chewy/rspec/update_index.rb +3 -6
  43. data/lib/chewy/search.rb +28 -7
  44. data/lib/chewy/strategy.rb +60 -0
  45. data/lib/chewy/strategy/atomic.rb +31 -0
  46. data/lib/chewy/strategy/base.rb +27 -0
  47. data/lib/chewy/strategy/bypass.rb +15 -0
  48. data/lib/chewy/strategy/urgent.rb +17 -0
  49. data/lib/chewy/type.rb +19 -5
  50. data/lib/chewy/type/adapter/active_record.rb +28 -117
  51. data/lib/chewy/type/adapter/base.rb +35 -0
  52. data/lib/chewy/type/adapter/mongoid.rb +23 -123
  53. data/lib/chewy/type/adapter/object.rb +41 -19
  54. data/lib/chewy/type/adapter/orm.rb +142 -0
  55. data/lib/chewy/type/import.rb +43 -16
  56. data/lib/chewy/type/observe.rb +8 -21
  57. data/lib/chewy/version.rb +1 -1
  58. data/lib/tasks/chewy.rake +8 -4
  59. data/spec/chewy/config_spec.rb +20 -97
  60. data/spec/chewy/fields/base_spec.rb +24 -11
  61. data/spec/chewy/fields/time_fields_spec.rb +27 -0
  62. data/spec/chewy/index/settings_spec.rb +2 -1
  63. data/spec/chewy/index_spec.rb +98 -79
  64. data/spec/chewy/query/criteria_spec.rb +14 -0
  65. data/spec/chewy/query_spec.rb +1 -1
  66. data/spec/chewy/repository_spec.rb +50 -0
  67. data/spec/chewy/search_spec.rb +100 -0
  68. data/spec/chewy/strategy_spec.rb +109 -0
  69. data/spec/chewy/type/adapter/active_record_spec.rb +110 -46
  70. data/spec/chewy/type/adapter/mongoid_spec.rb +123 -74
  71. data/spec/chewy/type/adapter/object_spec.rb +51 -34
  72. data/spec/chewy/type/import_spec.rb +21 -21
  73. data/spec/chewy/type/observe_spec.rb +26 -29
  74. data/spec/chewy/type_spec.rb +19 -0
  75. data/spec/chewy_spec.rb +19 -3
  76. data/spec/spec_helper.rb +1 -1
  77. data/spec/support/active_record.rb +2 -1
  78. data/spec/support/mongoid.rb +29 -38
  79. metadata +85 -55
  80. data/gemfiles/Gemfile.rails-3.2.active_record +0 -6
  81. data/gemfiles/Gemfile.rails-3.2.active_record.kaminari +0 -7
  82. data/gemfiles/Gemfile.rails-3.2.active_record.will_paginate +0 -7
  83. data/gemfiles/Gemfile.rails-4.0.active_record +0 -6
  84. data/gemfiles/Gemfile.rails-4.0.active_record.kaminari +0 -7
  85. data/gemfiles/Gemfile.rails-4.0.active_record.will_paginate +0 -7
  86. data/gemfiles/Gemfile.rails-4.0.mongoid +0 -6
  87. data/gemfiles/Gemfile.rails-4.0.mongoid.kaminari +0 -7
  88. data/gemfiles/Gemfile.rails-4.0.mongoid.will_paginate +0 -7
  89. data/gemfiles/Gemfile.rails-4.1.active_record +0 -6
  90. data/gemfiles/Gemfile.rails-4.1.active_record.kaminari +0 -7
  91. data/gemfiles/Gemfile.rails-4.1.active_record.will_paginate +0 -7
  92. data/gemfiles/Gemfile.rails-4.1.mongoid +0 -6
  93. data/gemfiles/Gemfile.rails-4.1.mongoid.kaminari +0 -7
  94. data/gemfiles/Gemfile.rails-4.1.mongoid.will_paginate +0 -7
  95. data/gemfiles/Gemfile.rails-4.2.active_record +0 -6
  96. data/gemfiles/Gemfile.rails-4.2.active_record.kaminari +0 -7
  97. data/gemfiles/Gemfile.rails-4.2.active_record.will_paginate +0 -7
  98. data/gemfiles/Gemfile.rails-4.2.mongoid +0 -6
  99. data/gemfiles/Gemfile.rails-4.2.mongoid.kaminari +0 -7
  100. data/gemfiles/Gemfile.rails-4.2.mongoid.will_paginate +0 -7
  101. data/spec/chewy/index/search_spec.rb +0 -46
@@ -1,27 +1,65 @@
1
1
  module Chewy
2
2
  class Railtie < Rails::Railtie
3
+ def self.all_engines
4
+ Rails::Engine.subclasses.map(&:instance) + [Rails.application]
5
+ end
6
+
7
+ class RequestStrategy
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ def call(env)
13
+ if env['PATH_INFO'] =~ /^\/assets\//
14
+ @app.call(env)
15
+ else
16
+ Chewy.strategy(Chewy.request_strategy) { @app.call(env) }
17
+ end
18
+ end
19
+ end
20
+
21
+ module MigrationStrategy
22
+ extend ActiveSupport::Concern
23
+ included do
24
+ alias_method_chain :migrate, :chewy
25
+ end
26
+
27
+ def migrate_with_chewy(*args)
28
+ Chewy.strategy(:bypass) { migrate_without_chewy(*args) }
29
+ end
30
+ end
31
+
3
32
  rake_tasks do
4
33
  load 'tasks/chewy.rake'
5
34
  end
6
35
 
7
- initializer 'chewy.add_app_chewy_path' do |app|
8
- app.config.paths.add 'app/chewy'
9
- end
36
+ console do |app|
37
+ Chewy.logger = ActiveRecord::Base.logger if defined?(ActiveRecord)
10
38
 
11
- initializer 'chewy.add_requests_logging' do |app|
12
- ActiveSupport::Notifications.subscribe('import_objects.chewy') do |name, start, finish, id, payload|
13
- duration = ((finish - start).to_f * 10000).round / 10.0
14
- Rails.logger.debug(" \e[1m\e[32m#{payload[:type]} Import (#{duration}ms)\e[0m #{payload[:import]}")
39
+ if app.sandbox?
40
+ Chewy.strategy(:bypass)
41
+ else
42
+ Chewy.strategy(:urgent)
15
43
  end
44
+ end
45
+
46
+ initializer 'chewy.logger', after: 'active_record.logger' do
47
+ ActiveSupport.on_load(:active_record) { Chewy.logger ||= ActiveRecord::Base.logger }
48
+ end
16
49
 
17
- ActiveSupport::Notifications.subscribe('search_query.chewy') do |name, start, finish, id, payload|
18
- duration = ((finish - start).to_f * 10000).round / 10.0
19
- Rails.logger.debug(" \e[1m\e[32m#{payload[:index]} Search (#{duration}ms)\e[0m #{payload[:request]}")
50
+ initializer 'chewy.migration_strategy' do
51
+ ActiveSupport.on_load(:active_record) do
52
+ ActiveRecord::Migration.send(:include, MigrationStrategy)
20
53
  end
54
+ end
21
55
 
22
- ActiveSupport::Notifications.subscribe('delete_query.chewy') do |name, start, finish, id, payload|
23
- duration = ((finish - start).to_f * 10000).round / 10.0
24
- Rails.logger.debug(" \e[1m\e[32m#{payload[:index]} Delete by Query (#{duration}ms)\e[0m #{payload[:request]}")
56
+ initializer 'chewy.request_strategy' do |app|
57
+ app.config.middleware.insert_after(Rack::Runtime, RequestStrategy)
58
+ end
59
+
60
+ initializer 'chewy.add_app_chewy_path' do |app|
61
+ Chewy::Railtie.all_engines.each do |engine|
62
+ engine.paths.add 'app/chewy'
25
63
  end
26
64
  end
27
65
  end
@@ -0,0 +1,61 @@
1
+ module Chewy
2
+ class Repository
3
+ include Singleton
4
+
5
+ attr_reader :analyzers, :tokenizers, :filters, :char_filters
6
+
7
+ def self.delegated
8
+ public_instance_methods - self.superclass.public_instance_methods - Singleton.public_instance_methods
9
+ end
10
+
11
+ def self.repository name
12
+ plural_name = name.to_s.pluralize
13
+
14
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
15
+ def #{name}(name, options = nil)
16
+ options ? #{plural_name}[name.to_sym] = options : #{plural_name}[name.to_sym]
17
+ end
18
+ METHOD
19
+ end
20
+
21
+ # Analysers repository:
22
+ #
23
+ # Chewy.analyzer :my_analyzer2, {
24
+ # type: custom,
25
+ # tokenizer: 'my_tokenizer1',
26
+ # filter : ['my_token_filter1', 'my_token_filter2']
27
+ # char_filter : ['my_html']
28
+ # }
29
+ # Chewy.analyzer(:my_analyzer2) # => {type: 'custom', tokenizer: ...}
30
+ #
31
+ repository :analyzer
32
+
33
+ # Tokenizers repository:
34
+ #
35
+ # Chewy.tokenizer :my_tokenizer1, {type: standard, max_token_length: 900}
36
+ # Chewy.tokenizer(:my_tokenizer1) # => {type: standard, max_token_length: 900}
37
+ #
38
+ repository :tokenizer
39
+
40
+ # Token filters repository:
41
+ #
42
+ # Chewy.filter :my_token_filter1, {type: stop, stopwords: [stop1, stop2, stop3, stop4]}
43
+ # Chewy.filter(:my_token_filter1) # => {type: stop, stopwords: [stop1, stop2, stop3, stop4]}
44
+ #
45
+ repository :filter
46
+
47
+ # Char filters repository:
48
+ #
49
+ # Chewy.char_filter :my_html, {type: html_strip, escaped_tags: [xxx, yyy], read_ahead: 1024}
50
+ # Chewy.char_filter(:my_html) # => {type: html_strip, escaped_tags: [xxx, yyy], read_ahead: 1024}
51
+ #
52
+ repository :char_filter
53
+
54
+ def initialize
55
+ @analyzers = {}
56
+ @tokenizers = {}
57
+ @filters = {}
58
+ @char_filters = {}
59
+ end
60
+ end
61
+ end
@@ -18,7 +18,7 @@ require 'i18n/core_ext/hash'
18
18
  # Combined matcher chain methods:
19
19
  #
20
20
  # specify { expect { user1.destroy!; user2.save! } }
21
- # .to update_index(UsersIndex:User).and_reindex(user2).and_delete(user1)
21
+ # .to update_index(UsersIndex:User).and_reindex(user2).and_delete(user1) }
22
22
  #
23
23
  RSpec::Matchers.define :update_index do |type_name, options = {}|
24
24
 
@@ -107,11 +107,8 @@ RSpec::Matchers.define :update_index do |type_name, options = {}|
107
107
  {}
108
108
  end
109
109
 
110
- if options[:atomic] == false
111
- block.call
112
- else
113
- Chewy.atomic { block.call }
114
- end
110
+ ActiveSupport::Deprecation.warn('`atomic: false` option is removed and not effective anymore, use `strategy: :atomic` option instead') if options.key?(:atomic)
111
+ Chewy.strategy(options[:strategy] || :atomic) { block.call }
115
112
 
116
113
  @updated.each do |updated_document|
117
114
  if body = updated_document[:index]
@@ -15,20 +15,41 @@ module Chewy
15
15
 
16
16
  module ClassMethods
17
17
  def all
18
- Chewy::Query.new(search_index, types: search_type)
18
+ query_class.scopes.last || query_class.new(self)
19
19
  end
20
20
 
21
21
  def search_string query, options = {}
22
- options = options.merge(index: search_index.index_name, type: search_type, q: query)
23
- client.search(options)
22
+ options = options.merge(
23
+ index: all._indexes.map(&:index_name),
24
+ type: all._types.map(&:type_name),
25
+ q: query)
26
+ Chewy.client.search(options)
24
27
  end
25
28
 
26
- def search_index
27
- raise NotImplementedError
29
+ private
30
+
31
+ def query_class
32
+ @query_class ||= begin
33
+ query_class = Class.new(Chewy::Query)
34
+ if self < Chewy::Type
35
+ index_scopes = index.scopes - scopes
36
+
37
+ delegate_scoped index, query_class, index_scopes
38
+ delegate_scoped index, self, index_scopes
39
+ end
40
+ delegate_scoped self, query_class, scopes
41
+ const_set('Query', query_class)
42
+ end
28
43
  end
29
44
 
30
- def search_type
31
- raise NotImplementedError
45
+ def delegate_scoped source, destination, methods
46
+ methods.each do |method|
47
+ destination.class_eval do
48
+ define_method method do |*args, &block|
49
+ scoping { source.public_send(method, *args, &block) }
50
+ end
51
+ end
52
+ end
32
53
  end
33
54
  end
34
55
  end
@@ -0,0 +1,60 @@
1
+ require 'chewy/strategy/base'
2
+ require 'chewy/strategy/bypass'
3
+ require 'chewy/strategy/urgent'
4
+ require 'chewy/strategy/atomic'
5
+
6
+ module Chewy
7
+ # This class represents strategies stack with `:base`
8
+ # Strategy on top of it. This causes raising exceptions
9
+ # on every index update attempt, so other strategy must
10
+ # be choosen.
11
+ #
12
+ # User.first.save # Raises UndefinedUpdateStrategy exception
13
+ #
14
+ # Chewy.strategy(:atomic) do
15
+ # User.last.save # Save user according to the `:atomic` strategy rules
16
+ # end
17
+ #
18
+ class Strategy
19
+ def initialize
20
+ @stack = [resolve(Chewy.root_strategy).new]
21
+ end
22
+
23
+ def current
24
+ @stack.last
25
+ end
26
+
27
+ def push name
28
+ result = @stack.push resolve(name).new
29
+ debug "[#{@stack.size}] <- #{current.name}"
30
+ result
31
+ end
32
+
33
+ def pop
34
+ raise "Can't pop root strategy" if @stack.one?
35
+ debug "[#{@stack.size}] -> #{current.name}"
36
+ result = @stack.pop.tap(&:leave)
37
+ result
38
+ end
39
+
40
+ def wrap name
41
+ push name
42
+ yield
43
+ ensure
44
+ pop
45
+ end
46
+
47
+ private
48
+
49
+ def debug string
50
+ if Chewy.logger && Chewy.logger.debug?
51
+ line = caller.detect { |line| line !~ %r{lib/chewy/strategy.rb:|lib/chewy.rb:} }
52
+ Chewy.logger.debug(["Chewy strategies stack: #{string}", line.sub(/:in\s.+$/, '')].join(' @ '))
53
+ end
54
+ end
55
+
56
+ def resolve name
57
+ "Chewy::Strategy::#{name.to_s.camelize}".constantize or raise "Can't find update strategy `#{name}`"
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,31 @@
1
+ module Chewy
2
+ class Strategy
3
+ # This strategy accomulates all the objects prepared for
4
+ # indexing and fires index process when strategy is popped
5
+ # from the strategies stack.
6
+ #
7
+ # Chewy.strategy(:atomic) do
8
+ # User.all.map(&:save) # Does nothing here
9
+ # Post.all.map(&:save) # And here
10
+ # # It imports all the changed users and posts right here
11
+ # # before block leaving with bulk ES API, kinda optimization
12
+ # end
13
+ #
14
+ class Atomic < Base
15
+ def initialize
16
+ @stash = {}
17
+ end
18
+
19
+ def update type, objects, options = {}
20
+ ActiveSupport::Deprecation.warn("`urgent: true` option is deprecated and is not effective inside `:atomic` strategy, use `Chewy.strategy(:urgent)` strategy instead") if options.key?(:urgent)
21
+
22
+ @stash[type] ||= []
23
+ @stash[type] |= type.adapter.identify(objects)
24
+ end
25
+
26
+ def leave
27
+ @stash.all? { |type, ids| type.import(ids) }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ module Chewy
2
+ class Strategy
3
+ # This strategy raises exception on every index update
4
+ # asking to choose some other strategy.
5
+ #
6
+ # Chewy.strategy(:base) do
7
+ # User.all.map(&:save) # Raises UndefinedUpdateStrategy exception
8
+ # end
9
+ #
10
+ class Base
11
+ def name
12
+ self.class.name.demodulize.underscore.to_sym
13
+ end
14
+ # This method called when some model tries to update index
15
+ #
16
+ def update type, objects, options = {}
17
+ raise UndefinedUpdateStrategy.new(type)
18
+ end
19
+
20
+ # This method called when strategy pops from the
21
+ # strategies stack
22
+ #
23
+ def leave
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,15 @@
1
+ module Chewy
2
+ class Strategy
3
+ # This strategy basically does nothing.
4
+ #
5
+ # Chewy.strategy(:bypass) do
6
+ # User.all.map(&:save) # Does nothing here
7
+ # # Does not update index all over the block.
8
+ # end
9
+ #
10
+ class Bypass < Base
11
+ def update type, objects, options = {}
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module Chewy
2
+ class Strategy
3
+ # This strategy updates index on demand. Not the best
4
+ # strategy in case of optimization. If you need to update
5
+ # indexes with bulk API calls - use :atomic instead.
6
+ #
7
+ # Chewy.strategy(:urgent) do
8
+ # User.all.map(&:save) # Updates index on every `save` call
9
+ # end
10
+ #
11
+ class Urgent < Base
12
+ def update type, objects, options = {}
13
+ type.import(Array.wrap(objects))
14
+ end
15
+ end
16
+ end
17
+ end
@@ -17,26 +17,40 @@ module Chewy
17
17
  include Actions
18
18
  include Import
19
19
 
20
- singleton_class.delegate :client, to: :index
20
+ singleton_class.delegate :index_name, :client, to: :index
21
21
 
22
+ # Chewy index current type blongs to. Defined inside `Chewy.create_type`
23
+ #
22
24
  def self.index
23
25
  raise NotImplementedError
24
26
  end
25
27
 
28
+ # Current type adapter. Defined inside `Chewy.create_type`, derived from
29
+ # `Chewy::Index.define_type` arguments.
30
+ #
26
31
  def self.adapter
27
32
  raise NotImplementedError
28
33
  end
29
34
 
35
+ # Returns type name string
36
+ #
30
37
  def self.type_name
31
38
  adapter.type_name
32
39
  end
33
40
 
34
- def self.search_index
35
- index
41
+ # Returns list of public class methods defined in current type
42
+ #
43
+ def self.scopes
44
+ public_methods - Chewy::Type.public_methods
36
45
  end
37
46
 
38
- def self.search_type
39
- type_name
47
+ def self.method_missing(method, *args, &block)
48
+ if index.scopes.include?(method)
49
+ define_singleton_method method do |*args, &block|
50
+ all.scoping { index.public_send(method, *args, &block) }
51
+ end
52
+ send(method, *args, &block)
53
+ end
40
54
  end
41
55
 
42
56
  def self.const_missing(name)
@@ -1,142 +1,53 @@
1
- require 'chewy/type/adapter/base'
1
+ require 'chewy/type/adapter/orm'
2
2
 
3
3
  module Chewy
4
4
  class Type
5
5
  module Adapter
6
- class ActiveRecord < Base
7
- def initialize *args
8
- @options = args.extract_options!
9
- subject = args.first
10
- if subject.is_a?(::ActiveRecord::Relation)
11
- @model = subject.klass
12
- @scope = subject
13
- else
14
- @model = subject
15
- end
16
- end
17
-
18
- def name
19
- @name ||= (options[:name].present? ? options[:name].to_s.camelize : model.model_name.to_s).demodulize
20
- end
21
-
22
- # Import method fo ActiveRecord takes import data and import options
23
- #
24
- # Import data types:
25
- #
26
- # * Nothing passed - imports all the model data
27
- # * ActiveRecord scope
28
- # * Objects collection
29
- # * Ids collection
30
- #
31
- # Import options:
32
- #
33
- # <tt>:batch_size</tt> - import batch size, 1000 objects by default
34
- #
35
- # Method handles destroyed objects as well. In case of objects AcriveRecord::Relation
36
- # or array passed, objects, responding with true to `destroyed?` method will be deleted
37
- # from index. In case of ids array passed - documents with missing records ids will be
38
- # deleted from index:
39
- #
40
- # users = User.all
41
- # users.each { |user| user.destroy if user.incative? }
42
- # UsersIndex::User.import users # inactive users will be deleted from index
43
- # # or
44
- # UsersIndex::User.import users.map(&:id) # deleted user ids will be deleted from index
45
- #
46
- # Also there is custom API method `delete_from_index?`. It it returns `true`
47
- # object will be deleted from index. Note that if this method is defined and
48
- # return `false` Chewy will still check `destroyed?` method. This is useful
49
- # for paranoid objects sdeleting implementation.
50
- #
51
- # class User
52
- # alias_method :delete_from_index?, :deleted_at?
53
- # end
54
- #
55
- # users = User.all
56
- # users.each { |user| user.deleted_at = Time.now }
57
- # UsersIndex::User.import users # paranoid deleted users will be deleted from index
58
- # # or
59
- # UsersIndex::User.import users.map(&:id) # user ids will be deleted from index
60
- #
61
- def import *args, &block
62
- import_options = args.extract_options!
63
- import_options[:batch_size] ||= BATCH_SIZE
64
- collection = args.none? ? model_all :
65
- (args.one? && args.first.is_a?(::ActiveRecord::Relation) ? args.first : args.flatten.compact)
6
+ class ActiveRecord < Orm
7
+ private
66
8
 
67
- if collection.is_a?(::ActiveRecord::Relation)
68
- result = true
69
- merged_scope(collection).find_in_batches(import_options.slice(:batch_size)) do |group|
70
- result &= block.call grouped_objects(group)
71
- end
72
- result
73
- else
74
- if collection.all? { |object| object.respond_to?(:id) }
75
- collection.each_slice(import_options[:batch_size]).map do |group|
76
- block.call grouped_objects(group)
77
- end.all?
78
- else
79
- import_ids(collection, import_options, &block)
80
- end
9
+ def cleanup_default_scope!
10
+ if Chewy.logger && (@default_scope.arel.orders.present? ||
11
+ @default_scope.arel.limit.present? || @default_scope.arel.offset.present?)
12
+ Chewy.logger.warn('Default type scope order, limit and offest are ignored and will be nullified')
81
13
  end
82
- end
83
14
 
84
- def load *args
85
- load_options = args.extract_options!
86
- objects = args.flatten
87
-
88
- additional_scope = load_options[load_options[:_type].type_name.to_sym].try(:[], :scope) || load_options[:scope]
89
-
90
- scope = scoped_model(objects.map(&:id))
91
- loaded_objects = if additional_scope.is_a?(Proc)
92
- scope.instance_exec(&additional_scope)
93
- elsif additional_scope.is_a?(::ActiveRecord::Relation)
94
- scope.merge(additional_scope)
95
- else
96
- scope
97
- end.index_by { |object| object.id.to_s }
98
-
99
- objects.map { |object| loaded_objects[object.id.to_s] }
15
+ @default_scope = @default_scope.reorder(nil).limit(nil).offset(nil)
100
16
  end
101
17
 
102
- private
18
+ def import_scope(scope, batch_size)
19
+ scope = scope.reorder(target.primary_key.to_sym).limit(batch_size)
103
20
 
104
- attr_reader :model, :scope, :options
21
+ ids = pluck_ids(scope)
22
+ result = true
105
23
 
106
- def import_ids(ids, import_options = {}, &block)
107
- ids.uniq!
108
-
109
- indexed = true
110
- merged_scope(scoped_model(ids)).find_in_batches(import_options.slice(:batch_size)) do |objects|
111
- ids -= objects.map(&:id)
112
- indexed &= block.call(grouped_objects(objects))
24
+ while ids.any?
25
+ result &= yield grouped_objects(default_scope_where_ids_in(ids))
26
+ break if ids.size < batch_size
27
+ ids = pluck_ids(scope.where(scope.table[target.primary_key].gt(ids.last)))
113
28
  end
114
29
 
115
- deleted = ids.each_slice(import_options[:batch_size]).map do |group|
116
- block.call(delete: group)
117
- end.all?
30
+ result
31
+ end
118
32
 
119
- indexed && deleted
33
+ def pluck_ids(scope)
34
+ scope.pluck(target.primary_key.to_sym)
120
35
  end
121
36
 
122
- def grouped_objects(objects)
123
- objects.group_by do |object|
124
- delete = object.delete_from_index? if object.respond_to?(:delete_from_index?)
125
- delete ||= object.destroyed?
126
- delete ? :delete : :index
127
- end
37
+ def scope_where_ids_in(scope, ids)
38
+ scope.where(target.primary_key => ids)
128
39
  end
129
40
 
130
- def merged_scope(target)
131
- scope ? scope.clone.merge(target) : target
41
+ def all_scope
42
+ target.where(nil)
132
43
  end
133
44
 
134
- def scoped_model(ids)
135
- model.where(Hash[model.primary_key.to_sym || :id, ids])
45
+ def relation_class
46
+ ::ActiveRecord::Relation
136
47
  end
137
48
 
138
- def model_all
139
- ::ActiveRecord::VERSION::MAJOR < 4 ? model.scoped : model.all
49
+ def object_class
50
+ ::ActiveRecord::Base
140
51
  end
141
52
  end
142
53
  end