elasticsearch-model 0.1.0 → 0.1.1

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.
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ ## 0.1.1
2
+
3
+ * Improved documentation and tests
4
+ * Fixed Kaminari implementation bugs and inconsistencies
5
+
6
+ ## 0.1.0 (Initial Version)
data/README.md CHANGED
@@ -76,10 +76,8 @@ module Searchable
76
76
  mapping do
77
77
  # ...
78
78
  end
79
- end
80
79
 
81
- module ClassMethods
82
- def search(query)
80
+ def self.search(query)
83
81
  # ...
84
82
  end
85
83
  end
@@ -99,8 +97,8 @@ all its functionality. To prevent polluting your model namespace, this functiona
99
97
  available via the `__elasticsearch__` class and instance level proxy methods;
100
98
  see the `Elasticsearch::Model::Proxy` class documentation for technical information.
101
99
 
102
- The module will include important methods, such as `search`, into the includeing class or module
103
- only when they haven't been defined already. Following two calls are thus functionally equivalent:
100
+ The module will include important methods, such as `search`, into the class or module only
101
+ when they haven't been defined already. Following two calls are thus functionally equivalent:
104
102
 
105
103
  ```ruby
106
104
  Article.__elasticsearch__.search 'fox'
@@ -38,6 +38,7 @@ Gem::Specification.new do |s|
38
38
  s.add_development_dependency "kaminari"
39
39
  # NOTE: Do not add Mongoid here, keep only in 3/4 files
40
40
 
41
+ s.add_development_dependency "minitest", "~> 4.0"
41
42
  s.add_development_dependency "shoulda-context"
42
43
  s.add_development_dependency "mocha"
43
44
  s.add_development_dependency "turn"
@@ -68,6 +68,8 @@ end
68
68
  class Author < ActiveRecord::Base
69
69
  has_many :authorships
70
70
 
71
+ after_update { self.authorships.each(&:touch) }
72
+
71
73
  def full_name
72
74
  [first_name, last_name].compact.join(' ')
73
75
  end
@@ -333,11 +333,17 @@ module Elasticsearch
333
333
  #
334
334
  def update_document(options={})
335
335
  if changed_attributes = self.instance_variable_get(:@__changed_attributes)
336
+ attributes = if respond_to?(:as_indexed_json)
337
+ changed_attributes.select { |k,v| self.as_indexed_json.keys.include? k }
338
+ else
339
+ changed_attributes
340
+ end
341
+
336
342
  client.update(
337
343
  { index: index_name,
338
344
  type: document_type,
339
345
  id: self.id,
340
- body: { doc: changed_attributes } }.merge(options)
346
+ body: { doc: attributes } }.merge(options)
341
347
  )
342
348
  else
343
349
  index_document(options)
@@ -30,8 +30,12 @@ module Elasticsearch
30
30
  @results = nil
31
31
  @records = nil
32
32
  @response = nil
33
- self.search.definition.update size: klass.default_per_page,
34
- from: klass.default_per_page * ([num.to_i, 1].max - 1)
33
+ @page = [num.to_i, 1].max
34
+ @per_page ||= klass.default_per_page
35
+
36
+ self.search.definition.update size: @per_page,
37
+ from: @per_page * (@page - 1)
38
+
35
39
  self
36
40
  end
37
41
  RUBY
@@ -41,12 +45,10 @@ module Elasticsearch
41
45
  #
42
46
  def limit_value
43
47
  case
44
- when search.definition[:body] && search.definition[:body][:size]
45
- search.definition[:body][:size]
46
48
  when search.definition[:size]
47
49
  search.definition[:size]
48
50
  else
49
- 0
51
+ search.klass.default_per_page
50
52
  end
51
53
  end
52
54
 
@@ -54,8 +56,6 @@ module Elasticsearch
54
56
  #
55
57
  def offset_value
56
58
  case
57
- when search.definition[:body] && search.definition[:body][:from]
58
- search.definition[:body][:from]
59
59
  when search.definition[:from]
60
60
  search.definition[:from]
61
61
  else
@@ -69,7 +69,10 @@ module Elasticsearch
69
69
  @results = nil
70
70
  @records = nil
71
71
  @response = nil
72
- search.definition.update :size => value
72
+ @per_page = value
73
+
74
+ search.definition.update :size => @per_page
75
+ search.definition.update :from => @per_page * (@page - 1) if @page
73
76
  self
74
77
  end
75
78
 
@@ -79,6 +82,7 @@ module Elasticsearch
79
82
  @results = nil
80
83
  @records = nil
81
84
  @response = nil
85
+ @page = nil
82
86
  search.definition.update :from => value
83
87
  self
84
88
  end
@@ -1,5 +1,5 @@
1
1
  module Elasticsearch
2
2
  module Model
3
- VERSION = "0.1.0"
3
+ VERSION = "0.1.1"
4
4
  end
5
5
  end
@@ -0,0 +1,61 @@
1
+ require 'test_helper'
2
+
3
+ module Elasticsearch
4
+ module Model
5
+ class ActiveRecordCustomSerializationTest < Elasticsearch::Test::IntegrationTestCase
6
+
7
+ class ::ArticleWithCustomSerialization < ActiveRecord::Base
8
+ include Elasticsearch::Model
9
+ include Elasticsearch::Model::Callbacks
10
+
11
+ mapping do
12
+ indexes :title
13
+ end
14
+
15
+ def as_indexed_json(options={})
16
+ as_json(options.merge root: false).slice('title')
17
+ end
18
+ end
19
+
20
+ context "ActiveRecord model with custom JSON serialization" do
21
+ setup do
22
+ ActiveRecord::Schema.define(:version => 1) do
23
+ create_table ArticleWithCustomSerialization.table_name do |t|
24
+ t.string :title
25
+ t.string :status
26
+ end
27
+ end
28
+
29
+ ArticleWithCustomSerialization.delete_all
30
+ ArticleWithCustomSerialization.__elasticsearch__.create_index! force: true
31
+ end
32
+
33
+ should "index only the title attribute when creating" do
34
+ ArticleWithCustomSerialization.create! title: 'Test', status: 'green'
35
+
36
+ a = ArticleWithCustomSerialization.__elasticsearch__.client.get \
37
+ index: 'article_with_custom_serializations',
38
+ type: 'article_with_custom_serialization',
39
+ id: '1'
40
+
41
+ assert_equal( { 'title' => 'Test' }, a['_source'] )
42
+ end
43
+
44
+ should "index only the title attribute when updating" do
45
+ ArticleWithCustomSerialization.create! title: 'Test', status: 'green'
46
+
47
+ article = ArticleWithCustomSerialization.first
48
+ article.update_attributes title: 'UPDATED', status: 'red'
49
+
50
+ a = ArticleWithCustomSerialization.__elasticsearch__.client.get \
51
+ index: 'article_with_custom_serializations',
52
+ type: 'article_with_custom_serialization',
53
+ id: '1'
54
+
55
+ assert_equal( { 'title' => 'UPDATED' }, a['_source'] )
56
+ end
57
+ end
58
+
59
+ end
60
+ end
61
+ end
@@ -26,6 +26,7 @@ module Elasticsearch
26
26
 
27
27
  ImportArticle.delete_all
28
28
  ImportArticle.__elasticsearch__.create_index! force: true
29
+ ImportArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow'
29
30
 
30
31
  100.times { |i| ImportArticle.create! title: "Test #{i}" }
31
32
  end
@@ -4,9 +4,11 @@ module Elasticsearch
4
4
  module Model
5
5
  class ActiveRecordPaginationTest < Elasticsearch::Test::IntegrationTestCase
6
6
 
7
- class ::Article < ActiveRecord::Base
7
+ class ::ArticleForPagination < ActiveRecord::Base
8
8
  include Elasticsearch::Model
9
9
 
10
+ scope :published, -> { where(published: true) }
11
+
10
12
  settings index: { number_of_shards: 1, number_of_replicas: 0 } do
11
13
  mapping do
12
14
  indexes :title, type: 'string', analyzer: 'snowball'
@@ -20,23 +22,26 @@ module Elasticsearch
20
22
  context "ActiveRecord pagination" do
21
23
  setup do
22
24
  ActiveRecord::Schema.define(:version => 1) do
23
- create_table :articles do |t|
25
+ create_table ::ArticleForPagination.table_name do |t|
24
26
  t.string :title
25
27
  t.datetime :created_at, :default => 'NOW()'
28
+ t.boolean :published
26
29
  end
27
30
  end
28
31
 
29
- Article.delete_all
30
- Article.__elasticsearch__.create_index! force: true
32
+ ArticleForPagination.delete_all
33
+ ArticleForPagination.__elasticsearch__.create_index! force: true
31
34
 
32
- 68.times do |i| ::Article.create! title: "Test #{i}" end
35
+ 68.times do |i|
36
+ ::ArticleForPagination.create! title: "Test #{i}", published: (i % 2 == 0)
37
+ end
33
38
 
34
- Article.import
35
- Article.__elasticsearch__.refresh_index!
39
+ ArticleForPagination.import
40
+ ArticleForPagination.__elasticsearch__.refresh_index!
36
41
  end
37
42
 
38
43
  should "be on the first page by default" do
39
- records = Article.search('title:test').page(1).records
44
+ records = ArticleForPagination.search('title:test').page(1).records
40
45
 
41
46
  assert_equal 25, records.size
42
47
  assert_equal 1, records.current_page
@@ -50,7 +55,7 @@ module Elasticsearch
50
55
  end
51
56
 
52
57
  should "load next page" do
53
- records = Article.search('title:test').page(2).records
58
+ records = ArticleForPagination.search('title:test').page(2).records
54
59
 
55
60
  assert_equal 25, records.size
56
61
  assert_equal 2, records.current_page
@@ -64,7 +69,7 @@ module Elasticsearch
64
69
  end
65
70
 
66
71
  should "load last page" do
67
- records = Article.search('title:test').page(3).records
72
+ records = ArticleForPagination.search('title:test').page(3).records
68
73
 
69
74
  assert_equal 18, records.size
70
75
  assert_equal 3, records.current_page
@@ -78,7 +83,7 @@ module Elasticsearch
78
83
  end
79
84
 
80
85
  should "not load invalid page" do
81
- records = Article.search('title:test').page(6).records
86
+ records = ArticleForPagination.search('title:test').page(6).records
82
87
 
83
88
  assert_equal 0, records.size
84
89
  assert_equal 6, records.current_page
@@ -91,16 +96,34 @@ module Elasticsearch
91
96
  assert records.out_of_range?, "Should be out of range"
92
97
  end
93
98
 
99
+ should "be combined with scopes" do
100
+ records = ArticleForPagination.search('title:test').page(2).records.published
101
+ assert records.all? { |r| r.published? }
102
+ assert_equal 12, records.size
103
+ end
104
+
105
+ should "set the limit per request" do
106
+ records = ArticleForPagination.search('title:test').limit(50).page(2).records
107
+
108
+ assert_equal 18, records.size
109
+ assert_equal 2, records.current_page
110
+ assert_equal 1, records.prev_page
111
+ assert_equal nil, records.next_page
112
+ assert_equal 2, records.total_pages
113
+
114
+ assert records.last_page?, "Should be the last page"
115
+ end
116
+
94
117
  context "with specific model settings" do
95
118
  teardown do
96
- Article.instance_variable_set(:@_default_per_page, nil)
119
+ ArticleForPagination.instance_variable_set(:@_default_per_page, nil)
97
120
  end
98
- end
99
121
 
100
- should "respect paginates_per" do
101
- Article.paginates_per 50
122
+ should "respect paginates_per" do
123
+ ArticleForPagination.paginates_per 50
102
124
 
103
- assert_equal 50, Article.search('*').page(1).records.size
125
+ assert_equal 50, ArticleForPagination.search('*').page(1).records.size
126
+ end
104
127
  end
105
128
  end
106
129
 
@@ -61,6 +61,7 @@ if ENV["MONGODB_AVAILABLE"]
61
61
  MongoidArticle.create! title: 'Coding'
62
62
 
63
63
  MongoidArticle.__elasticsearch__.refresh_index!
64
+ MongoidArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow'
64
65
  end
65
66
 
66
67
  should "index and find a document" do
@@ -147,6 +148,7 @@ if ENV["MONGODB_AVAILABLE"]
147
148
  MongoidArticle.delete_all
148
149
  97.times { |i| MongoidArticle.create! title: "Test #{i}" }
149
150
  MongoidArticle.__elasticsearch__.create_index! force: true
151
+ MongoidArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow'
150
152
  end
151
153
 
152
154
  should "import all the documents" do
@@ -129,6 +129,25 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
129
129
  end
130
130
  end
131
131
 
132
+ class ::DummyIndexingModelWithCallbacksAndCustomAsIndexedJson
133
+ extend Elasticsearch::Model::Indexing::ClassMethods
134
+ include Elasticsearch::Model::Indexing::InstanceMethods
135
+
136
+ def self.before_save(&block)
137
+ (@callbacks ||= {})[block.hash] = block
138
+ end
139
+
140
+ def changed_attributes; [:foo, :bar]; end
141
+
142
+ def changes
143
+ {:foo => ['A', 'B'], :bar => ['C', 'D']}
144
+ end
145
+
146
+ def as_indexed_json(options={})
147
+ { :foo => 'B' }
148
+ end
149
+ end
150
+
132
151
  should "register before_save callback when included" do
133
152
  ::DummyIndexingModelWithCallbacks.expects(:before_save).returns(true)
134
153
  ::DummyIndexingModelWithCallbacks.__send__ :include, Elasticsearch::Model::Indexing::InstanceMethods
@@ -251,6 +270,25 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
251
270
 
252
271
  instance.update_document
253
272
  end
273
+
274
+ should "exclude attributes not contained in custom as_indexed_json during partial update" do
275
+ client = mock('client')
276
+ instance = ::DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new
277
+
278
+ # Set the fake `changes` hash
279
+ instance.instance_variable_set(:@__changed_attributes, {foo: 'B', bar: 'D' })
280
+
281
+ client.expects(:update).with do |payload|
282
+ assert_equal({foo: 'B'}, payload[:body][:doc])
283
+ end
284
+
285
+ instance.expects(:client).returns(client)
286
+ instance.expects(:index_name).returns('foo')
287
+ instance.expects(:document_type).returns('bar')
288
+ instance.expects(:id).returns('1')
289
+
290
+ instance.update_document
291
+ end
254
292
  end
255
293
 
256
294
  context "Re-creating the index" do
@@ -13,7 +13,7 @@ class Elasticsearch::Model::ResponsePaginationTest < Test::Unit::TestCase
13
13
  'hits' => { 'total' => 100, 'hits' => (1..100).to_a.map { |i| { _id: i } } } }
14
14
 
15
15
  setup do
16
- @search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, '*'
16
+ @search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, '*'
17
17
  @response = Elasticsearch::Model::Response::Response.new ModelClass, @search, RESPONSE
18
18
  @response.klass.stubs(:client).returns mock('client')
19
19
  end
@@ -62,7 +62,7 @@ class Elasticsearch::Model::ResponsePaginationTest < Test::Unit::TestCase
62
62
 
63
63
  context "limit/offset readers" do
64
64
  should "return the default" do
65
- assert_equal 0, @response.limit_value
65
+ assert_equal Kaminari.config.default_per_page, @response.limit_value
66
66
  assert_equal 0, @response.offset_value
67
67
  end
68
68
 
@@ -74,12 +74,13 @@ class Elasticsearch::Model::ResponsePaginationTest < Test::Unit::TestCase
74
74
  assert_equal 50, @response.offset_value
75
75
  end
76
76
 
77
- should "return the value from body" do
78
- search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, { query: { match_all: {} }, from: 10, size: 50 }
77
+ should "ignore the value from request body" do
78
+ search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass,
79
+ { query: { match_all: {} }, from: 333, size: 999 }
79
80
  @response = Elasticsearch::Model::Response::Response.new ModelClass, search, RESPONSE
80
81
 
81
- assert_equal 50, @response.limit_value
82
- assert_equal 10, @response.offset_value
82
+ assert_equal Kaminari.config.default_per_page, @response.limit_value
83
+ assert_equal 0, @response.offset_value
83
84
  end
84
85
  end
85
86
 
@@ -103,6 +104,33 @@ class Elasticsearch::Model::ResponsePaginationTest < Test::Unit::TestCase
103
104
  end
104
105
  end
105
106
 
107
+ context "with the page() and limit() methods" do
108
+ setup do
109
+ @response.records
110
+ @response.results
111
+ end
112
+
113
+ should "set the values" do
114
+ @response.page(3).limit(35)
115
+ assert_equal 35, @response.search.definition[:size]
116
+ assert_equal 70, @response.search.definition[:from]
117
+ end
118
+
119
+ should "set the values when limit is called first" do
120
+ @response.limit(35).page(3)
121
+ assert_equal 35, @response.search.definition[:size]
122
+ assert_equal 70, @response.search.definition[:from]
123
+ end
124
+
125
+ should "reset the instance variables" do
126
+ @response.page(3).limit(35)
127
+
128
+ assert_nil @response.instance_variable_get(:@response)
129
+ assert_nil @response.instance_variable_get(:@records)
130
+ assert_nil @response.instance_variable_get(:@results)
131
+ end
132
+ end
133
+
106
134
  context "offset setter" do
107
135
  setup do
108
136
  @response.records
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elasticsearch-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-03-03 00:00:00.000000000 Z
12
+ date: 2014-04-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: elasticsearch
@@ -187,6 +187,22 @@ dependencies:
187
187
  - - ! '>='
188
188
  - !ruby/object:Gem::Version
189
189
  version: '0'
190
+ - !ruby/object:Gem::Dependency
191
+ name: minitest
192
+ requirement: !ruby/object:Gem::Requirement
193
+ none: false
194
+ requirements:
195
+ - - ~>
196
+ - !ruby/object:Gem::Version
197
+ version: '4.0'
198
+ type: :development
199
+ prerelease: false
200
+ version_requirements: !ruby/object:Gem::Requirement
201
+ none: false
202
+ requirements:
203
+ - - ~>
204
+ - !ruby/object:Gem::Version
205
+ version: '4.0'
190
206
  - !ruby/object:Gem::Dependency
191
207
  name: shoulda-context
192
208
  requirement: !ruby/object:Gem::Requirement
@@ -373,6 +389,7 @@ extra_rdoc_files:
373
389
  - LICENSE.txt
374
390
  files:
375
391
  - .gitignore
392
+ - CHANGELOG.md
376
393
  - Gemfile
377
394
  - LICENSE.txt
378
395
  - README.md
@@ -411,6 +428,7 @@ files:
411
428
  - test/integration/active_record_associations_parent_child.rb
412
429
  - test/integration/active_record_associations_test.rb
413
430
  - test/integration/active_record_basic_test.rb
431
+ - test/integration/active_record_custom_serialization_test.rb
414
432
  - test/integration/active_record_import_test.rb
415
433
  - test/integration/active_record_namespaced_model_test.rb
416
434
  - test/integration/active_record_pagination_test.rb
@@ -466,6 +484,7 @@ test_files:
466
484
  - test/integration/active_record_associations_parent_child.rb
467
485
  - test/integration/active_record_associations_test.rb
468
486
  - test/integration/active_record_basic_test.rb
487
+ - test/integration/active_record_custom_serialization_test.rb
469
488
  - test/integration/active_record_import_test.rb
470
489
  - test/integration/active_record_namespaced_model_test.rb
471
490
  - test/integration/active_record_pagination_test.rb