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