elasticsearch-model 6.0.0 → 6.1.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 (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