elasticsearch-model-extensions 0.2.2 → 0.3.0

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.
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