elasticsearch-model 6.0.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -0
  3. data/README.md +14 -7
  4. data/Rakefile +27 -36
  5. data/elasticsearch-model.gemspec +1 -1
  6. data/examples/activerecord_mapping_completion.rb +2 -15
  7. data/gemfiles/3.0.gemfile +6 -1
  8. data/gemfiles/4.0.gemfile +7 -1
  9. data/gemfiles/5.0.gemfile +6 -0
  10. data/lib/elasticsearch/model.rb +15 -8
  11. data/lib/elasticsearch/model/adapters/active_record.rb +7 -26
  12. data/lib/elasticsearch/model/indexing.rb +5 -3
  13. data/lib/elasticsearch/model/naming.rb +6 -1
  14. data/lib/elasticsearch/model/response.rb +2 -2
  15. data/lib/elasticsearch/model/response/pagination.rb +2 -192
  16. data/lib/elasticsearch/model/response/pagination/kaminari.rb +109 -0
  17. data/lib/elasticsearch/model/response/pagination/will_paginate.rb +95 -0
  18. data/lib/elasticsearch/model/response/result.rb +1 -1
  19. data/lib/elasticsearch/model/version.rb +1 -1
  20. data/spec/elasticsearch/model/adapter_spec.rb +119 -0
  21. data/spec/elasticsearch/model/adapters/active_record/associations_spec.rb +334 -0
  22. data/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +340 -0
  23. data/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb +18 -0
  24. data/spec/elasticsearch/model/adapters/active_record/import_spec.rb +187 -0
  25. data/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb +110 -0
  26. data/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb +38 -0
  27. data/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb +315 -0
  28. data/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb +75 -0
  29. data/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb +61 -0
  30. data/spec/elasticsearch/model/adapters/active_record_spec.rb +207 -0
  31. data/spec/elasticsearch/model/adapters/default_spec.rb +41 -0
  32. data/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb +267 -0
  33. data/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb +66 -0
  34. data/spec/elasticsearch/model/adapters/mongoid_spec.rb +235 -0
  35. data/spec/elasticsearch/model/adapters/multiple_spec.rb +125 -0
  36. data/spec/elasticsearch/model/callbacks_spec.rb +33 -0
  37. data/spec/elasticsearch/model/client_spec.rb +66 -0
  38. data/spec/elasticsearch/model/hash_wrapper_spec.rb +12 -0
  39. data/spec/elasticsearch/model/importing_spec.rb +214 -0
  40. data/spec/elasticsearch/model/indexing_spec.rb +918 -0
  41. data/spec/elasticsearch/model/module_spec.rb +101 -0
  42. data/spec/elasticsearch/model/multimodel_spec.rb +55 -0
  43. data/spec/elasticsearch/model/naming_inheritance_spec.rb +184 -0
  44. data/spec/elasticsearch/model/naming_spec.rb +186 -0
  45. data/spec/elasticsearch/model/proxy_spec.rb +107 -0
  46. data/spec/elasticsearch/model/response/aggregations_spec.rb +66 -0
  47. data/spec/elasticsearch/model/response/base_spec.rb +90 -0
  48. data/spec/elasticsearch/model/response/pagination/kaminari_spec.rb +410 -0
  49. data/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb +262 -0
  50. data/spec/elasticsearch/model/response/records_spec.rb +118 -0
  51. data/spec/elasticsearch/model/response/response_spec.rb +131 -0
  52. data/spec/elasticsearch/model/response/result_spec.rb +122 -0
  53. data/spec/elasticsearch/model/response/results_spec.rb +56 -0
  54. data/spec/elasticsearch/model/searching_search_request_spec.rb +112 -0
  55. data/spec/elasticsearch/model/searching_spec.rb +49 -0
  56. data/spec/elasticsearch/model/serializing_spec.rb +22 -0
  57. data/spec/spec_helper.rb +161 -0
  58. data/spec/support/app.rb +21 -0
  59. data/spec/support/app/answer.rb +33 -0
  60. data/spec/support/app/article.rb +22 -0
  61. data/spec/support/app/article_for_pagination.rb +12 -0
  62. data/spec/support/app/article_with_custom_serialization.rb +13 -0
  63. data/spec/support/app/article_with_dynamic_index_name.rb +15 -0
  64. data/spec/support/app/author.rb +9 -0
  65. data/spec/support/app/authorship.rb +4 -0
  66. data/spec/support/app/category.rb +3 -0
  67. data/spec/support/app/comment.rb +3 -0
  68. data/spec/support/app/episode.rb +11 -0
  69. data/spec/support/app/image.rb +19 -0
  70. data/spec/support/app/import_article.rb +12 -0
  71. data/spec/support/app/mongoid_article.rb +21 -0
  72. data/spec/support/app/namespaced_book.rb +10 -0
  73. data/spec/support/app/parent_and_child_searchable.rb +24 -0
  74. data/spec/support/app/post.rb +14 -0
  75. data/spec/support/app/question.rb +27 -0
  76. data/spec/support/app/searchable.rb +48 -0
  77. data/spec/support/app/series.rb +11 -0
  78. data/spec/support/model.json +1 -0
  79. data/{test → spec}/support/model.yml +0 -0
  80. metadata +129 -86
  81. data/test/integration/active_record_associations_parent_child_test.rb +0 -188
  82. data/test/integration/active_record_associations_test.rb +0 -339
  83. data/test/integration/active_record_basic_test.rb +0 -255
  84. data/test/integration/active_record_custom_serialization_test.rb +0 -67
  85. data/test/integration/active_record_import_test.rb +0 -168
  86. data/test/integration/active_record_namespaced_model_test.rb +0 -56
  87. data/test/integration/active_record_pagination_test.rb +0 -149
  88. data/test/integration/dynamic_index_name_test.rb +0 -52
  89. data/test/integration/mongoid_basic_test.rb +0 -240
  90. data/test/integration/multiple_models_test.rb +0 -176
  91. data/test/support/model.json +0 -1
  92. data/test/test_helper.rb +0 -92
  93. data/test/unit/adapter_active_record_test.rb +0 -157
  94. data/test/unit/adapter_default_test.rb +0 -41
  95. data/test/unit/adapter_mongoid_test.rb +0 -161
  96. data/test/unit/adapter_multiple_test.rb +0 -106
  97. data/test/unit/adapter_test.rb +0 -69
  98. data/test/unit/callbacks_test.rb +0 -31
  99. data/test/unit/client_test.rb +0 -27
  100. data/test/unit/hash_wrapper_test.rb +0 -13
  101. data/test/unit/importing_test.rb +0 -224
  102. data/test/unit/indexing_test.rb +0 -720
  103. data/test/unit/module_test.rb +0 -68
  104. data/test/unit/multimodel_test.rb +0 -38
  105. data/test/unit/naming_inheritance_test.rb +0 -94
  106. data/test/unit/naming_test.rb +0 -103
  107. data/test/unit/proxy_test.rb +0 -98
  108. data/test/unit/response_aggregations_test.rb +0 -46
  109. data/test/unit/response_base_test.rb +0 -40
  110. data/test/unit/response_pagination_kaminari_test.rb +0 -433
  111. data/test/unit/response_pagination_will_paginate_test.rb +0 -398
  112. data/test/unit/response_records_test.rb +0 -91
  113. data/test/unit/response_result_test.rb +0 -90
  114. data/test/unit/response_results_test.rb +0 -34
  115. data/test/unit/response_test.rb +0 -104
  116. data/test/unit/searching_search_request_test.rb +0 -78
  117. data/test/unit/searching_test.rb +0 -41
  118. data/test/unit/serializing_test.rb +0 -17
@@ -0,0 +1,109 @@
1
+ module Elasticsearch
2
+ module Model
3
+ module Response
4
+
5
+ # Pagination for search results/records
6
+ #
7
+ module Pagination
8
+ # Allow models to be paginated with the "kaminari" gem [https://github.com/amatsuda/kaminari]
9
+ #
10
+ module Kaminari
11
+ def self.included(base)
12
+ # Include the Kaminari configuration and paging method in response
13
+ #
14
+ base.__send__ :include, ::Kaminari::ConfigurationMethods::ClassMethods
15
+ base.__send__ :include, ::Kaminari::PageScopeMethods
16
+
17
+ # Include the Kaminari paging methods in results and records
18
+ #
19
+ Elasticsearch::Model::Response::Results.__send__ :include, ::Kaminari::ConfigurationMethods::ClassMethods
20
+ Elasticsearch::Model::Response::Results.__send__ :include, ::Kaminari::PageScopeMethods
21
+ Elasticsearch::Model::Response::Records.__send__ :include, ::Kaminari::PageScopeMethods
22
+
23
+ Elasticsearch::Model::Response::Results.__send__ :delegate, :limit_value, :offset_value, :total_count, :max_pages, to: :response
24
+ Elasticsearch::Model::Response::Records.__send__ :delegate, :limit_value, :offset_value, :total_count, :max_pages, to: :response
25
+
26
+ base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
27
+ # Define the `page` Kaminari method
28
+ #
29
+ def #{::Kaminari.config.page_method_name}(num=nil)
30
+ @results = nil
31
+ @records = nil
32
+ @response = nil
33
+ @page = [num.to_i, 1].max
34
+ @per_page ||= __default_per_page
35
+
36
+ self.search.definition.update size: @per_page,
37
+ from: @per_page * (@page - 1)
38
+
39
+ self
40
+ end
41
+ RUBY
42
+ end
43
+
44
+ # Returns the current "limit" (`size`) value
45
+ #
46
+ def limit_value
47
+ case
48
+ when search.definition[:size]
49
+ search.definition[:size]
50
+ else
51
+ __default_per_page
52
+ end
53
+ end
54
+
55
+ # Returns the current "offset" (`from`) value
56
+ #
57
+ def offset_value
58
+ case
59
+ when search.definition[:from]
60
+ search.definition[:from]
61
+ else
62
+ 0
63
+ end
64
+ end
65
+
66
+ # Set the "limit" (`size`) value
67
+ #
68
+ def limit(value)
69
+ return self if value.to_i <= 0
70
+ @results = nil
71
+ @records = nil
72
+ @response = nil
73
+ @per_page = value.to_i
74
+
75
+ search.definition.update :size => @per_page
76
+ search.definition.update :from => @per_page * (@page - 1) if @page
77
+ self
78
+ end
79
+
80
+ # Set the "offset" (`from`) value
81
+ #
82
+ def offset(value)
83
+ return self if value.to_i < 0
84
+ @results = nil
85
+ @records = nil
86
+ @response = nil
87
+ @page = nil
88
+ search.definition.update :from => value.to_i
89
+ self
90
+ end
91
+
92
+ # Returns the total number of results
93
+ #
94
+ def total_count
95
+ results.total
96
+ end
97
+
98
+ # Returns the models's `per_page` value or the default
99
+ #
100
+ # @api private
101
+ #
102
+ def __default_per_page
103
+ klass.respond_to?(:default_per_page) && klass.default_per_page || ::Kaminari.config.default_per_page
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,95 @@
1
+ module Elasticsearch
2
+ module Model
3
+ module Response
4
+
5
+ # Pagination for search results/records
6
+ #
7
+ module Pagination
8
+
9
+
10
+ # Allow models to be paginated with the "will_paginate" gem [https://github.com/mislav/will_paginate]
11
+ #
12
+ module WillPaginate
13
+ def self.included(base)
14
+ base.__send__ :include, ::WillPaginate::CollectionMethods
15
+
16
+ # Include the paging methods in results and records
17
+ #
18
+ methods = [:current_page, :offset, :length, :per_page, :total_entries, :total_pages, :previous_page, :next_page, :out_of_bounds?]
19
+ Elasticsearch::Model::Response::Results.__send__ :delegate, *methods, to: :response
20
+ Elasticsearch::Model::Response::Records.__send__ :delegate, *methods, to: :response
21
+ end
22
+
23
+ def offset
24
+ (current_page - 1) * per_page
25
+ end
26
+
27
+ def length
28
+ search.definition[:size]
29
+ end
30
+
31
+ # Main pagination method
32
+ #
33
+ # @example
34
+ #
35
+ # Article.search('foo').paginate(page: 1, per_page: 30)
36
+ #
37
+ def paginate(options)
38
+ param_name = options[:param_name] || :page
39
+ page = [options[param_name].to_i, 1].max
40
+ per_page = (options[:per_page] || __default_per_page).to_i
41
+
42
+ search.definition.update size: per_page,
43
+ from: (page - 1) * per_page
44
+ self
45
+ end
46
+
47
+ # Return the current page
48
+ #
49
+ def current_page
50
+ search.definition[:from] / per_page + 1 if search.definition[:from] && per_page
51
+ end
52
+
53
+ # Pagination method
54
+ #
55
+ # @example
56
+ #
57
+ # Article.search('foo').page(2)
58
+ #
59
+ def page(num)
60
+ paginate(page: num, per_page: per_page) # shorthand
61
+ end
62
+
63
+ # Return or set the "size" value
64
+ #
65
+ # @example
66
+ #
67
+ # Article.search('foo').per_page(15).page(2)
68
+ #
69
+ def per_page(num = nil)
70
+ if num.nil?
71
+ search.definition[:size]
72
+ else
73
+ paginate(page: current_page, per_page: num) # shorthand
74
+ end
75
+ end
76
+
77
+ # Returns the total number of results
78
+ #
79
+ def total_entries
80
+ results.total
81
+ end
82
+
83
+ # Returns the models's `per_page` value or the default
84
+ #
85
+ # @api private
86
+ #
87
+ def __default_per_page
88
+ klass.respond_to?(:per_page) && klass.per_page || ::WillPaginate.per_page
89
+ end
90
+ end
91
+ end
92
+
93
+ end
94
+ end
95
+ end
@@ -46,7 +46,7 @@ module Elasticsearch
46
46
 
47
47
  # Respond to methods from `@result` or `@result._source`
48
48
  #
49
- def respond_to?(method_name, include_private = false)
49
+ def respond_to_missing?(method_name, include_private = false)
50
50
  @result.respond_to?(method_name.to_sym) || \
51
51
  @result._source && @result._source.respond_to?(method_name.to_sym) || \
52
52
  super
@@ -1,5 +1,5 @@
1
1
  module Elasticsearch
2
2
  module Model
3
- VERSION = '6.0.0'
3
+ VERSION = '6.1.0'
4
4
  end
5
5
  end
@@ -0,0 +1,119 @@
1
+ require 'spec_helper'
2
+
3
+ describe Elasticsearch::Model::Adapter do
4
+
5
+ before(:all) do
6
+ class ::DummyAdapterClass; end
7
+ class ::DummyAdapterClassWithAdapter; end
8
+ class ::DummyAdapter
9
+ Records = Module.new
10
+ Callbacks = Module.new
11
+ Importing = Module.new
12
+ end
13
+ end
14
+
15
+ after(:all) do
16
+ [DummyAdapterClassWithAdapter, DummyAdapterClass, DummyAdapter].each do |adapter|
17
+ Elasticsearch::Model::Adapter::Adapter.adapters.delete(adapter)
18
+ end
19
+ remove_classes(DummyAdapterClass, DummyAdapterClassWithAdapter, DummyAdapter)
20
+ end
21
+
22
+ describe '#from_class' do
23
+
24
+ it 'should return an Adapter instance' do
25
+ expect(Elasticsearch::Model::Adapter.from_class(DummyAdapterClass)).to be_a(Elasticsearch::Model::Adapter::Adapter)
26
+ end
27
+ end
28
+
29
+ describe 'register' do
30
+
31
+ before do
32
+ expect(Elasticsearch::Model::Adapter::Adapter).to receive(:register).and_call_original
33
+ Elasticsearch::Model::Adapter.register(:foo, lambda { |c| false })
34
+ end
35
+
36
+ it 'should register an adapter' do
37
+ expect(Elasticsearch::Model::Adapter::Adapter.adapters[:foo]).to be_a(Proc)
38
+ end
39
+
40
+ context 'when a specific adapter class is set' do
41
+
42
+ before do
43
+ expect(Elasticsearch::Model::Adapter::Adapter).to receive(:register).and_call_original
44
+ Elasticsearch::Model::Adapter::Adapter.register(DummyAdapter,
45
+ lambda { |c| c == DummyAdapterClassWithAdapter })
46
+ end
47
+
48
+ let(:adapter) do
49
+ Elasticsearch::Model::Adapter::Adapter.new(DummyAdapterClassWithAdapter)
50
+ end
51
+
52
+ it 'should register the adapter' do
53
+ expect(adapter.adapter).to eq(DummyAdapter)
54
+ end
55
+ end
56
+ end
57
+
58
+ describe 'default adapter' do
59
+
60
+ let(:adapter) do
61
+ Elasticsearch::Model::Adapter::Adapter.new(DummyAdapterClass)
62
+ end
63
+
64
+ it 'sets a default adapter' do
65
+ expect(adapter.adapter).to eq(Elasticsearch::Model::Adapter::Default)
66
+ end
67
+ end
68
+
69
+ describe '#records_mixin' do
70
+
71
+ before do
72
+ Elasticsearch::Model::Adapter::Adapter.register(DummyAdapter,
73
+ lambda { |c| c == DummyAdapterClassWithAdapter })
74
+
75
+ end
76
+
77
+ let(:adapter) do
78
+ Elasticsearch::Model::Adapter::Adapter.new(DummyAdapterClassWithAdapter)
79
+ end
80
+
81
+ it 'returns a Module' do
82
+ expect(adapter.records_mixin).to be_a(Module)
83
+ end
84
+ end
85
+
86
+ describe '#callbacks_mixin' do
87
+
88
+ before do
89
+ Elasticsearch::Model::Adapter::Adapter.register(DummyAdapter,
90
+ lambda { |c| c == DummyAdapterClassWithAdapter })
91
+
92
+ end
93
+
94
+ let(:adapter) do
95
+ Elasticsearch::Model::Adapter::Adapter.new(DummyAdapterClassWithAdapter)
96
+ end
97
+
98
+ it 'returns a Module' do
99
+ expect(adapter.callbacks_mixin).to be_a(Module)
100
+ end
101
+ end
102
+
103
+ describe '#importing_mixin' do
104
+
105
+ before do
106
+ Elasticsearch::Model::Adapter::Adapter.register(DummyAdapter,
107
+ lambda { |c| c == DummyAdapterClassWithAdapter })
108
+
109
+ end
110
+
111
+ let(:adapter) do
112
+ Elasticsearch::Model::Adapter::Adapter.new(DummyAdapterClassWithAdapter)
113
+ end
114
+
115
+ it 'returns a Module' do
116
+ expect(adapter.importing_mixin).to be_a(Module)
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,334 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Elasticsearch::Model::Adapter::ActiveRecord Associations' do
4
+
5
+ before(:all) do
6
+ ActiveRecord::Schema.define(version: 1) do
7
+ create_table :categories do |t|
8
+ t.string :title
9
+ t.timestamps null: false
10
+ end
11
+
12
+ create_table :categories_posts do |t|
13
+ t.references :post, :category
14
+ end
15
+
16
+ create_table :authors do |t|
17
+ t.string :first_name, :last_name
18
+ t.timestamps null: false
19
+ end
20
+
21
+ create_table :authorships do |t|
22
+ t.string :first_name, :last_name
23
+ t.references :post
24
+ t.references :author
25
+ t.timestamps null: false
26
+ end
27
+
28
+ create_table :comments do |t|
29
+ t.string :text
30
+ t.string :author
31
+ t.references :post
32
+ t.timestamps null: false
33
+ end
34
+
35
+ add_index(:comments, :post_id) unless index_exists?(:comments, :post_id)
36
+
37
+ create_table :posts do |t|
38
+ t.string :title
39
+ t.text :text
40
+ t.boolean :published
41
+ t.timestamps null: false
42
+ end
43
+ end
44
+
45
+ Comment.__send__ :include, Elasticsearch::Model
46
+ Comment.__send__ :include, Elasticsearch::Model::Callbacks
47
+ end
48
+
49
+ before do
50
+ clear_tables(:categories, :categories_posts, :authors, :authorships, :comments, :posts)
51
+ clear_indices(Post)
52
+ Post.__elasticsearch__.create_index!(force: true)
53
+ Comment.__elasticsearch__.create_index!(force: true)
54
+ end
55
+
56
+ after do
57
+ clear_tables(Post, Category)
58
+ clear_indices(Post)
59
+ end
60
+
61
+ context 'when a document is created' do
62
+
63
+ before do
64
+ Post.create!(title: 'Test')
65
+ Post.create!(title: 'Testing Coding')
66
+ Post.create!(title: 'Coding')
67
+ Post.__elasticsearch__.refresh_index!
68
+ end
69
+
70
+ let(:search_result) do
71
+ Post.search('title:test')
72
+ end
73
+
74
+ it 'indexes the document' do
75
+ expect(search_result.results.size).to eq(2)
76
+ expect(search_result.results.first.title).to eq('Test')
77
+ expect(search_result.records.size).to eq(2)
78
+ expect(search_result.records.first.title).to eq('Test')
79
+ end
80
+ end
81
+
82
+ describe 'has_many_and_belongs_to association' do
83
+
84
+ context 'when an association is updated' do
85
+
86
+ before do
87
+ post.categories = [category_a, category_b]
88
+ Post.__elasticsearch__.refresh_index!
89
+ end
90
+
91
+ let(:category_a) do
92
+ Category.where(title: "One").first_or_create!
93
+ end
94
+
95
+ let(:category_b) do
96
+ Category.where(title: "Two").first_or_create!
97
+ end
98
+
99
+ let(:post) do
100
+ Post.create! title: "First Post", text: "This is the first post..."
101
+ end
102
+
103
+ let(:search_result) do
104
+ Post.search(query: {
105
+ bool: {
106
+ must: {
107
+ multi_match: {
108
+ fields: ['title'],
109
+ query: 'first'
110
+ }
111
+ },
112
+ filter: {
113
+ terms: {
114
+ categories: ['One']
115
+ }
116
+ }
117
+ }
118
+ } )
119
+ end
120
+
121
+ it 'applies the update with' do
122
+ expect(search_result.results.size).to eq(1)
123
+ expect(search_result.results.first.title).to eq('First Post')
124
+ expect(search_result.records.size).to eq(1)
125
+ expect(search_result.records.first.title).to eq('First Post')
126
+ end
127
+ end
128
+
129
+ context 'when an association is deleted' do
130
+
131
+ before do
132
+ post.categories = [category_a, category_b]
133
+ post.categories = [category_b]
134
+ Post.__elasticsearch__.refresh_index!
135
+ end
136
+
137
+ let(:category_a) do
138
+ Category.where(title: "One").first_or_create!
139
+ end
140
+
141
+ let(:category_b) do
142
+ Category.where(title: "Two").first_or_create!
143
+ end
144
+
145
+ let(:post) do
146
+ Post.create! title: "First Post", text: "This is the first post..."
147
+ end
148
+
149
+ let(:search_result) do
150
+ Post.search(query: {
151
+ bool: {
152
+ must: {
153
+ multi_match: {
154
+ fields: ['title'],
155
+ query: 'first'
156
+ }
157
+ },
158
+ filter: {
159
+ terms: {
160
+ categories: ['One']
161
+ }
162
+ }
163
+ }
164
+ } )
165
+ end
166
+
167
+ it 'applies the update with a reindex' do
168
+ expect(search_result.results.size).to eq(0)
169
+ expect(search_result.records.size).to eq(0)
170
+ end
171
+ end
172
+ end
173
+
174
+ describe 'has_many through association' do
175
+
176
+ context 'when the association is updated' do
177
+
178
+ before do
179
+ author_a = Author.where(first_name: "John", last_name: "Smith").first_or_create!
180
+ author_b = Author.where(first_name: "Mary", last_name: "Smith").first_or_create!
181
+ author_c = Author.where(first_name: "Kobe", last_name: "Griss").first_or_create!
182
+
183
+ # Create posts
184
+ post_1 = Post.create!(title: "First Post", text: "This is the first post...")
185
+ post_2 = Post.create!(title: "Second Post", text: "This is the second post...")
186
+ post_3 = Post.create!(title: "Third Post", text: "This is the third post...")
187
+
188
+ # Assign authors
189
+ post_1.authors = [author_a, author_b]
190
+ post_2.authors = [author_a]
191
+ post_3.authors = [author_c]
192
+
193
+ Post.__elasticsearch__.refresh_index!
194
+ end
195
+
196
+ context 'if active record is at least 4' do
197
+
198
+ let(:search_result) do
199
+ Post.search('authors.full_name:john')
200
+ end
201
+
202
+ it 'applies the update', if: active_record_at_least_4? do
203
+ expect(search_result.results.size).to eq(2)
204
+ expect(search_result.records.size).to eq(2)
205
+ end
206
+ end
207
+
208
+ context 'if active record is less than 4' do
209
+
210
+ let(:search_result) do
211
+ Post.search('authors.author.full_name:john')
212
+ end
213
+
214
+ it 'applies the update', if: !active_record_at_least_4? do
215
+ expect(search_result.results.size).to eq(2)
216
+ expect(search_result.records.size).to eq(2)
217
+ end
218
+ end
219
+ end
220
+
221
+ context 'when an association is added', if: active_record_at_least_4? do
222
+
223
+ before do
224
+ author_a = Author.where(first_name: "John", last_name: "Smith").first_or_create!
225
+ author_b = Author.where(first_name: "Mary", last_name: "Smith").first_or_create!
226
+
227
+ # Create posts
228
+ post_1 = Post.create!(title: "First Post", text: "This is the first post...")
229
+
230
+ # Assign authors
231
+ post_1.authors = [author_a]
232
+ post_1.authors << author_b
233
+ Post.__elasticsearch__.refresh_index!
234
+ end
235
+
236
+ let(:search_result) do
237
+ Post.search('authors.full_name:john')
238
+ end
239
+
240
+ it 'adds the association' do
241
+ expect(search_result.results.size).to eq(1)
242
+ expect(search_result.records.size).to eq(1)
243
+ end
244
+ end
245
+ end
246
+
247
+ describe 'has_many association' do
248
+
249
+ context 'when an association is added', if: active_record_at_least_4? do
250
+
251
+ before do
252
+ # Create posts
253
+ post_1 = Post.create!(title: "First Post", text: "This is the first post...")
254
+ post_2 = Post.create!(title: "Second Post", text: "This is the second post...")
255
+
256
+ # Add comments
257
+ post_1.comments.create!(author: 'John', text: 'Excellent')
258
+ post_1.comments.create!(author: 'Abby', text: 'Good')
259
+
260
+ post_2.comments.create!(author: 'John', text: 'Terrible')
261
+
262
+ post_1.comments.create!(author: 'John', text: 'Or rather just good...')
263
+ Post.__elasticsearch__.refresh_index!
264
+ end
265
+
266
+ let(:search_result) do
267
+ Post.search(query: {
268
+ nested: {
269
+ path: 'comments',
270
+ query: {
271
+ bool: {
272
+ must: [
273
+ { match: { 'comments.author' => 'john' } },
274
+ { match: { 'comments.text' => 'good' } }
275
+ ]
276
+ }
277
+ }
278
+ }
279
+ })
280
+ end
281
+
282
+ it 'adds the association' do
283
+ expect(search_result.results.size).to eq(1)
284
+ end
285
+ end
286
+ end
287
+
288
+ describe '#touch' do
289
+
290
+ context 'when a touch callback is defined on the model' do
291
+
292
+ before do
293
+ # Create categories
294
+ category_a = Category.where(title: "One").first_or_create!
295
+
296
+ # Create post
297
+ post = Post.create!(title: "First Post", text: "This is the first post...")
298
+
299
+ # Assign category
300
+ post.categories << category_a
301
+ category_a.update_attribute(:title, "Updated")
302
+ category_a.posts.each { |p| p.touch }
303
+
304
+ Post.__elasticsearch__.refresh_index!
305
+ end
306
+
307
+ it 'executes the callback after #touch' do
308
+ expect(Post.search('categories:One').size).to eq(0)
309
+ expect(Post.search('categories:Updated').size).to eq(1)
310
+ end
311
+ end
312
+ end
313
+
314
+ describe '#includes' do
315
+
316
+ before do
317
+ post_1 = Post.create(title: 'One')
318
+ post_2 = Post.create(title: 'Two')
319
+ post_1.comments.create(text: 'First comment')
320
+ post_2.comments.create(text: 'Second comment')
321
+
322
+ Comment.__elasticsearch__.refresh_index!
323
+ end
324
+
325
+ let(:search_result) do
326
+ Comment.search('first').records(includes: :post)
327
+ end
328
+
329
+ it 'eager loads associations' do
330
+ expect(search_result.first.association(:post)).to be_loaded
331
+ expect(search_result.first.post.title).to eq('One')
332
+ end
333
+ end
334
+ end