elasticsearch-model 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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