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
@@ -44,12 +44,10 @@ class ::Article < ActiveRecord::Base
44
44
  include Elasticsearch::Model::Extensions::BatchUpdating
45
45
  include Elasticsearch::Model::Extensions::PartialUpdating
46
46
 
47
- DEPENDENT_CUSTOM_ATTRIBUTES = {
48
- %w| comments | => %w| num_comments |
49
- }
50
-
51
47
  include Elasticsearch::Model::Extensions::DependencyTracking
52
48
 
49
+ tracks_attributes_dependencies %w| comments | => %w| num_comments |
50
+
53
51
  settings index: {number_of_shards: 1, number_of_replicas: 0} do
54
52
  mapping do
55
53
  indexes :title, type: 'string', analyzer: 'snowball'
@@ -0,0 +1,183 @@
1
+ load 'setup/undefine.rb'
2
+
3
+ require 'elasticsearch/model/extensions/all'
4
+
5
+ ActiveRecord::Schema.define(:version => 1) do
6
+ create_table :authors do |t|
7
+ t.string :nickname
8
+ t.datetime :created_at, :default => 'NOW()'
9
+ end
10
+
11
+ create_table :author_profiles do |t|
12
+ t.integer :author_id
13
+ t.string :description
14
+ t.datetime :created_at, :default => 'NOW()'
15
+ end
16
+
17
+ create_table :books do |t|
18
+ t.integer :author_id
19
+ t.string :title
20
+ t.datetime :created_at, :default => 'NOW()'
21
+ end
22
+
23
+ create_table :tags do |t|
24
+ t.string :taggable_type
25
+ t.integer :taggable_id
26
+ t.string :body
27
+ t.datetime :created_at, :default => 'NOW()'
28
+ t.datetime :deleted_at, :null => true
29
+ end
30
+ end
31
+
32
+ class Author < ActiveRecord::Base
33
+ has_many :books
34
+ has_one :profile, class_name: 'AuthorProfile'
35
+ has_many :tags, as: :taggable
36
+
37
+ accepts_nested_attributes_for :books
38
+ accepts_nested_attributes_for :profile
39
+ accepts_nested_attributes_for :tags
40
+
41
+ include Elasticsearch::Model
42
+ include Elasticsearch::Model::Callbacks
43
+ include Elasticsearch::Model::Extensions::IndexOperations
44
+ include Elasticsearch::Model::Extensions::BatchUpdating
45
+ include Elasticsearch::Model::Extensions::PartialUpdating
46
+
47
+ include Elasticsearch::Model::Extensions::DependencyTracking
48
+
49
+ tracks_attributes_dependencies (
50
+ {
51
+ %w| books | => %w| num_books |,
52
+ %w| tags | => %w| num_tags |
53
+ }
54
+ )
55
+
56
+ settings index: {number_of_shards: 1, number_of_replicas: 0} do
57
+ mapping do
58
+ indexes :nickname, type: 'string', analyzer: 'snowball', include_in_all: true
59
+ indexes :created_at, type: 'date'
60
+ indexes :books, type: 'object' do
61
+ indexes :title, type: 'string', include_in_all: true
62
+ end
63
+ indexes :profile, type: 'object' do
64
+ indexes :description, type: 'string', include_in_all: true
65
+ end
66
+ indexes :tags, type: 'object' do
67
+ indexes :body, type: 'string', include_in_all: true
68
+ end
69
+ indexes :num_books, type: 'long'
70
+ indexes :num_tags, type: 'long'
71
+ end
72
+ end
73
+
74
+ def num_books
75
+ books.count
76
+ end
77
+
78
+ def num_tags
79
+ tags.count
80
+ end
81
+
82
+ # Required by AuthorProfile's `OuterDocumentUpdating`
83
+ include Elasticsearch::Model::Extensions::MappingReflection
84
+ end
85
+
86
+ class ::AuthorProfile < ActiveRecord::Base
87
+ belongs_to :author
88
+ end
89
+
90
+ class ::Book < ActiveRecord::Base
91
+ belongs_to :author
92
+ has_many :tags, as: :taggable
93
+
94
+ accepts_nested_attributes_for :tags
95
+
96
+ include Elasticsearch::Model
97
+ include Elasticsearch::Model::Callbacks
98
+ include Elasticsearch::Model::Extensions::IndexOperations
99
+ include Elasticsearch::Model::Extensions::BatchUpdating
100
+ include Elasticsearch::Model::Extensions::PartialUpdating
101
+ include Elasticsearch::Model::Extensions::OuterDocumentUpdating
102
+
103
+ include Elasticsearch::Model::Extensions::DependencyTracking
104
+
105
+ tracks_attributes_dependencies %w| tags | => %w| num_tags |
106
+
107
+ settings index: {number_of_shards: 1, number_of_replicas: 0} do
108
+ mapping do
109
+ indexes :title, type: 'string', analyzer: 'snowball', include_in_all: true
110
+ indexes :created_at, type: 'date'
111
+ indexes :tags, type: 'object' do
112
+ indexes :body, type: 'string', include_in_all: true
113
+ end
114
+ indexes :num_tags, type: 'long'
115
+ end
116
+ end
117
+
118
+ def num_tags
119
+ tags.count
120
+ end
121
+
122
+ # Required by Review's `OuterDocumentUpdating`
123
+ include Elasticsearch::Model::Extensions::MappingReflection
124
+
125
+ partially_updates_document_of ::Author, records_to_update_documents: -> book { Author.find(book.author_id) } do |t, changed_fields|
126
+ t.partially_update_document(*changed_fields)
127
+ end
128
+ end
129
+
130
+ class ::Tag < ActiveRecord::Base
131
+ belongs_to :taggable, polymorphic: true
132
+
133
+ default_scope { where(arel_table[:deleted_at].eq(nil)) }
134
+
135
+ include Elasticsearch::Model
136
+ include Elasticsearch::Model::Extensions::IndexOperations
137
+ include Elasticsearch::Model::Extensions::BatchUpdating
138
+ include Elasticsearch::Model::Extensions::PartialUpdating
139
+ include Elasticsearch::Model::Extensions::OuterDocumentUpdating
140
+
141
+ def assigned_to_author?
142
+ taggable_type == 'Author'
143
+ end
144
+
145
+ def assigned_to_book?
146
+ taggable_type == 'Book'
147
+ end
148
+
149
+ partially_updates_document_of ::Book, records_to_update_documents: -> tag { Book.find(tag.taggable_id) } do |t, changed_fields|
150
+ t.partially_update_document(*changed_fields)
151
+ end
152
+
153
+ partially_updates_document_of ::Author, records_to_update_documents: -> tag { Author.find(tag.taggable_id) } do |t, changed_fields|
154
+ t.partially_update_document(*changed_fields)
155
+ end
156
+ end
157
+
158
+ Book.delete_all
159
+ Book.__elasticsearch__.create_index! force: true
160
+
161
+ Author.delete_all
162
+ Author.__elasticsearch__.create_index! force: true
163
+
164
+ Author.create! nickname: 'Mikoto',
165
+ books_attributes: [
166
+ {title: 'Test', tags_attributes: [{body: 'testing'}]}
167
+ ],
168
+ tags_attributes: [
169
+ {body: 'testing'}
170
+ ]
171
+
172
+ Author.create! nickname: 'Kuroko',
173
+ books_attributes: [
174
+ {title: 'Code & Test', tags_attributes: [{body: 'testing'}, {body: 'coding'}]},
175
+ {title: 'Code', tags_attributes: [{body: 'coding'}]}
176
+ ],
177
+ tags_attributes: [
178
+ {body: 'testing'},
179
+ {body: 'coding'}
180
+ ]
181
+
182
+ Book.__elasticsearch__.refresh_index!
183
+ Author.__elasticsearch__.refresh_index!
File without changes
data/spec/setup/sqlite.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'active_record'
2
2
  require 'logger'
3
3
 
4
- ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ":memory:")
4
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => "test.db")
5
5
  logger = ::Logger.new(STDERR)
6
6
  logger.formatter = lambda { |s, d, p, m| "\e[2;36m#{m}\e[0m\n" }
7
7
  ActiveRecord::Base.logger = logger unless ENV['QUIET']
@@ -1,7 +1,7 @@
1
- if defined? ::Article
2
- Object.send(:remove_const, :Article)
3
- end
4
-
5
- if defined? ::Comment
6
- Object.send(:remove_const, :Comment)
1
+ # Required to clear classes defined in `load 'setup/*.rb'` to make subsequent `load 'setup/*.rb'` to work correctly.
2
+ %i| Article Comment AuthorProfile Author Tag Book |.each do |class_name|
3
+ if Object.const_defined?(class_name)
4
+ Object.send(:remove_const, class_name)
5
+ STDERR.puts "Undefined #{class_name}"
6
+ end
7
7
  end
data/spec/spec_helper.rb CHANGED
@@ -40,4 +40,10 @@ RSpec.configure do |config|
40
40
  example.run
41
41
  end
42
42
  end
43
+
44
+ config.before(:each) do |s|
45
+ md = s.metadata
46
+ x = md[:example_group]
47
+ STDERR.puts "==>>> #{x[:file_path]}:#{x[:line_number]} #{md[:description_args]}"
48
+ end
43
49
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elasticsearch-model-extensions
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yusuke KUOKA
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-18 00:00:00.000000000 Z
11
+ date: 2014-12-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -54,9 +54,15 @@ files:
54
54
  - README.md
55
55
  - Rakefile
56
56
  - elasticsearch-model-extensions.gemspec
57
+ - gemfiles/rails41.gemfile
58
+ - gemfiles/rails41.gemfile.lock
57
59
  - lib/elasticsearch/model/extensions.rb
58
60
  - lib/elasticsearch/model/extensions/all.rb
61
+ - lib/elasticsearch/model/extensions/association_path_finding/association_path_finder.rb
62
+ - lib/elasticsearch/model/extensions/association_path_finding/mapping_node.rb
63
+ - lib/elasticsearch/model/extensions/association_path_finding/shortest_path.rb
59
64
  - lib/elasticsearch/model/extensions/batch_updating.rb
65
+ - lib/elasticsearch/model/extensions/batch_updating/batch_updater.rb
60
66
  - lib/elasticsearch/model/extensions/callback.rb
61
67
  - lib/elasticsearch/model/extensions/configuration.rb
62
68
  - lib/elasticsearch/model/extensions/delayed_job.rb
@@ -65,15 +71,19 @@ files:
65
71
  - lib/elasticsearch/model/extensions/delayed_job/index_document_job.rb
66
72
  - lib/elasticsearch/model/extensions/delayed_job/partially_update_document_job.rb
67
73
  - lib/elasticsearch/model/extensions/dependency_tracking.rb
74
+ - lib/elasticsearch/model/extensions/dependency_tracking/dependency_tracker.rb
68
75
  - lib/elasticsearch/model/extensions/destroy_callback.rb
69
76
  - lib/elasticsearch/model/extensions/index_operations.rb
70
- - lib/elasticsearch/model/extensions/mapping_node.rb
71
77
  - lib/elasticsearch/model/extensions/mapping_reflection.rb
78
+ - lib/elasticsearch/model/extensions/mapping_reflection/mapping_reflector.rb
72
79
  - lib/elasticsearch/model/extensions/outer_document_updating.rb
73
80
  - lib/elasticsearch/model/extensions/partial_updating.rb
74
- - lib/elasticsearch/model/extensions/shortest_path.rb
81
+ - lib/elasticsearch/model/extensions/partial_updating/partial_updater.rb
82
+ - lib/elasticsearch/model/extensions/proxy.rb
75
83
  - lib/elasticsearch/model/extensions/update_callback.rb
76
84
  - lib/elasticsearch/model/extensions/version.rb
85
+ - spec/batch_updating/batch_updater_spec.rb
86
+ - spec/batch_updating_spec.rb
77
87
  - spec/example/articles_with_comments.rb
78
88
  - spec/integration_spec.rb
79
89
  - spec/outer_document_updating_spec.rb
@@ -81,9 +91,11 @@ files:
81
91
  - spec/setup/articles.rb
82
92
  - spec/setup/articles_with_comments.rb
83
93
  - spec/setup/articles_with_comments_and_delayed_jobs.rb
94
+ - spec/setup/authors_and_books_with_tags.rb
84
95
  - spec/setup/elasticsearch/model.rb
85
96
  - spec/setup/elasticsearch/start.rb
86
97
  - spec/setup/elasticsearch/stop.rb
98
+ - spec/setup/items_and_categories.rb
87
99
  - spec/setup/sqlite.rb
88
100
  - spec/setup/undefine.rb
89
101
  - spec/spec_helper.rb
@@ -114,6 +126,8 @@ summary: A set of extensions for elasticsearch-model which aims to ease the burd
114
126
  of things like re-indexing, verbose/complex mapping that you may face once you started
115
127
  using elasticsearch seriously.
116
128
  test_files:
129
+ - spec/batch_updating/batch_updater_spec.rb
130
+ - spec/batch_updating_spec.rb
117
131
  - spec/example/articles_with_comments.rb
118
132
  - spec/integration_spec.rb
119
133
  - spec/outer_document_updating_spec.rb
@@ -121,9 +135,11 @@ test_files:
121
135
  - spec/setup/articles.rb
122
136
  - spec/setup/articles_with_comments.rb
123
137
  - spec/setup/articles_with_comments_and_delayed_jobs.rb
138
+ - spec/setup/authors_and_books_with_tags.rb
124
139
  - spec/setup/elasticsearch/model.rb
125
140
  - spec/setup/elasticsearch/start.rb
126
141
  - spec/setup/elasticsearch/stop.rb
142
+ - spec/setup/items_and_categories.rb
127
143
  - spec/setup/sqlite.rb
128
144
  - spec/setup/undefine.rb
129
145
  - spec/spec_helper.rb
@@ -1,65 +0,0 @@
1
- require_relative 'shortest_path'
2
-
3
- module Elasticsearch
4
- module Model
5
- module Extensions
6
- class MappingNode < ShortestPath::Node
7
- def self.from_class(klass)
8
- name = klass.document_type.intern
9
-
10
- new(klass: klass, name: name, mapping: klass.mapping.to_hash[name])
11
- end
12
-
13
- def initialize(klass:, name:, mapping:, through_class:nil)
14
- @klass = klass
15
- @name = name
16
- @mapping = mapping
17
- @through_class = through_class
18
- end
19
-
20
- def name
21
- @name
22
- end
23
-
24
- def relates_to_class?(klass)
25
- @klass == klass || @through_class == klass
26
- end
27
-
28
- def klass
29
- @klass
30
- end
31
-
32
- def through_class
33
- @through_class
34
- end
35
-
36
- def each(&block)
37
- associations = @klass.reflect_on_all_associations
38
-
39
- props = @mapping[:properties]
40
- fields = props.keys
41
-
42
- edges = fields.map { |f|
43
- a = associations.find { |a| a.name == f }
44
-
45
- if a && a.options[:polymorphic] != true
46
- through_class = if a.options[:through]
47
- a.options[:through].to_s.classify.constantize
48
- end
49
-
50
- dest = MappingNode.new(klass: a.class_name.constantize, name: f.to_s.pluralize.intern, mapping: props[f], through_class: through_class)
51
-
52
- edge_class.new(name: f, destination: dest)
53
- end
54
- }.reject(&:nil?)
55
-
56
- if block.nil?
57
- edges
58
- else
59
- edges.each(&block)
60
- end
61
- end
62
- end
63
- end
64
- end
65
- end
@@ -1,106 +0,0 @@
1
- module Elasticsearch
2
- module Model
3
- module Extensions
4
- class ShortestPath
5
- MAX_DEPTH = 5
6
-
7
- class Node
8
- include Enumerable
9
-
10
- def each(&block)
11
- raise "A required method #{self.class}#each is not implemented."
12
- end
13
-
14
- def name
15
- raise "A required method #{self.class}#name is not implemented."
16
- end
17
-
18
- def each_with_name(&block)
19
- iterator = each.lazy.map do |edge|
20
- [edge, edge.name]
21
- end
22
-
23
- if block.nil?
24
- iterator
25
- else
26
- iterator.each(&block)
27
- end
28
- end
29
-
30
- def hash
31
- name.hash
32
- end
33
-
34
- def eql?(other)
35
- self.class == other.class && (name.eql? other.name)
36
- end
37
-
38
- def edge_class
39
- ShortestPath::Edge
40
- end
41
-
42
- def breadth_first_search(&block)
43
- ShortestPath.breadth_first_search self, &block
44
- end
45
- end
46
-
47
- class Edge
48
- def initialize(name:, destination:)
49
- @name = name
50
- @destination = destination
51
- end
52
-
53
- def name
54
- @name
55
- end
56
-
57
- def destination
58
- @destination
59
- end
60
- end
61
-
62
- module ClassMethods
63
- def breadth_first_search(node, &block)
64
- original_paths = node.each.map { |e| [e] }
65
- paths = original_paths
66
-
67
- depth = 0
68
-
69
- loop {
70
- a = paths.select { |p|
71
- if block.call(p.last)
72
- p
73
- end
74
- }
75
-
76
- return a if a.size != 0
77
- raise RuntimeError, 'Maximum depth exceeded while calculating the shortest path' if depth >= Elasticsearch::Model::Extensions::ShortestPath::MAX_DEPTH
78
-
79
- paths = paths.flat_map { |p|
80
- p.last.destination.each.map { |e|
81
- p + [e]
82
- }
83
- }
84
-
85
- depth += 1
86
- }
87
- end
88
-
89
- def depth_first_search(node, &block)
90
- node.each.select do |edge|
91
- if block.call(edge)
92
- [[edge]]
93
- else
94
- depth_first_search(edge.destination, &block).map do |path|
95
- [edge] + path
96
- end
97
- end
98
- end
99
- end
100
- end
101
-
102
- extend ClassMethods
103
- end
104
- end
105
- end
106
- end