elasticsearch-model-extensions 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -0
- data/Gemfile +2 -1
- data/README.md +5 -0
- data/gemfiles/rails41.gemfile +11 -0
- data/gemfiles/rails41.gemfile.lock +109 -0
- data/lib/elasticsearch/model/extensions/association_path_finding/association_path_finder.rb +19 -0
- data/lib/elasticsearch/model/extensions/association_path_finding/mapping_node.rb +68 -0
- data/lib/elasticsearch/model/extensions/association_path_finding/shortest_path.rb +108 -0
- data/lib/elasticsearch/model/extensions/batch_updating.rb +9 -52
- data/lib/elasticsearch/model/extensions/batch_updating/batch_updater.rb +68 -0
- data/lib/elasticsearch/model/extensions/configuration.rb +33 -5
- data/lib/elasticsearch/model/extensions/dependency_tracking.rb +17 -21
- data/lib/elasticsearch/model/extensions/dependency_tracking/dependency_tracker.rb +52 -0
- data/lib/elasticsearch/model/extensions/mapping_reflection.rb +7 -80
- data/lib/elasticsearch/model/extensions/mapping_reflection/mapping_reflector.rb +107 -0
- data/lib/elasticsearch/model/extensions/outer_document_updating.rb +0 -21
- data/lib/elasticsearch/model/extensions/partial_updating.rb +16 -116
- data/lib/elasticsearch/model/extensions/partial_updating/partial_updater.rb +149 -0
- data/lib/elasticsearch/model/extensions/proxy.rb +0 -0
- data/lib/elasticsearch/model/extensions/update_callback.rb +3 -2
- data/lib/elasticsearch/model/extensions/version.rb +1 -1
- data/spec/batch_updating/batch_updater_spec.rb +50 -0
- data/spec/batch_updating_spec.rb +134 -0
- data/spec/integration_spec.rb +350 -4
- data/spec/partial_updating_spec.rb +36 -6
- data/spec/setup/articles_with_comments.rb +2 -4
- data/spec/setup/articles_with_comments_and_delayed_jobs.rb +2 -4
- data/spec/setup/authors_and_books_with_tags.rb +183 -0
- data/spec/setup/items_and_categories.rb +0 -0
- data/spec/setup/sqlite.rb +1 -1
- data/spec/setup/undefine.rb +6 -6
- data/spec/spec_helper.rb +6 -0
- metadata +20 -4
- data/lib/elasticsearch/model/extensions/mapping_node.rb +0 -65
- data/lib/elasticsearch/model/extensions/shortest_path.rb +0 -106
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fdacb9b2813cbdf61813171273ec16c3626a40cd
|
4
|
+
data.tar.gz: a47ded66adef66a3ac2cba15e802ee5443ed50f1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2373b26f5f54d52b89ad22270cf7e076cafd31eea7f69042f0cf695fda8bfb3676d89be631c6f649819dbe17698ee8d7053e55dc1f263b143b8a9cd72333bec
|
7
|
+
data.tar.gz: b354472253b3a6b61501aaecf67afc31cd69e99a6b3ce4b6e1651153c7ab09b2e4780444c98da1a944351a4171fe02b21e3a8967d95a07a39674ddb95381ed60
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in elasticsearch-model-extensions.gemspec
|
4
|
-
gemspec
|
4
|
+
gemspec path: File.dirname(__FILE__)
|
5
5
|
|
6
6
|
group :test do
|
7
7
|
gem 'rspec', '~> 3.1.0'
|
8
8
|
gem 'database_cleaner'
|
9
9
|
gem 'coveralls', require: false
|
10
|
+
gem 'parallel'
|
10
11
|
end
|
11
12
|
|
12
13
|
group :test, :development do
|
data/README.md
CHANGED
@@ -34,6 +34,11 @@ With Elasticsearch installed, you can run examples configured with an ES instanc
|
|
34
34
|
(A bunch of log output here)
|
35
35
|
irb(main):001:0> Article.search('Comment1')
|
36
36
|
|
37
|
+
To run the same example with Rails 4.1.x:
|
38
|
+
|
39
|
+
$ BUNDLE_GEMFILE=gemfiles/rails41.gemfile bundle exec irb -I spec -r example/articles_with_comments
|
40
|
+
|
41
|
+
|
37
42
|
## Contributing
|
38
43
|
|
39
44
|
1. Fork it ( https://github.com/[my-github-username]/elasticsearch-model-extensions/fork )
|
@@ -0,0 +1,11 @@
|
|
1
|
+
eval_gemfile File.expand_path(File.join(File.dirname(__FILE__), '../Gemfile'))
|
2
|
+
|
3
|
+
ignored_gems = %w(activerecord)
|
4
|
+
|
5
|
+
dependencies.delete_if do |g|
|
6
|
+
ignored_gems.include?(g.name)
|
7
|
+
end
|
8
|
+
|
9
|
+
group :test, :development do
|
10
|
+
gem 'activerecord', '~> 4.1.4'
|
11
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
PATH
|
2
|
+
remote: /Users/mumoshu/Repositories/elasticsearch-model-extensions
|
3
|
+
specs:
|
4
|
+
elasticsearch-model-extensions (0.2.2)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
activemodel (4.1.8)
|
10
|
+
activesupport (= 4.1.8)
|
11
|
+
builder (~> 3.1)
|
12
|
+
activerecord (4.1.8)
|
13
|
+
activemodel (= 4.1.8)
|
14
|
+
activesupport (= 4.1.8)
|
15
|
+
arel (~> 5.0.0)
|
16
|
+
activesupport (4.1.8)
|
17
|
+
i18n (~> 0.6, >= 0.6.9)
|
18
|
+
json (~> 1.7, >= 1.7.7)
|
19
|
+
minitest (~> 5.1)
|
20
|
+
thread_safe (~> 0.1)
|
21
|
+
tzinfo (~> 1.1)
|
22
|
+
ansi (1.4.3)
|
23
|
+
arel (5.0.1.20140414130214)
|
24
|
+
builder (3.2.2)
|
25
|
+
coveralls (0.7.2)
|
26
|
+
multi_json (~> 1.3)
|
27
|
+
rest-client (= 1.6.7)
|
28
|
+
simplecov (>= 0.7)
|
29
|
+
term-ansicolor (= 1.2.2)
|
30
|
+
thor (= 0.18.1)
|
31
|
+
database_cleaner (1.3.0)
|
32
|
+
delayed_job (4.0.6)
|
33
|
+
activesupport (>= 3.0, < 5.0)
|
34
|
+
delayed_job_active_record (4.0.3)
|
35
|
+
activerecord (>= 3.0, < 5.0)
|
36
|
+
delayed_job (>= 3.0, < 4.1)
|
37
|
+
diff-lcs (1.2.5)
|
38
|
+
docile (1.1.5)
|
39
|
+
elasticsearch (1.0.6)
|
40
|
+
elasticsearch-api (= 1.0.6)
|
41
|
+
elasticsearch-transport (= 1.0.6)
|
42
|
+
elasticsearch-api (1.0.6)
|
43
|
+
multi_json
|
44
|
+
elasticsearch-extensions (0.0.17)
|
45
|
+
ansi
|
46
|
+
ruby-prof
|
47
|
+
elasticsearch-model (0.1.6)
|
48
|
+
activesupport (> 3)
|
49
|
+
elasticsearch (> 0.4)
|
50
|
+
hashie
|
51
|
+
elasticsearch-transport (1.0.6)
|
52
|
+
faraday
|
53
|
+
multi_json
|
54
|
+
faraday (0.9.0)
|
55
|
+
multipart-post (>= 1.2, < 3)
|
56
|
+
hashie (3.3.2)
|
57
|
+
i18n (0.7.0)
|
58
|
+
json (1.8.1)
|
59
|
+
mime-types (2.4.3)
|
60
|
+
minitest (5.5.0)
|
61
|
+
multi_json (1.10.1)
|
62
|
+
multipart-post (2.0.0)
|
63
|
+
parallel (1.3.3)
|
64
|
+
rake (10.4.2)
|
65
|
+
rest-client (1.6.7)
|
66
|
+
mime-types (>= 1.16)
|
67
|
+
rspec (3.1.0)
|
68
|
+
rspec-core (~> 3.1.0)
|
69
|
+
rspec-expectations (~> 3.1.0)
|
70
|
+
rspec-mocks (~> 3.1.0)
|
71
|
+
rspec-core (3.1.7)
|
72
|
+
rspec-support (~> 3.1.0)
|
73
|
+
rspec-expectations (3.1.2)
|
74
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
75
|
+
rspec-support (~> 3.1.0)
|
76
|
+
rspec-mocks (3.1.3)
|
77
|
+
rspec-support (~> 3.1.0)
|
78
|
+
rspec-support (3.1.2)
|
79
|
+
ruby-prof (0.15.2)
|
80
|
+
simplecov (0.9.1)
|
81
|
+
docile (~> 1.1.0)
|
82
|
+
multi_json (~> 1.0)
|
83
|
+
simplecov-html (~> 0.8.0)
|
84
|
+
simplecov-html (0.8.0)
|
85
|
+
sqlite3 (1.3.10)
|
86
|
+
term-ansicolor (1.2.2)
|
87
|
+
tins (~> 0.8)
|
88
|
+
thor (0.18.1)
|
89
|
+
thread_safe (0.3.4)
|
90
|
+
tins (0.13.2)
|
91
|
+
tzinfo (1.2.2)
|
92
|
+
thread_safe (~> 0.1)
|
93
|
+
|
94
|
+
PLATFORMS
|
95
|
+
ruby
|
96
|
+
|
97
|
+
DEPENDENCIES
|
98
|
+
activerecord (~> 4.1.4)
|
99
|
+
bundler (~> 1.7)
|
100
|
+
coveralls
|
101
|
+
database_cleaner
|
102
|
+
delayed_job_active_record (~> 4.0.1)
|
103
|
+
elasticsearch-extensions
|
104
|
+
elasticsearch-model
|
105
|
+
elasticsearch-model-extensions!
|
106
|
+
parallel
|
107
|
+
rake (~> 10.0)
|
108
|
+
rspec (~> 3.1.0)
|
109
|
+
sqlite3
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative 'mapping_node'
|
2
|
+
|
3
|
+
module Elasticsearch
|
4
|
+
module Model
|
5
|
+
module Extensions
|
6
|
+
module AssociationPathFinding
|
7
|
+
class AssociationPathFinder
|
8
|
+
def find_path(from:, to:)
|
9
|
+
MappingNode.
|
10
|
+
from_class(from).
|
11
|
+
breadth_first_search { |e| e.destination.relates_to_class?(to) }.
|
12
|
+
first
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require_relative 'shortest_path'
|
2
|
+
|
3
|
+
module Elasticsearch
|
4
|
+
module Model
|
5
|
+
module Extensions
|
6
|
+
module AssociationPathFinding
|
7
|
+
class MappingNode < ShortestPath::Node
|
8
|
+
def self.from_class(klass)
|
9
|
+
name = klass.document_type.intern
|
10
|
+
|
11
|
+
new(klass: klass, name: name, mapping: klass.mapping.to_hash[name])
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(klass:, name:, mapping:, through_class:nil)
|
15
|
+
@klass = klass
|
16
|
+
@name = name
|
17
|
+
@mapping = mapping
|
18
|
+
@through_class = through_class
|
19
|
+
end
|
20
|
+
|
21
|
+
def name
|
22
|
+
@name
|
23
|
+
end
|
24
|
+
|
25
|
+
def relates_to_class?(klass)
|
26
|
+
@klass == klass || @through_class == klass
|
27
|
+
end
|
28
|
+
|
29
|
+
def klass
|
30
|
+
@klass
|
31
|
+
end
|
32
|
+
|
33
|
+
def through_class
|
34
|
+
@through_class
|
35
|
+
end
|
36
|
+
|
37
|
+
def each(&block)
|
38
|
+
associations = @klass.reflect_on_all_associations
|
39
|
+
|
40
|
+
props = @mapping[:properties]
|
41
|
+
fields = props.keys
|
42
|
+
|
43
|
+
edges = fields.map { |f|
|
44
|
+
a = associations.find { |a| a.name == f }
|
45
|
+
|
46
|
+
if a && a.options[:polymorphic] != true
|
47
|
+
through_class = if a.options[:through]
|
48
|
+
a.options[:through].to_s.classify.constantize
|
49
|
+
end
|
50
|
+
|
51
|
+
dest = MappingNode.new(klass: a.class_name.constantize, name: f.to_s.pluralize.intern, mapping: props[f], through_class: through_class)
|
52
|
+
|
53
|
+
edge_class.new(name: f, destination: dest)
|
54
|
+
end
|
55
|
+
}.reject(&:nil?)
|
56
|
+
|
57
|
+
if block.nil?
|
58
|
+
edges
|
59
|
+
else
|
60
|
+
edges.each(&block)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Model
|
3
|
+
module Extensions
|
4
|
+
module AssociationPathFinding
|
5
|
+
class ShortestPath
|
6
|
+
MAX_DEPTH = 5
|
7
|
+
|
8
|
+
class Node
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
def each(&block)
|
12
|
+
raise "A required method #{self.class}#each is not implemented."
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
raise "A required method #{self.class}#name is not implemented."
|
17
|
+
end
|
18
|
+
|
19
|
+
def each_with_name(&block)
|
20
|
+
iterator = each.lazy.map do |edge|
|
21
|
+
[edge, edge.name]
|
22
|
+
end
|
23
|
+
|
24
|
+
if block.nil?
|
25
|
+
iterator
|
26
|
+
else
|
27
|
+
iterator.each(&block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def hash
|
32
|
+
name.hash
|
33
|
+
end
|
34
|
+
|
35
|
+
def eql?(other)
|
36
|
+
self.class == other.class && (name.eql? other.name)
|
37
|
+
end
|
38
|
+
|
39
|
+
def edge_class
|
40
|
+
ShortestPath::Edge
|
41
|
+
end
|
42
|
+
|
43
|
+
def breadth_first_search(&block)
|
44
|
+
ShortestPath.breadth_first_search self, &block
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class Edge
|
49
|
+
def initialize(name:, destination:)
|
50
|
+
@name = name
|
51
|
+
@destination = destination
|
52
|
+
end
|
53
|
+
|
54
|
+
def name
|
55
|
+
@name
|
56
|
+
end
|
57
|
+
|
58
|
+
def destination
|
59
|
+
@destination
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module ClassMethods
|
64
|
+
def breadth_first_search(node, &block)
|
65
|
+
original_paths = node.each.map { |e| [e] }
|
66
|
+
paths = original_paths
|
67
|
+
|
68
|
+
depth = 0
|
69
|
+
|
70
|
+
loop {
|
71
|
+
a = paths.select { |p|
|
72
|
+
if block.call(p.last)
|
73
|
+
p
|
74
|
+
end
|
75
|
+
}
|
76
|
+
|
77
|
+
return a if a.size != 0
|
78
|
+
raise RuntimeError, 'Maximum depth exceeded while calculating the shortest path' if depth >= Elasticsearch::Model::Extensions::ShortestPath::MAX_DEPTH
|
79
|
+
|
80
|
+
paths = paths.flat_map { |p|
|
81
|
+
p.last.destination.each.map { |e|
|
82
|
+
p + [e]
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
depth += 1
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
def depth_first_search(node, &block)
|
91
|
+
node.each.select do |edge|
|
92
|
+
if block.call(edge)
|
93
|
+
[[edge]]
|
94
|
+
else
|
95
|
+
depth_first_search(edge.destination, &block).map do |path|
|
96
|
+
[edge] + path
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
extend ClassMethods
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require_relative 'mapping_reflection'
|
2
|
+
require_relative 'batch_updating/batch_updater'
|
2
3
|
|
3
4
|
module Elasticsearch
|
4
5
|
module Model
|
@@ -27,29 +28,15 @@ module Elasticsearch
|
|
27
28
|
end
|
28
29
|
|
29
30
|
module ClassMethods
|
30
|
-
def
|
31
|
-
|
32
|
-
max ||= maximum(:id)
|
33
|
-
chunk_num.times.inject([]) do |r,i|
|
34
|
-
chunk_size = ((max-min+1)/chunk_num.to_f).ceil
|
35
|
-
first = chunk_size * i
|
36
|
-
|
37
|
-
last = if i == chunk_num - 1
|
38
|
-
max
|
39
|
-
else
|
40
|
-
chunk_size * (i + 1) - 1
|
41
|
-
end
|
42
|
-
|
43
|
-
r << (first..last)
|
44
|
-
end
|
31
|
+
def __batch_updater__
|
32
|
+
@__batch_updater__ ||= ::Elasticsearch::Model::Extensions::BatchUpdating::BatchUpdater.new(self)
|
45
33
|
end
|
46
34
|
|
47
35
|
def update_index_in_parallel(parallelism:, index: nil, type: nil, min: nil, max: nil, batch_size:DEFAULT_BATCH_SIZE)
|
48
36
|
klass = self
|
49
37
|
|
50
|
-
Parallel.each(
|
51
|
-
|
52
|
-
@elasticsearch_reconnected ||= klass.__elasticsearch__.client = Elasticsearch::Client.new(host: klass.elasticsearch_hosts)
|
38
|
+
Parallel.each(__batch_updater__.split_ids_into(parallelism, min: min, max: max), in_processes: parallelism) do |id_range|
|
39
|
+
__batch_updater__.reconnect!
|
53
40
|
klass.for_indexing.update_index_for_ids_in_range id_range, index: index, type: type, batch_size: batch_size
|
54
41
|
end
|
55
42
|
|
@@ -74,36 +61,6 @@ module Elasticsearch
|
|
74
61
|
|
75
62
|
records_in_scope.update_index_in_batches(batch_size: batch_size, index: index, type: type)
|
76
63
|
end
|
77
|
-
|
78
|
-
# @param [Array] records
|
79
|
-
def update_index_in_batch(records, index: nil, type: nil, client: nil)
|
80
|
-
klass = self
|
81
|
-
|
82
|
-
client ||= klass.__elasticsearch__.client
|
83
|
-
index ||= klass.index_name
|
84
|
-
type ||= klass.document_type
|
85
|
-
|
86
|
-
if records.size > 1
|
87
|
-
response = client.bulk \
|
88
|
-
index: index,
|
89
|
-
type: type,
|
90
|
-
body: records.map { |r| { index: { _id: r.id, data: r.as_indexed_json } } }
|
91
|
-
|
92
|
-
one_or_more_errors_occurred = response["errors"]
|
93
|
-
|
94
|
-
if one_or_more_errors_occurred
|
95
|
-
if defined? ::Rails
|
96
|
-
::Rails.logger.warn "One or more error(s) occurred while updating the index #{records} for the type #{type}\n#{JSON.pretty_generate(response)}"
|
97
|
-
else
|
98
|
-
warn "One or more error(s) occurred while updating the index #{records} for the type #{type}\n#{JSON.pretty_generate(response)}"
|
99
|
-
end
|
100
|
-
end
|
101
|
-
else
|
102
|
-
records.each do |r|
|
103
|
-
client.index index: index, type: type, id: r.id, body: r.as_indexed_json
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
64
|
end
|
108
65
|
|
109
66
|
module Association
|
@@ -123,16 +80,16 @@ module Elasticsearch
|
|
123
80
|
|
124
81
|
conditions = record_id.gteq(from).and(record_id.lteq(to))
|
125
82
|
|
126
|
-
update_index_in_batches(batch_size: batch_size, index: index, type: type
|
83
|
+
where(conditions).update_index_in_batches(batch_size: batch_size, index: index, type: type)
|
127
84
|
end
|
128
85
|
|
129
86
|
def update_index_for_ids_in_range(range, index: nil, type: nil, batch_size: DEFAULT_BATCH_SIZE)
|
130
87
|
update_index_for_ids_from(range.first, to: range.last, type: type, index: index, batch_size: batch_size)
|
131
88
|
end
|
132
89
|
|
133
|
-
def update_index_in_batches(batch_size: DEFAULT_BATCH_SIZE,
|
134
|
-
find_in_batches(batch_size: batch_size
|
135
|
-
|
90
|
+
def update_index_in_batches(batch_size: DEFAULT_BATCH_SIZE, index: nil, type: nil)
|
91
|
+
find_in_batches(batch_size: batch_size) do |records|
|
92
|
+
__batch_updater__.update_index_in_batch(records, index: index, type: type)
|
136
93
|
end
|
137
94
|
end
|
138
95
|
end
|