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