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
@@ -7,6 +7,12 @@ module Chewy
|
|
7
7
|
|
8
8
|
attr_reader :target, :options
|
9
9
|
|
10
|
+
# Returns `true` if this adapter is applicable for the given target.
|
11
|
+
#
|
12
|
+
def self.accepts? target
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
10
16
|
# Camelcased name, used as type class constant name.
|
11
17
|
# For returned value 'Product' will be generated class name `ProductsIndex::Product`
|
12
18
|
#
|
@@ -5,6 +5,12 @@ module Chewy
|
|
5
5
|
module Adapter
|
6
6
|
class Mongoid < Orm
|
7
7
|
|
8
|
+
def self.accepts?(target)
|
9
|
+
defined?(::Mongoid::Document) && (
|
10
|
+
target.is_a?(Class) && target.ancestors.include?(::Mongoid::Document) ||
|
11
|
+
target.is_a?(::Mongoid::Criteria))
|
12
|
+
end
|
13
|
+
|
8
14
|
def identify collection
|
9
15
|
super(collection).map { |id| id.is_a?(BSON::ObjectId) ? id.to_s : id }
|
10
16
|
end
|
@@ -13,7 +19,7 @@ module Chewy
|
|
13
19
|
|
14
20
|
def cleanup_default_scope!
|
15
21
|
if Chewy.logger && @default_scope.options.values_at(:sort, :limit, :skip).compact.present?
|
16
|
-
Chewy.logger.warn('Default type scope order, limit and
|
22
|
+
Chewy.logger.warn('Default type scope order, limit and offset are ignored and will be nullified')
|
17
23
|
end
|
18
24
|
|
19
25
|
@default_scope = @default_scope.reorder(nil)
|
@@ -53,7 +53,7 @@ module Chewy
|
|
53
53
|
# deleted from index:
|
54
54
|
#
|
55
55
|
# users = User.all
|
56
|
-
# users.each { |user| user.destroy if user.
|
56
|
+
# users.each { |user| user.destroy if user.inactive? }
|
57
57
|
# UsersIndex::User.import users # inactive users will be deleted from index
|
58
58
|
# # or
|
59
59
|
# UsersIndex::User.import users.map(&:id) # deleted user ids will be deleted from index
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'chewy/type/adapter/base'
|
2
|
+
|
3
|
+
module Chewy
|
4
|
+
class Type
|
5
|
+
module Adapter
|
6
|
+
class Sequel < Base
|
7
|
+
|
8
|
+
attr_reader :default_dataset
|
9
|
+
|
10
|
+
def self.accepts?(target)
|
11
|
+
defined?(::Sequel::Model) && (
|
12
|
+
target.is_a?(Class) && target < ::Sequel::Model || target.is_a?(::Sequel::Dataset))
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(*args)
|
16
|
+
@options = args.extract_options!
|
17
|
+
|
18
|
+
if dataset? args.first
|
19
|
+
dataset = args.first
|
20
|
+
@target = dataset.model
|
21
|
+
@default_dataset = dataset.unordered.unlimited
|
22
|
+
else
|
23
|
+
model = args.first
|
24
|
+
@target = model
|
25
|
+
@default_dataset = model.where(nil)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def name
|
30
|
+
@name ||= (options[:name].presence || target.name).camelize.demodulize
|
31
|
+
end
|
32
|
+
|
33
|
+
def identify(obj)
|
34
|
+
if dataset? obj
|
35
|
+
obj.select_map(target_pk)
|
36
|
+
else
|
37
|
+
Array.wrap(obj).map do |item|
|
38
|
+
model?(item) ? item.pk : item
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def import(*args, &block)
|
44
|
+
import_options = args.extract_options!
|
45
|
+
batch_size = import_options[:batch_size] || BATCH_SIZE
|
46
|
+
|
47
|
+
if args.empty?
|
48
|
+
import_dataset(default_dataset, batch_size, &block)
|
49
|
+
elsif args.one? && dataset?(args.first)
|
50
|
+
import_dataset(args.first, batch_size, &block)
|
51
|
+
else
|
52
|
+
import_models(args.flatten.compact, batch_size, &block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def load(*args)
|
57
|
+
load_options = args.extract_options!
|
58
|
+
index_ids = args.flatten.map(&:id) # args contains index instances
|
59
|
+
|
60
|
+
type_name = load_options[:_type].type_name.to_sym
|
61
|
+
additional_scope = load_options[type_name].try(:[], :scope) || load_options[:scope]
|
62
|
+
|
63
|
+
dataset = select_by_ids(target, index_ids)
|
64
|
+
|
65
|
+
if additional_scope.is_a?(Proc)
|
66
|
+
index_ids.map!(&:to_s)
|
67
|
+
dataset.instance_exec(&additional_scope).to_a.select do |model|
|
68
|
+
index_ids.include? model.pk.to_s
|
69
|
+
end
|
70
|
+
else
|
71
|
+
dataset.to_a
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def import_dataset(dataset, batch_size)
|
78
|
+
dataset = dataset.limit(batch_size)
|
79
|
+
|
80
|
+
dataset.db.transaction(isolation: :committed) do
|
81
|
+
0.step(Float::INFINITY, batch_size).lazy
|
82
|
+
.map { |offset| dataset.offset(offset).to_a }
|
83
|
+
.take_while(&:any?)
|
84
|
+
.map { |items| yield grouped_objects(items) }
|
85
|
+
.reduce(:&)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def import_models(objects, batch_size)
|
90
|
+
objects_by_id = objects.index_by do |item|
|
91
|
+
model?(item) ? item.pk : item
|
92
|
+
end
|
93
|
+
|
94
|
+
indexed = objects_by_id.keys.each_slice(batch_size).map do |ids|
|
95
|
+
models = select_by_ids(default_dataset, ids).to_a
|
96
|
+
models.each { |model| objects_by_id.delete(model.pk) }
|
97
|
+
models.empty? || yield(grouped_objects(models))
|
98
|
+
end
|
99
|
+
|
100
|
+
deleted = objects_by_id.keys.each_slice(batch_size).map do |ids|
|
101
|
+
yield delete: objects_by_id.values_at(*ids)
|
102
|
+
end
|
103
|
+
|
104
|
+
indexed.all? && deleted.all?
|
105
|
+
end
|
106
|
+
|
107
|
+
def select_by_ids(dataset, ids)
|
108
|
+
dataset.where(target_pk => Array.wrap(ids))
|
109
|
+
end
|
110
|
+
|
111
|
+
def target_pk
|
112
|
+
target.primary_key
|
113
|
+
end
|
114
|
+
|
115
|
+
def dataset?(obj)
|
116
|
+
obj.is_a? ::Sequel::Dataset
|
117
|
+
end
|
118
|
+
|
119
|
+
def model?(obj)
|
120
|
+
obj.is_a? ::Sequel::Model
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
data/lib/chewy/type/mapping.rb
CHANGED
@@ -6,6 +6,8 @@ module Chewy
|
|
6
6
|
included do
|
7
7
|
class_attribute :root_object, instance_reader: false, instance_writer: false
|
8
8
|
class_attribute :_templates
|
9
|
+
class_attribute :_agg_defs
|
10
|
+
self._agg_defs = {}
|
9
11
|
end
|
10
12
|
|
11
13
|
module ClassMethods
|
@@ -118,7 +120,30 @@ module Chewy
|
|
118
120
|
end
|
119
121
|
end
|
120
122
|
|
121
|
-
# Defines
|
123
|
+
# Defines an aggregation that can be bound to a query or filter
|
124
|
+
#
|
125
|
+
# Suppose that a user has posts and each post has ratings
|
126
|
+
# avg_post_rating is the mean of all ratings
|
127
|
+
#
|
128
|
+
# class UsersIndex < Chewy::Index
|
129
|
+
# define_type User do
|
130
|
+
# field :posts do
|
131
|
+
# field :rating
|
132
|
+
# end
|
133
|
+
#
|
134
|
+
# agg :avg_rating do
|
135
|
+
# { avg: { field: 'posts.rating' } }
|
136
|
+
# end
|
137
|
+
# end
|
138
|
+
# end
|
139
|
+
def agg *args, &block
|
140
|
+
options = args.extract_options!
|
141
|
+
build_root unless root_object
|
142
|
+
self._agg_defs = _agg_defs.merge(args.first => block)
|
143
|
+
end
|
144
|
+
alias_method :aggregation, :agg
|
145
|
+
|
146
|
+
# Defines dynamic template in mapping root objects
|
122
147
|
#
|
123
148
|
# class CarsIndex < Chewy::Index
|
124
149
|
# define_type Car do
|
data/lib/chewy/type/observe.rb
CHANGED
@@ -3,41 +3,64 @@ module Chewy
|
|
3
3
|
module Observe
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
self
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
6
|
+
module Helpers
|
7
|
+
def update_proc(type_name, *args, &block)
|
8
|
+
options = args.extract_options!
|
9
|
+
method = args.first
|
10
|
+
|
11
|
+
Proc.new do
|
12
|
+
backreference = if method && method.to_s == 'self'
|
13
|
+
self
|
14
|
+
elsif method
|
15
|
+
send(method)
|
16
|
+
else
|
17
|
+
instance_eval(&block)
|
18
|
+
end
|
19
|
+
|
20
|
+
reference = if type_name.is_a?(Proc)
|
21
|
+
type_name.arity == 0 ?
|
22
|
+
instance_exec(&type_name) :
|
23
|
+
type_name.call(self)
|
24
|
+
else
|
25
|
+
type_name
|
26
|
+
end
|
27
|
+
|
28
|
+
Chewy.derive_type(reference).update_index(backreference, options)
|
17
29
|
end
|
30
|
+
end
|
18
31
|
|
19
|
-
|
32
|
+
def extract_callback_options!(args)
|
33
|
+
options = args.extract_options!
|
34
|
+
options.each_key.with_object({}) { |key, hash|
|
35
|
+
hash[key] = options.delete(key) if [:if, :unless].include?(key)
|
36
|
+
}.tap {
|
37
|
+
args.push(options) unless options.empty?
|
38
|
+
}
|
20
39
|
end
|
21
40
|
end
|
22
41
|
|
42
|
+
extend Helpers
|
43
|
+
|
23
44
|
module MongoidMethods
|
24
45
|
def update_index(type_name, *args, &block)
|
46
|
+
callback_options = Observe.extract_callback_options!(args)
|
25
47
|
update_proc = Observe.update_proc(type_name, *args, &block)
|
26
48
|
|
27
|
-
after_save &update_proc
|
28
|
-
after_destroy &update_proc
|
49
|
+
after_save(callback_options, &update_proc)
|
50
|
+
after_destroy(callback_options, &update_proc)
|
29
51
|
end
|
30
52
|
end
|
31
53
|
|
32
54
|
module ActiveRecordMethods
|
33
55
|
def update_index(type_name, *args, &block)
|
56
|
+
callback_options = Observe.extract_callback_options!(args)
|
34
57
|
update_proc = Observe.update_proc(type_name, *args, &block)
|
35
58
|
|
36
59
|
if Chewy.use_after_commit_callbacks
|
37
|
-
after_commit &update_proc
|
60
|
+
after_commit(callback_options, &update_proc)
|
38
61
|
else
|
39
|
-
after_save &update_proc
|
40
|
-
after_destroy &update_proc
|
62
|
+
after_save(callback_options, &update_proc)
|
63
|
+
after_destroy(callback_options, &update_proc)
|
41
64
|
end
|
42
65
|
end
|
43
66
|
end
|
data/lib/chewy/version.rb
CHANGED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'active_support/callbacks'
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# This Sequel plugin adds support for chewy's model-observing hook for
|
6
|
+
# updating indexes after model save or destroy.
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
#
|
10
|
+
# # Make all model subclasses support the `update_index` hook (called
|
11
|
+
# # before loading subclasses).
|
12
|
+
# Sequel::Model.plugin :chewy_observe
|
13
|
+
#
|
14
|
+
# # Make the Album class support the `update_index` hooks.
|
15
|
+
# Album.plugin :chewy_observe
|
16
|
+
#
|
17
|
+
# # Declare one or more `update_index` observers in model.
|
18
|
+
# class Album < Sequel::Model
|
19
|
+
# update_index('albums#album') { self }
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
module ChewyObserve
|
23
|
+
extend ::Chewy::Type::Observe::Helpers
|
24
|
+
|
25
|
+
def self.apply(model)
|
26
|
+
model.instance_eval do
|
27
|
+
include ActiveSupport::Callbacks
|
28
|
+
define_callbacks :commit, :save, :destroy
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Class level methods for Sequel::Model
|
33
|
+
#
|
34
|
+
module ClassMethods
|
35
|
+
def update_index(type_name, *args, &block)
|
36
|
+
callback_options = ChewyObserve.extract_callback_options!(args)
|
37
|
+
update_proc = ChewyObserve.update_proc(type_name, *args, &block)
|
38
|
+
|
39
|
+
if Chewy.use_after_commit_callbacks
|
40
|
+
set_callback(:commit, callback_options, &update_proc)
|
41
|
+
else
|
42
|
+
set_callback(:save, callback_options, &update_proc)
|
43
|
+
set_callback(:destroy, callback_options, &update_proc)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Instance level methods for Sequel::Model
|
49
|
+
#
|
50
|
+
module InstanceMethods
|
51
|
+
def after_commit
|
52
|
+
run_callbacks(:commit) do
|
53
|
+
super
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def after_save
|
58
|
+
run_callbacks(:save) do
|
59
|
+
super
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def after_destroy
|
64
|
+
run_callbacks(:destroy) do
|
65
|
+
super
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/tasks/chewy.rake
CHANGED
@@ -1,83 +1,41 @@
|
|
1
|
-
|
2
|
-
ActiveSupport::Notifications.subscribe('import_objects.chewy') do |name, start, finish, id, payload|
|
3
|
-
duration = (finish - start).round(2)
|
4
|
-
puts " Imported #{payload[:type]} for #{duration}s, documents total: #{payload[:import].try(:[], :index).to_i}"
|
5
|
-
payload[:errors].each do |action, errors|
|
6
|
-
puts " #{action.to_s.humanize} errors:"
|
7
|
-
errors.each do |error, documents|
|
8
|
-
puts " `#{error}`"
|
9
|
-
puts " on #{documents.count} documents: #{documents}"
|
10
|
-
end
|
11
|
-
end if payload[:errors]
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
def eager_load_chewy!
|
16
|
-
dirs = Chewy::Railtie.all_engines.map { |engine| engine.paths['app/chewy'].existent }.flatten.uniq
|
17
|
-
|
18
|
-
dirs.each do |dir|
|
19
|
-
Dir.glob(File.join(dir, '**/*.rb')).each do |file|
|
20
|
-
require_dependency file
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def normalize_index index
|
26
|
-
"#{index.to_s.gsub(/index$/i, '').camelize}Index".constantize
|
27
|
-
end
|
28
|
-
|
29
|
-
def reset_index index
|
30
|
-
index = normalize_index(index)
|
31
|
-
puts "Resetting #{index}"
|
32
|
-
index.reset! (Time.now.to_f * 1000).round
|
33
|
-
end
|
34
|
-
|
35
|
-
def reset_all
|
36
|
-
eager_load_chewy!
|
37
|
-
Chewy::Index.descendants.each { |index| reset_index index }
|
38
|
-
end
|
39
|
-
|
40
|
-
def update_index index
|
41
|
-
index = normalize_index(index)
|
42
|
-
puts "Updating #{index}"
|
43
|
-
if index.exists?
|
44
|
-
index.import
|
45
|
-
else
|
46
|
-
puts "Index `#{index.index_name}` does not exists. Use rake chewy:reset[#{index.index_name}] to create and update it."
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def update_all
|
51
|
-
eager_load_chewy!
|
52
|
-
Chewy::Index.descendants.each { |index| update_index index }
|
53
|
-
end
|
1
|
+
require 'chewy/rake_helper'
|
54
2
|
|
55
3
|
namespace :chewy do
|
56
4
|
desc 'Destroy, recreate and import data to specified index'
|
57
5
|
task :reset, [:index] => :environment do |task, args|
|
58
|
-
subscribe_task_stats!
|
59
|
-
|
6
|
+
Chewy::RakeHelper.subscribe_task_stats!
|
7
|
+
|
8
|
+
if args[:index].present?
|
9
|
+
Chewy::RakeHelper.reset_index(args[:index])
|
10
|
+
else
|
11
|
+
Chewy::RakeHelper.reset_all
|
12
|
+
end
|
60
13
|
end
|
61
14
|
|
62
15
|
namespace :reset do
|
63
16
|
desc 'Destroy, recreate and import data for all found indexes'
|
64
17
|
task all: :environment do
|
65
|
-
subscribe_task_stats!
|
66
|
-
reset_all
|
18
|
+
Chewy::RakeHelper.subscribe_task_stats!
|
19
|
+
Chewy::RakeHelper.reset_all
|
67
20
|
end
|
68
21
|
end
|
69
22
|
|
70
23
|
desc 'Updates data specified index'
|
71
24
|
task :update, [:index] => :environment do |task, args|
|
72
|
-
subscribe_task_stats!
|
73
|
-
|
25
|
+
Chewy::RakeHelper.subscribe_task_stats!
|
26
|
+
|
27
|
+
if args[:index].present?
|
28
|
+
Chewy::RakeHelper.update_index(args[:index])
|
29
|
+
else
|
30
|
+
Chewy::RakeHelper.update_all
|
31
|
+
end
|
74
32
|
end
|
75
33
|
|
76
34
|
namespace :update do
|
77
35
|
desc 'Updates data for all found indexes'
|
78
36
|
task all: :environment do
|
79
|
-
subscribe_task_stats!
|
80
|
-
update_all
|
37
|
+
Chewy::RakeHelper.subscribe_task_stats!
|
38
|
+
Chewy::RakeHelper.update_all
|
81
39
|
end
|
82
40
|
end
|
83
41
|
end
|