chewy 0.8.1 → 0.8.2
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.
- checksums.yaml +4 -4
- data/.travis.yml +10 -8
- data/Appraisals +8 -0
- data/CHANGELOG.md +33 -6
- data/Gemfile +6 -4
- data/README.md +240 -111
- data/gemfiles/rails.4.2.activerecord.gemfile +1 -0
- data/gemfiles/rails.4.2.activerecord.kaminari.gemfile +1 -0
- data/gemfiles/rails.4.2.activerecord.will_paginate.gemfile +1 -0
- data/gemfiles/sequel.4.23.gemfile +13 -0
- data/lib/chewy.rb +24 -11
- data/lib/chewy/config.rb +4 -4
- data/lib/chewy/index.rb +1 -1
- data/lib/chewy/index/settings.rb +1 -1
- data/lib/chewy/query.rb +43 -4
- data/lib/chewy/railtie.rb +2 -2
- data/lib/chewy/rake_helper.rb +62 -0
- data/lib/chewy/rspec/update_index.rb +3 -3
- data/lib/chewy/strategy.rb +6 -0
- data/lib/chewy/strategy/active_job.rb +28 -0
- data/lib/chewy/strategy/atomic.rb +1 -1
- data/lib/chewy/strategy/sidekiq.rb +3 -1
- data/lib/chewy/type.rb +2 -1
- data/lib/chewy/type/adapter/active_record.rb +9 -2
- data/lib/chewy/type/adapter/base.rb +6 -0
- data/lib/chewy/type/adapter/mongoid.rb +7 -1
- data/lib/chewy/type/adapter/orm.rb +1 -1
- data/lib/chewy/type/adapter/sequel.rb +125 -0
- data/lib/chewy/type/mapping.rb +26 -1
- data/lib/chewy/type/observe.rb +40 -17
- data/lib/chewy/version.rb +1 -1
- data/lib/sequel/plugins/chewy_observe.rb +71 -0
- data/lib/tasks/chewy.rake +19 -61
- data/spec/chewy/config_spec.rb +9 -5
- data/spec/chewy/fields/base_spec.rb +21 -7
- data/spec/chewy/index/actions_spec.rb +5 -5
- data/spec/chewy/query_spec.rb +69 -0
- data/spec/chewy/runtime_spec.rb +1 -1
- data/spec/chewy/strategy/active_job_spec.rb +49 -0
- data/spec/chewy/strategy_spec.rb +2 -2
- data/spec/chewy/type/adapter/sequel_spec.rb +46 -0
- data/spec/chewy/type/import_spec.rb +4 -2
- data/spec/chewy/type/mapping_spec.rb +19 -0
- data/spec/chewy/type/observe_spec.rb +43 -14
- data/spec/chewy_spec.rb +2 -3
- data/spec/spec_helper.rb +6 -3
- data/spec/support/active_record.rb +5 -8
- data/spec/support/mongoid.rb +5 -8
- data/spec/support/sequel.rb +69 -0
- metadata +14 -3
data/lib/chewy.rb
CHANGED
@@ -1,9 +1,17 @@
|
|
1
|
-
require 'active_support'
|
2
|
-
require 'active_support/log_subscriber'
|
3
|
-
require 'active_support/deprecation'
|
4
|
-
require 'active_support/core_ext'
|
5
1
|
require 'active_support/concern'
|
2
|
+
require 'active_support/deprecation'
|
6
3
|
require 'active_support/json'
|
4
|
+
require 'active_support/log_subscriber'
|
5
|
+
|
6
|
+
require 'active_support/core_ext/array/access'
|
7
|
+
require 'active_support/core_ext/array/wrap'
|
8
|
+
require 'active_support/core_ext/enumerable'
|
9
|
+
require 'active_support/core_ext/hash/reverse_merge'
|
10
|
+
require 'active_support/core_ext/numeric/time'
|
11
|
+
require 'active_support/core_ext/object/blank'
|
12
|
+
require 'active_support/core_ext/object/inclusion'
|
13
|
+
require 'active_support/core_ext/string/inflections'
|
14
|
+
|
7
15
|
require 'i18n/core_ext/hash'
|
8
16
|
require 'chewy/backports/deep_dup' unless Object.respond_to?(:deep_dup)
|
9
17
|
require 'singleton'
|
@@ -13,6 +21,7 @@ require 'elasticsearch'
|
|
13
21
|
require 'chewy/version'
|
14
22
|
require 'chewy/errors'
|
15
23
|
require 'chewy/config'
|
24
|
+
require 'chewy/rake_helper'
|
16
25
|
require 'chewy/repository'
|
17
26
|
require 'chewy/runtime'
|
18
27
|
require 'chewy/log_subscriber'
|
@@ -58,7 +67,17 @@ ActiveSupport.on_load(:mongoid) do
|
|
58
67
|
end
|
59
68
|
|
60
69
|
module Chewy
|
70
|
+
|
71
|
+
@adapters = [
|
72
|
+
Chewy::Type::Adapter::ActiveRecord,
|
73
|
+
Chewy::Type::Adapter::Mongoid,
|
74
|
+
Chewy::Type::Adapter::Sequel,
|
75
|
+
Chewy::Type::Adapter::Object
|
76
|
+
]
|
77
|
+
|
61
78
|
class << self
|
79
|
+
attr_accessor :adapters
|
80
|
+
|
62
81
|
# Derives type from string `index#type` representation:
|
63
82
|
#
|
64
83
|
# Chewy.derive_type('users#user') # => UsersIndex::User
|
@@ -90,13 +109,7 @@ module Chewy
|
|
90
109
|
def create_type index, target, options = {}, &block
|
91
110
|
type = Class.new(Chewy::Type)
|
92
111
|
|
93
|
-
adapter =
|
94
|
-
Chewy::Type::Adapter::ActiveRecord.new(target, options)
|
95
|
-
elsif defined?(::Mongoid::Document) && ((target.is_a?(Class) && target.ancestors.include?(::Mongoid::Document)) || target.is_a?(::Mongoid::Criteria))
|
96
|
-
Chewy::Type::Adapter::Mongoid.new(target, options)
|
97
|
-
else
|
98
|
-
Chewy::Type::Adapter::Object.new(target, options)
|
99
|
-
end
|
112
|
+
adapter = adapters.find { |adapter| adapter.accepts?(target) }.new(target, options)
|
100
113
|
|
101
114
|
index.const_set(adapter.name, type)
|
102
115
|
type.send(:define_singleton_method, :index) { index }
|
data/lib/chewy/config.rb
CHANGED
@@ -19,8 +19,8 @@ module Chewy
|
|
19
19
|
#
|
20
20
|
:post_filter_mode,
|
21
21
|
|
22
|
-
# The first
|
23
|
-
# If you
|
22
|
+
# The first strategy in stack. `:base` by default.
|
23
|
+
# If you need to return to the previous chewy behavior -
|
24
24
|
# just set it to `:bypass`
|
25
25
|
#
|
26
26
|
:root_strategy,
|
@@ -106,8 +106,8 @@ module Chewy
|
|
106
106
|
#
|
107
107
|
def configuration
|
108
108
|
yaml_settings.merge(settings.deep_symbolize_keys).tap do |configuration|
|
109
|
-
configuration.merge(logger: transport_logger) if transport_logger
|
110
|
-
configuration.merge(tracer: transport_tracer) if transport_tracer
|
109
|
+
configuration.merge!(logger: transport_logger) if transport_logger
|
110
|
+
configuration.merge!(tracer: transport_tracer) if transport_tracer
|
111
111
|
end
|
112
112
|
end
|
113
113
|
|
data/lib/chewy/index.rb
CHANGED
@@ -92,7 +92,7 @@ module Chewy
|
|
92
92
|
#
|
93
93
|
# UsersIndex.types # => [UsersIndex::Admin, UsersIndex::Manager, UsersIndex::User]
|
94
94
|
#
|
95
|
-
# If arguments are passed it treats like a part of chainable query
|
95
|
+
# If arguments are passed it treats like a part of chainable query DSL and
|
96
96
|
# adds types array for index to select.
|
97
97
|
#
|
98
98
|
# UsersIndex.filters { name =~ 'ro' }.types(:admin, :manager)
|
data/lib/chewy/index/settings.rb
CHANGED
@@ -2,7 +2,7 @@ module Chewy
|
|
2
2
|
class Index
|
3
3
|
|
4
4
|
# Stores ElasticSearch index settings and resolves `analysis`
|
5
|
-
# hash. At first, you need to store
|
5
|
+
# hash. At first, you need to store some analyzers or other
|
6
6
|
# analysis options to the corresponding repository:
|
7
7
|
#
|
8
8
|
# Chewy.analyzer :title_analyzer, type: 'custom', filter: %w(lowercase icu_folding title_nysiis)
|
data/lib/chewy/query.rb
CHANGED
@@ -386,7 +386,7 @@ module Chewy
|
|
386
386
|
# added to the search request and combinded according to
|
387
387
|
# <tt>boost_mode</tt> and <tt>score_mode</tt>
|
388
388
|
#
|
389
|
-
# This probably only makes sense if you
|
389
|
+
# This probably only makes sense if you specify a filter
|
390
390
|
# for the boost factor as well
|
391
391
|
#
|
392
392
|
# UsersIndex.boost_factor(23, filter: { term: { foo: :bar} })
|
@@ -408,7 +408,7 @@ module Chewy
|
|
408
408
|
# added to the search request and combinded according to
|
409
409
|
# <tt>boost_mode</tt> and <tt>score_mode</tt>
|
410
410
|
#
|
411
|
-
# This probably only makes sense if you
|
411
|
+
# This probably only makes sense if you specify a filter
|
412
412
|
# for the random score as well.
|
413
413
|
#
|
414
414
|
# If you do not pass in a seed value, Time.now will be used
|
@@ -511,7 +511,11 @@ module Chewy
|
|
511
511
|
# }}
|
512
512
|
#
|
513
513
|
def aggregations params = nil
|
514
|
+
@_named_aggs ||= _build_named_aggs
|
515
|
+
@_fully_qualified_named_aggs ||= _build_fqn_aggs
|
514
516
|
if params
|
517
|
+
params = { params => @_named_aggs[params] } if params.is_a?(Symbol)
|
518
|
+
params = { params => _get_fully_qualified_named_agg(params) } if params.is_a?(String) && params =~ /\A\S+#\S+\.\S+\z/
|
515
519
|
chain { criteria.update_aggregations params }
|
516
520
|
else
|
517
521
|
_response['aggregations'] || {}
|
@@ -519,6 +523,41 @@ module Chewy
|
|
519
523
|
end
|
520
524
|
alias :aggs :aggregations
|
521
525
|
|
526
|
+
# In this simplest of implementations each named aggregation must be uniquely named
|
527
|
+
def _build_named_aggs
|
528
|
+
named_aggs = {}
|
529
|
+
@_indexes.each do |index|
|
530
|
+
index.types.each do |type|
|
531
|
+
type._agg_defs.each do |agg_name, prc|
|
532
|
+
named_aggs[agg_name] = prc.call
|
533
|
+
end
|
534
|
+
end
|
535
|
+
end
|
536
|
+
named_aggs
|
537
|
+
end
|
538
|
+
|
539
|
+
def _build_fqn_aggs
|
540
|
+
named_aggs = {}
|
541
|
+
@_indexes.each do |index|
|
542
|
+
named_aggs[index.to_s.downcase] ||= {}
|
543
|
+
index.types.each do |type|
|
544
|
+
named_aggs[index.to_s.downcase][type.to_s.downcase] ||= {}
|
545
|
+
type._agg_defs.each do |agg_name, prc|
|
546
|
+
named_aggs[index.to_s.downcase][type.to_s.downcase][agg_name.to_s.downcase] = prc.call
|
547
|
+
end
|
548
|
+
end
|
549
|
+
end
|
550
|
+
named_aggs
|
551
|
+
end
|
552
|
+
|
553
|
+
def _get_fully_qualified_named_agg(str)
|
554
|
+
parts = str.scan(/\A(\S+)#(\S+)\.(\S+)\z/).first
|
555
|
+
idx = "#{parts[0]}index"
|
556
|
+
type = "#{idx}::#{parts[1]}"
|
557
|
+
agg_name = parts[2]
|
558
|
+
@_fully_qualified_named_aggs[idx][type][agg_name]
|
559
|
+
end
|
560
|
+
|
522
561
|
# Sets elasticsearch <tt>suggest</tt> search request param
|
523
562
|
#
|
524
563
|
# UsersIndex.suggest(name: {text: 'Joh', term: {field: 'name'}})
|
@@ -876,7 +915,7 @@ module Chewy
|
|
876
915
|
end
|
877
916
|
end
|
878
917
|
|
879
|
-
#
|
918
|
+
# Find all records matching a query.
|
880
919
|
#
|
881
920
|
# UsersIndex.find(42)
|
882
921
|
# UsersIndex.filter{ age <= 42 }.find(42)
|
@@ -982,7 +1021,7 @@ module Chewy
|
|
982
1021
|
|
983
1022
|
def _derive_index index_name
|
984
1023
|
(@derive_index ||= {})[index_name] ||= _indexes_hash[index_name] ||
|
985
|
-
_indexes_hash[_indexes_hash.keys.sort_by(&:length).reverse.detect { |name| index_name.
|
1024
|
+
_indexes_hash[_indexes_hash.keys.sort_by(&:length).reverse.detect { |name| index_name.start_with?(name) }]
|
986
1025
|
end
|
987
1026
|
|
988
1027
|
def _indexes_hash
|
data/lib/chewy/railtie.rb
CHANGED
@@ -10,7 +10,7 @@ module Chewy
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def call(env)
|
13
|
-
if env['PATH_INFO']
|
13
|
+
if env['PATH_INFO'].start_with?('/assets/')
|
14
14
|
@app.call(env)
|
15
15
|
else
|
16
16
|
Chewy.strategy(Chewy.request_strategy) { @app.call(env) }
|
@@ -55,7 +55,7 @@ module Chewy
|
|
55
55
|
end
|
56
56
|
|
57
57
|
initializer 'chewy.request_strategy' do |app|
|
58
|
-
app.config.middleware.insert_after(Rack::
|
58
|
+
app.config.middleware.insert_after(Rails::Rack::Logger, RequestStrategy)
|
59
59
|
end
|
60
60
|
|
61
61
|
initializer 'chewy.add_app_chewy_path' do |app|
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Chewy
|
2
|
+
module RakeHelper
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def subscribe_task_stats!
|
6
|
+
ActiveSupport::Notifications.subscribe('import_objects.chewy') do |name, start, finish, id, payload|
|
7
|
+
duration = (finish - start).round(2)
|
8
|
+
puts " Imported #{payload[:type]} for #{duration}s, documents total: #{payload[:import].try(:[], :index).to_i}"
|
9
|
+
payload[:errors].each do |action, errors|
|
10
|
+
puts " #{action.to_s.humanize} errors:"
|
11
|
+
errors.each do |error, documents|
|
12
|
+
puts " `#{error}`"
|
13
|
+
puts " on #{documents.count} documents: #{documents}"
|
14
|
+
end
|
15
|
+
end if payload[:errors]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def eager_load_chewy!
|
20
|
+
dirs = Chewy::Railtie.all_engines.map { |engine| engine.paths['app/chewy'] }.compact.map(&:existent).flatten.uniq
|
21
|
+
|
22
|
+
dirs.each do |dir|
|
23
|
+
Dir.glob(File.join(dir, '**/*.rb')).each do |file|
|
24
|
+
require_dependency file
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def normalize_index index
|
30
|
+
"#{index.to_s.gsub(/index\z/i, '').camelize}Index".constantize
|
31
|
+
end
|
32
|
+
|
33
|
+
# Performs zero downtime reindexing of all documents in the specified index.
|
34
|
+
def reset_index index
|
35
|
+
index = normalize_index(index)
|
36
|
+
puts "Resetting #{index}"
|
37
|
+
index.reset! (Time.now.to_f * 1000).round
|
38
|
+
end
|
39
|
+
|
40
|
+
# Performs zero downtime reindexing of all documents across all indices.
|
41
|
+
def reset_all
|
42
|
+
eager_load_chewy!
|
43
|
+
Chewy::Index.descendants.each { |index| reset_index index }
|
44
|
+
end
|
45
|
+
|
46
|
+
def update_index index
|
47
|
+
index = normalize_index(index)
|
48
|
+
puts "Updating #{index}"
|
49
|
+
if index.exists?
|
50
|
+
index.import
|
51
|
+
else
|
52
|
+
puts "Index `#{index.index_name}` does not exists. Use rake chewy:reset[#{index.index_name}] to create and update it."
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def update_all
|
57
|
+
eager_load_chewy!
|
58
|
+
Chewy::Index.descendants.each { |index| update_index index }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
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
|
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
|
|
@@ -43,7 +43,7 @@ RSpec::Matchers.define :update_index do |type_name, options = {}|
|
|
43
43
|
# .to update_index(UsersIndex::User).and_reindex(user, times: 2) }
|
44
44
|
#
|
45
45
|
# Specify reindexed attributes. Note that arrays are
|
46
|
-
# compared position-
|
46
|
+
# compared position-independently.
|
47
47
|
#
|
48
48
|
# specify { expect { user.update_attributes!(name: 'Duke') }
|
49
49
|
# .to update_index(UsersIndex.user).and_reindex(user, with: {name: 'Duke'}) }
|
@@ -241,4 +241,4 @@ RSpec::Matchers.define :update_index do |type_name, options = {}|
|
|
241
241
|
end
|
242
242
|
difference.none?
|
243
243
|
end
|
244
|
-
end
|
244
|
+
end
|
data/lib/chewy/strategy.rb
CHANGED
@@ -15,6 +15,12 @@ begin
|
|
15
15
|
rescue LoadError
|
16
16
|
end
|
17
17
|
|
18
|
+
begin
|
19
|
+
require 'active_job'
|
20
|
+
require 'chewy/strategy/active_job'
|
21
|
+
rescue LoadError
|
22
|
+
end
|
23
|
+
|
18
24
|
module Chewy
|
19
25
|
# This class represents strategies stack with `:base`
|
20
26
|
# Strategy on top of it. This causes raising exceptions
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Chewy
|
2
|
+
class Strategy
|
3
|
+
# The strategy works the same way as atomic, but performs
|
4
|
+
# async index update driven by active_job
|
5
|
+
#
|
6
|
+
# Chewy.strategy(:active_job) do
|
7
|
+
# User.all.map(&:save) # Does nothing here
|
8
|
+
# Post.all.map(&:save) # And here
|
9
|
+
# # It imports all the changed users and posts right here
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
class ActiveJob < Atomic
|
13
|
+
class Worker < ::ActiveJob::Base
|
14
|
+
queue_as :chewy
|
15
|
+
|
16
|
+
def perform(type, ids)
|
17
|
+
type.constantize.import!(ids)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def leave
|
22
|
+
@stash.each do |type, ids|
|
23
|
+
Chewy::Strategy::ActiveJob::Worker.perform_later(type.name, ids) unless ids.empty?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -19,7 +19,9 @@ module Chewy
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def leave
|
22
|
-
@stash.
|
22
|
+
@stash.each do |type, ids|
|
23
|
+
Chewy::Strategy::Sidekiq::Worker.perform_async(type.name, ids) unless ids.empty?
|
24
|
+
end
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
data/lib/chewy/type.rb
CHANGED
@@ -8,6 +8,7 @@ require 'chewy/type/import'
|
|
8
8
|
require 'chewy/type/adapter/object'
|
9
9
|
require 'chewy/type/adapter/active_record'
|
10
10
|
require 'chewy/type/adapter/mongoid'
|
11
|
+
require 'chewy/type/adapter/sequel'
|
11
12
|
|
12
13
|
module Chewy
|
13
14
|
class Type
|
@@ -21,7 +22,7 @@ module Chewy
|
|
21
22
|
|
22
23
|
singleton_class.delegate :index_name, :client, to: :index
|
23
24
|
|
24
|
-
# Chewy index current type
|
25
|
+
# Chewy index current type belongs to. Defined inside `Chewy.create_type`
|
25
26
|
#
|
26
27
|
def self.index
|
27
28
|
raise NotImplementedError
|
@@ -4,12 +4,19 @@ module Chewy
|
|
4
4
|
class Type
|
5
5
|
module Adapter
|
6
6
|
class ActiveRecord < Orm
|
7
|
+
|
8
|
+
def self.accepts?(target)
|
9
|
+
defined?(::ActiveRecord::Base) && (
|
10
|
+
target.is_a?(Class) && target < ::ActiveRecord::Base ||
|
11
|
+
target.is_a?(::ActiveRecord::Relation))
|
12
|
+
end
|
13
|
+
|
7
14
|
private
|
8
15
|
|
9
16
|
def cleanup_default_scope!
|
10
17
|
if Chewy.logger && (@default_scope.arel.orders.present? ||
|
11
18
|
@default_scope.arel.limit.present? || @default_scope.arel.offset.present?)
|
12
|
-
Chewy.logger.warn('Default type scope order, limit and
|
19
|
+
Chewy.logger.warn('Default type scope order, limit and offset are ignored and will be nullified')
|
13
20
|
end
|
14
21
|
|
15
22
|
@default_scope = @default_scope.reorder(nil).limit(nil).offset(nil)
|
@@ -31,7 +38,7 @@ module Chewy
|
|
31
38
|
end
|
32
39
|
|
33
40
|
def pluck_ids(scope)
|
34
|
-
scope.pluck(target.primary_key.to_sym)
|
41
|
+
scope.except(:includes).uniq.pluck(target.primary_key.to_sym)
|
35
42
|
end
|
36
43
|
|
37
44
|
def scope_where_ids_in(scope, ids)
|