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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -0
  3. data/Gemfile +2 -1
  4. data/README.md +5 -0
  5. data/gemfiles/rails41.gemfile +11 -0
  6. data/gemfiles/rails41.gemfile.lock +109 -0
  7. data/lib/elasticsearch/model/extensions/association_path_finding/association_path_finder.rb +19 -0
  8. data/lib/elasticsearch/model/extensions/association_path_finding/mapping_node.rb +68 -0
  9. data/lib/elasticsearch/model/extensions/association_path_finding/shortest_path.rb +108 -0
  10. data/lib/elasticsearch/model/extensions/batch_updating.rb +9 -52
  11. data/lib/elasticsearch/model/extensions/batch_updating/batch_updater.rb +68 -0
  12. data/lib/elasticsearch/model/extensions/configuration.rb +33 -5
  13. data/lib/elasticsearch/model/extensions/dependency_tracking.rb +17 -21
  14. data/lib/elasticsearch/model/extensions/dependency_tracking/dependency_tracker.rb +52 -0
  15. data/lib/elasticsearch/model/extensions/mapping_reflection.rb +7 -80
  16. data/lib/elasticsearch/model/extensions/mapping_reflection/mapping_reflector.rb +107 -0
  17. data/lib/elasticsearch/model/extensions/outer_document_updating.rb +0 -21
  18. data/lib/elasticsearch/model/extensions/partial_updating.rb +16 -116
  19. data/lib/elasticsearch/model/extensions/partial_updating/partial_updater.rb +149 -0
  20. data/lib/elasticsearch/model/extensions/proxy.rb +0 -0
  21. data/lib/elasticsearch/model/extensions/update_callback.rb +3 -2
  22. data/lib/elasticsearch/model/extensions/version.rb +1 -1
  23. data/spec/batch_updating/batch_updater_spec.rb +50 -0
  24. data/spec/batch_updating_spec.rb +134 -0
  25. data/spec/integration_spec.rb +350 -4
  26. data/spec/partial_updating_spec.rb +36 -6
  27. data/spec/setup/articles_with_comments.rb +2 -4
  28. data/spec/setup/articles_with_comments_and_delayed_jobs.rb +2 -4
  29. data/spec/setup/authors_and_books_with_tags.rb +183 -0
  30. data/spec/setup/items_and_categories.rb +0 -0
  31. data/spec/setup/sqlite.rb +1 -1
  32. data/spec/setup/undefine.rb +6 -6
  33. data/spec/spec_helper.rb +6 -0
  34. metadata +20 -4
  35. data/lib/elasticsearch/model/extensions/mapping_node.rb +0 -65
  36. 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: cc88b24aa703c53b9c35f7f0dfc72c291299d861
4
- data.tar.gz: 8ee00b4d40f76173ec408ea6068d4a697a3fb9b0
3
+ metadata.gz: fdacb9b2813cbdf61813171273ec16c3626a40cd
4
+ data.tar.gz: a47ded66adef66a3ac2cba15e802ee5443ed50f1
5
5
  SHA512:
6
- metadata.gz: 181f6efceba36b6a0b032f06a9e612e51d8a371f3c88cc780b641262b7a59149ebbf2dd1528c6bf4f864823c7e6bd6773a4a4f9fada2eccc6e5d7e4248c364d5
7
- data.tar.gz: 139e8c22de2c2be86dc885b1a54552f940aac3725ec88bc7d6a137efd50cd253c890f37268a2da7e78ddcd79d75b4f8972855cffa674fb73a17361bbdb074aca
6
+ metadata.gz: d2373b26f5f54d52b89ad22270cf7e076cafd31eea7f69042f0cf695fda8bfb3676d89be631c6f649819dbe17698ee8d7053e55dc1f263b143b8a9cd72333bec
7
+ data.tar.gz: b354472253b3a6b61501aaecf67afc31cd69e99a6b3ce4b6e1651153c7ab09b2e4780444c98da1a944351a4171fe02b21e3a8967d95a07a39674ddb95381ed60
data/.travis.yml CHANGED
@@ -4,6 +4,9 @@ rvm:
4
4
  - 2.1.2
5
5
  - 2.1.3
6
6
  - ruby-head
7
+ gemfile:
8
+ - Gemfile
9
+ - gemfiles/rails41.gemfile
7
10
  matrix:
8
11
  allow_failures:
9
12
  - rvm: ruby-head
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 split_ids_into(chunk_num, min:nil, max:nil)
31
- min ||= minimum(:id)
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(klass.split_ids_into(parallelism, min: min, max: max), in_processes: parallelism) do |id_range|
51
- @rdb_reconnected ||= klass.connection.reconnect! || true
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, conditions: conditions)
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, conditions:nil, index: nil, type: nil)
134
- find_in_batches(batch_size: batch_size, conditions: conditions) do |records|
135
- klass.update_index_in_batch(records, index: index, type: type)
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