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