elastictastic 0.5.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.
- data/LICENSE +19 -0
- data/README.md +326 -0
- data/lib/elastictastic/association.rb +21 -0
- data/lib/elastictastic/bulk_persistence_strategy.rb +70 -0
- data/lib/elastictastic/callbacks.rb +30 -0
- data/lib/elastictastic/child_collection_proxy.rb +56 -0
- data/lib/elastictastic/client.rb +101 -0
- data/lib/elastictastic/configuration.rb +35 -0
- data/lib/elastictastic/dirty.rb +130 -0
- data/lib/elastictastic/discrete_persistence_strategy.rb +52 -0
- data/lib/elastictastic/document.rb +98 -0
- data/lib/elastictastic/errors.rb +7 -0
- data/lib/elastictastic/field.rb +38 -0
- data/lib/elastictastic/index.rb +19 -0
- data/lib/elastictastic/mass_assignment_security.rb +15 -0
- data/lib/elastictastic/middleware.rb +119 -0
- data/lib/elastictastic/nested_document.rb +29 -0
- data/lib/elastictastic/new_relic_instrumentation.rb +26 -0
- data/lib/elastictastic/observer.rb +3 -0
- data/lib/elastictastic/observing.rb +21 -0
- data/lib/elastictastic/parent_child.rb +115 -0
- data/lib/elastictastic/persistence.rb +67 -0
- data/lib/elastictastic/properties.rb +236 -0
- data/lib/elastictastic/railtie.rb +35 -0
- data/lib/elastictastic/resource.rb +4 -0
- data/lib/elastictastic/scope.rb +283 -0
- data/lib/elastictastic/scope_builder.rb +32 -0
- data/lib/elastictastic/scoped.rb +20 -0
- data/lib/elastictastic/search.rb +180 -0
- data/lib/elastictastic/server_error.rb +15 -0
- data/lib/elastictastic/test_helpers.rb +172 -0
- data/lib/elastictastic/util.rb +63 -0
- data/lib/elastictastic/validations.rb +45 -0
- data/lib/elastictastic/version.rb +3 -0
- data/lib/elastictastic.rb +82 -0
- data/spec/environment.rb +6 -0
- data/spec/examples/active_model_lint_spec.rb +20 -0
- data/spec/examples/bulk_persistence_strategy_spec.rb +233 -0
- data/spec/examples/callbacks_spec.rb +96 -0
- data/spec/examples/dirty_spec.rb +238 -0
- data/spec/examples/document_spec.rb +600 -0
- data/spec/examples/mass_assignment_security_spec.rb +13 -0
- data/spec/examples/middleware_spec.rb +92 -0
- data/spec/examples/observing_spec.rb +141 -0
- data/spec/examples/parent_child_spec.rb +308 -0
- data/spec/examples/properties_spec.rb +92 -0
- data/spec/examples/scope_spec.rb +491 -0
- data/spec/examples/search_spec.rb +382 -0
- data/spec/examples/spec_helper.rb +15 -0
- data/spec/examples/validation_spec.rb +65 -0
- data/spec/models/author.rb +9 -0
- data/spec/models/blog.rb +5 -0
- data/spec/models/comment.rb +5 -0
- data/spec/models/post.rb +41 -0
- data/spec/models/post_observer.rb +11 -0
- data/spec/support/fakeweb_request_history.rb +13 -0
- metadata +227 -0
@@ -0,0 +1,141 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Elastictastic::Observing do
|
4
|
+
include Elastictastic::TestHelpers
|
5
|
+
|
6
|
+
let(:id) { '123' }
|
7
|
+
let(:post) { Post.new }
|
8
|
+
let(:persisted_post) do
|
9
|
+
Post.new.tap do |post|
|
10
|
+
post.elasticsearch_hit = { '_id' => id, '_index' => 'default' }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
before do
|
15
|
+
stub_elasticsearch_create('default', 'post')
|
16
|
+
stub_elasticsearch_update('default', 'post', id)
|
17
|
+
stub_elasticsearch_destroy('default', 'post', id)
|
18
|
+
Elastictastic.config.observers = [:post_observer]
|
19
|
+
Elastictastic.config.instantiate_observers
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'on create' do
|
23
|
+
let(:observers) { post.observers_that_ran }
|
24
|
+
|
25
|
+
before do
|
26
|
+
post.save
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should run before_create' do
|
30
|
+
observers.should include(:before_create)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should run after_create' do
|
34
|
+
observers.should include(:after_create)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should run before_save' do
|
38
|
+
observers.should include(:before_save)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should run after_save' do
|
42
|
+
observers.should include(:after_save)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should not run before_update' do
|
46
|
+
observers.should_not include(:before_update)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should not run after_update' do
|
50
|
+
observers.should_not include(:after_update)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should not run before_destroy' do
|
54
|
+
observers.should_not include(:before_destroy)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should not run after_destroy' do
|
58
|
+
observers.should_not include(:after_destroy)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'on update' do
|
63
|
+
let(:observers) { persisted_post.observers_that_ran }
|
64
|
+
|
65
|
+
before do
|
66
|
+
persisted_post.save
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should not run before_create' do
|
70
|
+
observers.should_not include(:before_create)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should not run after_create' do
|
74
|
+
observers.should_not include(:after_create)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should run before_update' do
|
78
|
+
observers.should include(:before_update)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should run after_update' do
|
82
|
+
observers.should include(:after_update)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should run before_save' do
|
86
|
+
observers.should include(:before_save)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should run after_save' do
|
90
|
+
observers.should include(:after_save)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should not run before_destroy' do
|
94
|
+
observers.should_not include(:before_destroy)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'should not run after_destroy' do
|
98
|
+
observers.should_not include(:after_destroy)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'on destroy' do
|
103
|
+
let(:observers) { persisted_post.observers_that_ran }
|
104
|
+
|
105
|
+
before do
|
106
|
+
persisted_post.destroy
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'should not run before_create' do
|
110
|
+
observers.should_not include(:before_create)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'should not run after_create' do
|
114
|
+
observers.should_not include(:after_create)
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should not run before_update' do
|
118
|
+
observers.should_not include(:before_update)
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should not run after_update' do
|
122
|
+
observers.should_not include(:after_update)
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'should not run before_save' do
|
126
|
+
observers.should_not include(:before_save)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should not run after_save' do
|
130
|
+
observers.should_not include(:after_save)
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should run before_destroy' do
|
134
|
+
observers.should include(:before_destroy)
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'should run after_destroy' do
|
138
|
+
observers.should include(:after_destroy)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,308 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Elastictastic::ParentChild do
|
4
|
+
include Elastictastic::TestHelpers
|
5
|
+
|
6
|
+
describe 'mappings' do
|
7
|
+
it 'should put _parent in mapping' do
|
8
|
+
Post.mapping['post']['_parent'].should == { 'type' => 'blog' }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'child instance' do
|
13
|
+
let(:post) { blog.posts.new }
|
14
|
+
let(:blog) do
|
15
|
+
stub_elasticsearch_create('default', 'blog')
|
16
|
+
Blog.new.tap { |blog| blog.save }
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should set parent' do
|
20
|
+
blog.posts.new.blog.should == blog
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should set parent when creating via class method' do
|
24
|
+
blog.posts.from_hash('title' => 'hey').blog.should == blog
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'discrete persistence' do
|
28
|
+
it 'should pass parent param on create' do
|
29
|
+
stub_elasticsearch_create('default', 'post')
|
30
|
+
post.save
|
31
|
+
URI.parse(FakeWeb.last_request.path).query.should == "parent=#{blog.id}"
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should pass parent param on update' do
|
35
|
+
stub_elasticsearch_create('default', 'post')
|
36
|
+
post.save
|
37
|
+
stub_elasticsearch_update('default', 'post', post.id)
|
38
|
+
post.save
|
39
|
+
URI.parse(FakeWeb.last_request.path).query.should == "parent=#{blog.id}"
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should pass parent on delete' do
|
43
|
+
stub_elasticsearch_create('default', 'post')
|
44
|
+
post = blog.posts.new
|
45
|
+
post.save
|
46
|
+
stub_elasticsearch_destroy('default', 'post', post.id)
|
47
|
+
post.destroy
|
48
|
+
URI.parse(FakeWeb.last_request.path).query.should == "parent=#{blog.id}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'bulk persistence' do
|
53
|
+
let(:bulk_requests) do
|
54
|
+
FakeWeb.last_request.body.split("\n").map do |line|
|
55
|
+
JSON.parse(line)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
before do
|
60
|
+
stub_elasticsearch_bulk
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should pass parent param on create' do
|
64
|
+
post = blog.posts.new
|
65
|
+
Elastictastic.bulk { post.save }
|
66
|
+
bulk_requests.first.should == {
|
67
|
+
'create' => {
|
68
|
+
'_index' => 'default', '_type' => 'post', 'parent' => blog.id
|
69
|
+
}
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should pass parent param on update' do
|
74
|
+
post = blog.posts.new
|
75
|
+
post.id = '1'
|
76
|
+
post.persisted!
|
77
|
+
Elastictastic.bulk { post.save }
|
78
|
+
bulk_requests.first.should == {
|
79
|
+
'index' => {
|
80
|
+
'_index' => 'default',
|
81
|
+
'_type' => 'post',
|
82
|
+
'_id' => '1',
|
83
|
+
'parent' => blog.id
|
84
|
+
}
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should pass parent param on delete' do
|
89
|
+
post = blog.posts.new
|
90
|
+
post.id = '1'
|
91
|
+
post.persisted!
|
92
|
+
Elastictastic.bulk { post.destroy }
|
93
|
+
bulk_requests.first.should == {
|
94
|
+
'delete' => {
|
95
|
+
'_index' => 'default',
|
96
|
+
'_type' => 'post',
|
97
|
+
'_id' => '1',
|
98
|
+
'parent' => blog.id
|
99
|
+
}
|
100
|
+
}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should set index' do
|
105
|
+
stub_elasticsearch_create('my_index', 'blog')
|
106
|
+
blog = Blog.in_index('my_index').new
|
107
|
+
blog.save
|
108
|
+
post = blog.posts.new
|
109
|
+
post.index.name.should == 'my_index'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe 'collection proxies' do
|
114
|
+
let(:blog) do
|
115
|
+
stub_elasticsearch_create('my_index', 'blog')
|
116
|
+
Blog.in_index('my_index').new.tap { |blog| blog.save }
|
117
|
+
end
|
118
|
+
let(:posts) { blog.posts }
|
119
|
+
|
120
|
+
it 'should by default scope query to the parent' do
|
121
|
+
posts.params.should == { 'query' => { 'constant_score' => { 'filter' => { 'term' => { '_parent' => blog.id }}}}}
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'should filter existing query' do
|
125
|
+
posts.query { query_string(:query => 'bacon') }.params['query'].should ==
|
126
|
+
{
|
127
|
+
'filtered' => {
|
128
|
+
'query' => { 'query_string' => { 'query' => 'bacon' }},
|
129
|
+
'filter' => { 'term' => { '_parent' => blog.id }}
|
130
|
+
}
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should retain other parts of scope' do
|
135
|
+
scope = posts.size(10)
|
136
|
+
scope.params['size'].should == 10
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should search correct index' do
|
140
|
+
stub_elasticsearch_scan('my_index', 'post', 100, { '_id' => '1' })
|
141
|
+
posts.to_a.first.id.should == '1'
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'should set routing to parent ID on get' do
|
145
|
+
stub_elasticsearch_get('my_index', 'post', 1)
|
146
|
+
blog.posts.find(1)
|
147
|
+
URI.parse(FakeWeb.last_request.path).query.should == "routing=#{blog.id}"
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'should set routing to parent ID on multiget' do
|
151
|
+
stub_elasticsearch_mget('my_index', 'post')
|
152
|
+
blog.posts.find(1, 2)
|
153
|
+
JSON.parse(FakeWeb.last_request.body).should == {
|
154
|
+
'docs' => [
|
155
|
+
{ '_id' => 1, 'routing' => blog.id },
|
156
|
+
{ '_id' => 2, 'routing' => blog.id }
|
157
|
+
]
|
158
|
+
}
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'should save transient instances when parent is saved' do
|
162
|
+
post = posts.new
|
163
|
+
stub_elasticsearch_update('my_index', 'blog', blog.id)
|
164
|
+
stub_elasticsearch_create('my_index', 'post')
|
165
|
+
blog.save
|
166
|
+
post.should be_persisted
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'should not attempt to save transient instances that are pending for save in a bulk block' do
|
170
|
+
stub_elasticsearch_bulk(
|
171
|
+
{ 'create' => { '_index' => 'my_index', '_type' => 'post', '_id' => '123', '_version' => 1, 'ok' => true }},
|
172
|
+
{ 'index' => { '_index' => 'my_index', '_type' => 'blog', '_id' => blog.id, '_version' => 2, 'ok' => true }}
|
173
|
+
)
|
174
|
+
post = blog.posts.new
|
175
|
+
Elastictastic.bulk do
|
176
|
+
post.save
|
177
|
+
blog.save
|
178
|
+
end
|
179
|
+
request_statements =
|
180
|
+
FakeWeb.last_request.body.each_line.map { |line| JSON.parse(line) }
|
181
|
+
request_statements.length.should == 4
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'should not save transient instances again' do
|
185
|
+
post = posts.new
|
186
|
+
stub_elasticsearch_update('my_index', 'blog', blog.id)
|
187
|
+
stub_elasticsearch_create('my_index', 'post')
|
188
|
+
blog.save
|
189
|
+
FakeWeb.clean_registry
|
190
|
+
stub_elasticsearch_update('my_index', 'blog', blog.id)
|
191
|
+
expect { blog.save }.to_not raise_error
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'should populate parent when finding one' do
|
195
|
+
stub_elasticsearch_get('my_index', 'post', '1')
|
196
|
+
blog.posts.find('1').blog.should == blog
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should populate parent when finding many' do
|
200
|
+
stub_elasticsearch_mget('my_index', 'post', '1', '2')
|
201
|
+
blog.posts.find('1', '2').each do |post|
|
202
|
+
post.blog.should == blog
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'should populate parent when retrieving first' do
|
207
|
+
stub_elasticsearch_search(
|
208
|
+
'my_index', 'post',
|
209
|
+
'total' => 1,
|
210
|
+
'hits' => { 'hits' => [{ '_id' => '2', 'index' => 'my_index', '_type' => 'post' }]}
|
211
|
+
)
|
212
|
+
blog.posts.first.blog.id.should == blog.id
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'should populate parent when iterating over cursor' do
|
216
|
+
stub_elasticsearch_scan(
|
217
|
+
'my_index', 'post', 100,
|
218
|
+
'_id' => '1'
|
219
|
+
)
|
220
|
+
blog.posts.to_a.first.blog.should == blog
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'should populate parent when paginating' do
|
224
|
+
stub_elasticsearch_search(
|
225
|
+
'my_index', 'post',
|
226
|
+
'hits' => {
|
227
|
+
'total' => 1,
|
228
|
+
'hits' => [{ '_id' => '1', '_type' => 'post', '_index' => 'my_index' }]
|
229
|
+
}
|
230
|
+
)
|
231
|
+
blog.posts.from(0).to_a.first.blog.should == blog
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'should iterate over transient instances along with retrieved results' do
|
235
|
+
stub_elasticsearch_scan('my_index', 'post', 100, '_id' => '1')
|
236
|
+
post = blog.posts.new
|
237
|
+
posts = blog.posts.to_a
|
238
|
+
posts[0].id.should == '1'
|
239
|
+
posts[1].should == post
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'should return transient instance as #first if no persisted results' do
|
243
|
+
stub_elasticsearch_search(
|
244
|
+
'my_index', 'post', 'hits' => { 'total' => 0, 'hits' => [] })
|
245
|
+
post = blog.posts.new
|
246
|
+
blog.posts.first.should == post
|
247
|
+
end
|
248
|
+
|
249
|
+
describe '#<<' do
|
250
|
+
let(:post) { Post.new }
|
251
|
+
|
252
|
+
before do
|
253
|
+
blog.posts << post
|
254
|
+
end
|
255
|
+
|
256
|
+
it 'should set parent of child object' do
|
257
|
+
post.blog.should == blog
|
258
|
+
end
|
259
|
+
|
260
|
+
it 'should not allow setting of a different parent' do
|
261
|
+
blog2 = Blog.new
|
262
|
+
expect { blog2.posts << post }.to raise_error(
|
263
|
+
Elastictastic::IllegalModificationError)
|
264
|
+
end
|
265
|
+
|
266
|
+
it 'should not allow setting of parent on already-persisted object' do
|
267
|
+
post = Post.new
|
268
|
+
post.persisted!
|
269
|
+
expect { blog.posts << post }.to raise_error(
|
270
|
+
Elastictastic::IllegalModificationError)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
describe 'searching children directly' do
|
276
|
+
before do
|
277
|
+
stub_elasticsearch_search(
|
278
|
+
'default', 'post',
|
279
|
+
'hits' => {
|
280
|
+
'hits' => [
|
281
|
+
{
|
282
|
+
'_id' => '1', '_index' => 'default', '_type' => 'post',
|
283
|
+
'_source' => { 'title' => 'hey' },
|
284
|
+
'fields' => { '_parent' => '3' }
|
285
|
+
}
|
286
|
+
]
|
287
|
+
}
|
288
|
+
)
|
289
|
+
end
|
290
|
+
|
291
|
+
let(:post) { Post.first }
|
292
|
+
|
293
|
+
it 'should provide access to parent' do
|
294
|
+
stub_elasticsearch_get('default', 'blog', '3')
|
295
|
+
post.blog.id.should == '3'
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'should populate other fields from source' do
|
299
|
+
post.title.should == 'hey'
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'should save post without dereferencing parent' do
|
303
|
+
stub_elasticsearch_update('default', 'post', post.id)
|
304
|
+
post.save
|
305
|
+
URI.parse(FakeWeb.last_request.path).query.should == 'parent=3'
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Elastictastic::Properties do
|
4
|
+
describe '::mapping' do
|
5
|
+
let(:mapping) { Post.mapping }
|
6
|
+
let(:properties) { mapping['post']['properties'] }
|
7
|
+
|
8
|
+
it 'should set basic field' do
|
9
|
+
properties.should have_key('title')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should default type to string' do
|
13
|
+
properties['title']['type'].should == 'string'
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should force date format as date_time_no_millis' do
|
17
|
+
properties['published_at']['format'].should == 'date_time_no_millis'
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should accept options' do
|
21
|
+
properties['comments_count']['type'].should == 'integer'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should set up multifield' do
|
25
|
+
properties['tags'].should == {
|
26
|
+
'type' => 'multi_field',
|
27
|
+
'fields' => {
|
28
|
+
'tags' => { 'type' => 'string', 'index' => 'analyzed' },
|
29
|
+
'non_analyzed' => { 'type' => 'string', 'index' => 'not_analyzed' }
|
30
|
+
}
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should map embedded object fields' do
|
35
|
+
properties['author']['properties']['id']['type'].should == 'integer'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#elasticsearch_doc' do
|
40
|
+
let(:post) { Post.new }
|
41
|
+
let(:doc) { post.elasticsearch_doc }
|
42
|
+
|
43
|
+
it 'should return scalar properties' do
|
44
|
+
post.title = 'You know, for search.'
|
45
|
+
doc['title'].should == 'You know, for search.'
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should serialize dates to integers' do
|
49
|
+
time = Time.now
|
50
|
+
post.published_at = time
|
51
|
+
doc['published_at'].should == time.to_i * 1000 + time.usec / 1000
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should serialize an array of dates' do
|
55
|
+
time1, time2 = Time.now, Time.now + 60
|
56
|
+
post.published_at = [time1, time2]
|
57
|
+
doc['published_at'].should ==
|
58
|
+
[(time1.to_f * 1000).to_i, (time2.to_f * 1000).to_i]
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should not include unset values' do
|
62
|
+
doc.should_not have_key('title')
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should have embedded object properties' do
|
66
|
+
post.author = Author.new
|
67
|
+
post.author.name = 'Smedley Butler'
|
68
|
+
doc['author']['name'].should == 'Smedley Butler'
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should embed properties of arrays of embedded objects' do
|
72
|
+
authors = [Author.new, Author.new]
|
73
|
+
post.author = authors
|
74
|
+
authors[0].name = 'Smedley Butler'
|
75
|
+
authors[1].name = 'Harry S Truman'
|
76
|
+
doc['author'].should == [
|
77
|
+
{ 'name' => 'Smedley Butler' },
|
78
|
+
{ 'name' => 'Harry S Truman' }
|
79
|
+
]
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should ignore missing embedded docs' do
|
83
|
+
doc.should_not have_key('author')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe 'attributes' do
|
88
|
+
it 'should raise TypeError if improper type passed to embed setter' do
|
89
|
+
lambda { Post.new.author = Post.new }.should raise_error(TypeError)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|