terrestrial 0.1.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Gemfile.lock +29 -24
- data/README.md +35 -17
- data/Rakefile +4 -9
- data/TODO.md +25 -18
- data/bin/test +31 -0
- data/docs/domain_object_contract.md +50 -0
- data/features/env.rb +4 -6
- data/features/example.feature +28 -28
- data/features/step_definitions/example_steps.rb +2 -2
- data/lib/terrestrial/adapters/memory_adapter.rb +241 -0
- data/lib/terrestrial/collection_mutability_proxy.rb +7 -2
- data/lib/terrestrial/dirty_map.rb +5 -0
- data/lib/terrestrial/error.rb +69 -0
- data/lib/terrestrial/graph_loader.rb +58 -35
- data/lib/terrestrial/graph_serializer.rb +37 -30
- data/lib/terrestrial/inspection_string.rb +19 -0
- data/lib/terrestrial/lazy_collection.rb +2 -2
- data/lib/terrestrial/lazy_object_proxy.rb +1 -1
- data/lib/terrestrial/many_to_one_association.rb +17 -11
- data/lib/terrestrial/public_conveniencies.rb +125 -95
- data/lib/terrestrial/relation_mapping.rb +30 -0
- data/lib/terrestrial/{mapper_facade.rb → relational_store.rb} +11 -1
- data/lib/terrestrial/version.rb +1 -1
- data/spec/config_override_spec.rb +10 -14
- data/spec/custom_serializers_spec.rb +4 -6
- data/spec/deletion_spec.rb +12 -14
- data/spec/error_handling/factory_error_handling_spec.rb +61 -0
- data/spec/error_handling/serialization_error_spec.rb +50 -0
- data/spec/error_handling/upsert_error_spec.rb +132 -0
- data/spec/graph_persistence_spec.rb +80 -24
- data/spec/graph_traversal_spec.rb +14 -6
- data/spec/new_graph_persistence_spec.rb +43 -9
- data/spec/object_identity_spec.rb +5 -7
- data/spec/ordered_association_spec.rb +4 -6
- data/spec/predefined_queries_spec.rb +4 -6
- data/spec/querying_spec.rb +4 -12
- data/spec/readme_examples_spec.rb +3 -6
- data/spec/{persistence_efficiency_spec.rb → sequel_query_efficiency_spec.rb} +101 -19
- data/spec/spec_helper.rb +24 -2
- data/spec/support/memory_adapter_test_support.rb +21 -0
- data/spec/support/{mapper_setup.rb → object_store_setup.rb} +5 -5
- data/spec/support/seed_data_setup.rb +3 -1
- data/spec/support/sequel_test_support.rb +58 -25
- data/spec/{sequel_mapper → terrestrial}/abstract_record_spec.rb +0 -0
- data/spec/{sequel_mapper → terrestrial}/collection_mutability_proxy_spec.rb +0 -0
- data/spec/{sequel_mapper → terrestrial}/deleted_record_spec.rb +0 -0
- data/spec/{sequel_mapper → terrestrial}/dirty_map_spec.rb +38 -6
- data/spec/{sequel_mapper → terrestrial}/lazy_collection_spec.rb +2 -3
- data/spec/{sequel_mapper → terrestrial}/lazy_object_proxy_spec.rb +0 -0
- data/spec/{sequel_mapper → terrestrial}/public_conveniencies_spec.rb +12 -7
- data/spec/{sequel_mapper → terrestrial}/upserted_record_spec.rb +0 -0
- data/{sequel_mapper.gemspec → terrestrial.gemspec} +3 -3
- metadata +47 -39
- data/lib/terrestrial/short_inspection_string.rb +0 -18
- data/spec/proxying_spec.rb +0 -88
- data/spec/support/mock_sequel.rb +0 -193
- data/spec/support/sequel_persistence_setup.rb +0 -19
@@ -1,28 +1,26 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
require "support/have_persisted_matcher"
|
4
|
-
require "support/
|
5
|
-
require "support/sequel_persistence_setup"
|
4
|
+
require "support/object_store_setup"
|
6
5
|
require "support/seed_data_setup"
|
7
6
|
require "terrestrial"
|
8
7
|
|
9
8
|
RSpec.describe "Graph persistence" do
|
10
|
-
include_context "
|
11
|
-
include_context "sequel persistence setup"
|
9
|
+
include_context "object store setup"
|
12
10
|
include_context "seed data setup"
|
13
11
|
|
14
|
-
subject(:
|
12
|
+
subject(:user_store) { object_store.fetch(:users) }
|
15
13
|
|
16
14
|
let(:user) {
|
17
|
-
|
15
|
+
user_store.where(id: "users/1").first
|
18
16
|
}
|
19
17
|
|
20
18
|
context "without associations" do
|
21
|
-
let(:modified_email) { "
|
19
|
+
let(:modified_email) { "hasel+modified@gmail.com" }
|
22
20
|
|
23
21
|
it "saves the root object" do
|
24
22
|
user.email = modified_email
|
25
|
-
|
23
|
+
user_store.save(user)
|
26
24
|
|
27
25
|
expect(datastore).to have_persisted(
|
28
26
|
:users,
|
@@ -35,7 +33,7 @@ RSpec.describe "Graph persistence" do
|
|
35
33
|
|
36
34
|
it "doesn't send associated objects to the database as columns" do
|
37
35
|
user.email = modified_email
|
38
|
-
|
36
|
+
user_store.save(user)
|
39
37
|
|
40
38
|
expect(datastore).not_to have_persisted(
|
41
39
|
:users,
|
@@ -50,7 +48,7 @@ RSpec.describe "Graph persistence" do
|
|
50
48
|
it "saves the object" do
|
51
49
|
user.email << "MUTATED"
|
52
50
|
|
53
|
-
|
51
|
+
user_store.save(user)
|
54
52
|
|
55
53
|
expect(datastore).to have_persisted(
|
56
54
|
:users,
|
@@ -69,7 +67,7 @@ RSpec.describe "Graph persistence" do
|
|
69
67
|
|
70
68
|
it "saves the associated object" do
|
71
69
|
post.body = modified_post_body
|
72
|
-
|
70
|
+
user_store.save(user)
|
73
71
|
|
74
72
|
expect(datastore).to have_persisted(
|
75
73
|
:posts,
|
@@ -92,7 +90,7 @@ RSpec.describe "Graph persistence" do
|
|
92
90
|
|
93
91
|
it "saves the associated object" do
|
94
92
|
comment.body = modified_comment_body
|
95
|
-
|
93
|
+
user_store.save(user)
|
96
94
|
|
97
95
|
expect(datastore).to have_persisted(
|
98
96
|
:comments,
|
@@ -133,7 +131,7 @@ RSpec.describe "Graph persistence" do
|
|
133
131
|
it "persists the object" do
|
134
132
|
user.posts.push(new_post)
|
135
133
|
|
136
|
-
|
134
|
+
user_store.save(user)
|
137
135
|
|
138
136
|
expect(datastore).to have_persisted(
|
139
137
|
:posts,
|
@@ -144,6 +142,18 @@ RSpec.describe "Graph persistence" do
|
|
144
142
|
)
|
145
143
|
)
|
146
144
|
end
|
145
|
+
|
146
|
+
context "when the collection is not loaded until the new object is persisted" do
|
147
|
+
it "is consistent with the datastore" do
|
148
|
+
user.posts.push(new_post)
|
149
|
+
|
150
|
+
user_store.save(user)
|
151
|
+
|
152
|
+
expect(user.posts.to_a.map(&:id)).to eq(
|
153
|
+
["posts/1", "posts/2", "posts/neu"]
|
154
|
+
)
|
155
|
+
end
|
156
|
+
end
|
147
157
|
end
|
148
158
|
|
149
159
|
context "delete an object from a has many association" do
|
@@ -157,7 +167,7 @@ RSpec.describe "Graph persistence" do
|
|
157
167
|
|
158
168
|
it "delete the object from the datastore on save" do
|
159
169
|
user.posts.delete(post)
|
160
|
-
|
170
|
+
user_store.save(user)
|
161
171
|
|
162
172
|
expect(datastore).not_to have_persisted(
|
163
173
|
:posts,
|
@@ -182,7 +192,7 @@ RSpec.describe "Graph persistence" do
|
|
182
192
|
it "deletes the 'join table' record" do
|
183
193
|
category = post.categories.first
|
184
194
|
post.categories.delete(category)
|
185
|
-
|
195
|
+
user_store.save(user)
|
186
196
|
|
187
197
|
expect(datastore).not_to have_persisted(
|
188
198
|
:categories_to_posts,
|
@@ -196,7 +206,7 @@ RSpec.describe "Graph persistence" do
|
|
196
206
|
it "does not delete the object" do
|
197
207
|
category = post.categories.first
|
198
208
|
post.categories.delete(category)
|
199
|
-
|
209
|
+
user_store.save(user)
|
200
210
|
|
201
211
|
expect(datastore).to have_persisted(
|
202
212
|
:categories,
|
@@ -220,7 +230,7 @@ RSpec.describe "Graph persistence" do
|
|
220
230
|
|
221
231
|
it "persists the change" do
|
222
232
|
post_with_one_category.categories.push(new_category)
|
223
|
-
|
233
|
+
user_store.save(user)
|
224
234
|
|
225
235
|
expect(datastore).to have_persisted(
|
226
236
|
:categories_to_posts,
|
@@ -245,7 +255,7 @@ RSpec.describe "Graph persistence" do
|
|
245
255
|
|
246
256
|
it "persists the change" do
|
247
257
|
category.name = modified_category_name
|
248
|
-
|
258
|
+
user_store.save(user)
|
249
259
|
|
250
260
|
expect(datastore).to have_persisted(
|
251
261
|
:categories,
|
@@ -258,13 +268,13 @@ RSpec.describe "Graph persistence" do
|
|
258
268
|
end
|
259
269
|
|
260
270
|
context "node loaded as root has undefined one to many association" do
|
261
|
-
let(:
|
262
|
-
let(:post) {
|
271
|
+
let(:post_store) { object_store[:posts] }
|
272
|
+
let(:post) { post_store.where(id: "posts/1").first }
|
263
273
|
|
264
274
|
it "persists the changes to the root node" do
|
265
275
|
post.body = "modified body"
|
266
276
|
|
267
|
-
|
277
|
+
post_store.save(post)
|
268
278
|
|
269
279
|
expect(datastore).to have_persisted(
|
270
280
|
:posts,
|
@@ -278,7 +288,7 @@ RSpec.describe "Graph persistence" do
|
|
278
288
|
it "does not overwrite unused foreign key" do
|
279
289
|
post.body = "modified body"
|
280
290
|
|
281
|
-
|
291
|
+
post_store.save(post)
|
282
292
|
|
283
293
|
expect(datastore).to have_persisted(
|
284
294
|
:posts,
|
@@ -291,6 +301,52 @@ RSpec.describe "Graph persistence" do
|
|
291
301
|
end
|
292
302
|
end
|
293
303
|
|
304
|
+
context "when a many to one association is nil" do
|
305
|
+
context "when the object does not have a reference to its parent" do
|
306
|
+
it "populates that association with a nil" do
|
307
|
+
post = Post.new(
|
308
|
+
id: "posts/orphan",
|
309
|
+
subject: "Nils gonna getcha",
|
310
|
+
body: "",
|
311
|
+
created_at: Time.parse("2015-09-05T15:00:00+01:00"),
|
312
|
+
categories: [],
|
313
|
+
comments: [],
|
314
|
+
)
|
315
|
+
|
316
|
+
object_store[:posts].save(post)
|
317
|
+
|
318
|
+
expect(datastore).to have_persisted(
|
319
|
+
:posts,
|
320
|
+
hash_including(
|
321
|
+
id: "posts/orphan",
|
322
|
+
author_id: nil,
|
323
|
+
)
|
324
|
+
)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
context "when an existing partent object reference is set to nil" do
|
329
|
+
it "populates that association with a nil" do
|
330
|
+
comment = user
|
331
|
+
.posts
|
332
|
+
.flat_map(&:comments)
|
333
|
+
.detect { |c| c.id == "comments/1" }
|
334
|
+
|
335
|
+
comment.commenter = nil
|
336
|
+
|
337
|
+
user_store.save(user)
|
338
|
+
|
339
|
+
expect(datastore).to have_persisted(
|
340
|
+
:comments,
|
341
|
+
hash_including(
|
342
|
+
id: "comments/1",
|
343
|
+
commenter_id: nil,
|
344
|
+
)
|
345
|
+
)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
294
350
|
context "when a save operation fails (some object is not persistable)" do
|
295
351
|
let(:unpersistable_object) { ->() { } }
|
296
352
|
|
@@ -301,8 +357,8 @@ RSpec.describe "Graph persistence" do
|
|
301
357
|
user.first_name = "this will be rolled back"
|
302
358
|
user.posts.first.subject = unpersistable_object
|
303
359
|
|
304
|
-
|
305
|
-
rescue
|
360
|
+
user_store.save(user)
|
361
|
+
rescue Terrestrial::Error
|
306
362
|
end
|
307
363
|
|
308
364
|
post_change = datastore[:users].to_a.map(&:to_a).sort
|
@@ -1,20 +1,18 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
-
require "support/
|
4
|
-
require "support/sequel_persistence_setup"
|
3
|
+
require "support/object_store_setup"
|
5
4
|
require "support/seed_data_setup"
|
6
5
|
require "terrestrial"
|
7
6
|
|
8
7
|
RSpec.describe "Graph traversal" do
|
9
|
-
include_context "
|
10
|
-
include_context "sequel persistence setup"
|
8
|
+
include_context "object store setup"
|
11
9
|
include_context "seed data setup"
|
12
10
|
|
13
11
|
describe "associations" do
|
14
|
-
subject(:
|
12
|
+
subject(:user_store) { object_store[:users] }
|
15
13
|
|
16
14
|
let(:user_query) {
|
17
|
-
|
15
|
+
user_store.where(id: "users/1")
|
18
16
|
}
|
19
17
|
|
20
18
|
let(:user) { user_query.first }
|
@@ -43,6 +41,16 @@ RSpec.describe "Graph traversal" do
|
|
43
41
|
).to eq("oh noes")
|
44
42
|
end
|
45
43
|
|
44
|
+
context "when a many to one association foreign key is nil" do
|
45
|
+
before do
|
46
|
+
datastore[:comments].update(commenter_id: nil)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "populates that association with a nil" do
|
50
|
+
expect(user.posts.flat_map(&:comments).flat_map(&:commenter).uniq).to eq([nil])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
46
54
|
describe "lazy loading" do
|
47
55
|
let(:post_factory) { double(:post_factory, call: nil) }
|
48
56
|
|
@@ -1,15 +1,13 @@
|
|
1
1
|
require "spec_helper"
|
2
|
-
require "support/
|
3
|
-
require "support/sequel_persistence_setup"
|
2
|
+
require "support/object_store_setup"
|
4
3
|
require "support/have_persisted_matcher"
|
5
4
|
|
6
5
|
RSpec.describe "Persist a new graph in empty datastore" do
|
7
|
-
include_context "
|
8
|
-
include_context "sequel persistence setup"
|
6
|
+
include_context "object store setup"
|
9
7
|
|
10
8
|
context "given a graph of new objects" do
|
11
9
|
it "persists the root node" do
|
12
|
-
|
10
|
+
object_store[:users].save(hansel)
|
13
11
|
|
14
12
|
expect(datastore).to have_persisted(:users, {
|
15
13
|
id: hansel.id,
|
@@ -20,7 +18,7 @@ RSpec.describe "Persist a new graph in empty datastore" do
|
|
20
18
|
end
|
21
19
|
|
22
20
|
it "persists one to many related nodes 1 level deep" do
|
23
|
-
|
21
|
+
object_store[:users].save(hansel)
|
24
22
|
|
25
23
|
expect(datastore).to have_persisted(:posts, hash_including(
|
26
24
|
id: "posts/1",
|
@@ -39,7 +37,7 @@ RSpec.describe "Persist a new graph in empty datastore" do
|
|
39
37
|
|
40
38
|
context "deep node with two foreign keys" do
|
41
39
|
it "persists the node with both foreign keys" do
|
42
|
-
|
40
|
+
object_store[:users].save(hansel)
|
43
41
|
|
44
42
|
expect(datastore).to have_persisted(:comments, {
|
45
43
|
id: "comments/1",
|
@@ -51,7 +49,7 @@ RSpec.describe "Persist a new graph in empty datastore" do
|
|
51
49
|
end
|
52
50
|
|
53
51
|
it "persists many to many related nodes" do
|
54
|
-
|
52
|
+
object_store[:users].save(hansel)
|
55
53
|
|
56
54
|
expect(datastore).to have_persisted(:categories, {
|
57
55
|
id: "categories/1",
|
@@ -60,12 +58,48 @@ RSpec.describe "Persist a new graph in empty datastore" do
|
|
60
58
|
end
|
61
59
|
|
62
60
|
it "persists a 'join table' to faciliate many to many" do
|
63
|
-
|
61
|
+
object_store[:users].save(hansel)
|
64
62
|
|
65
63
|
expect(datastore).to have_persisted(:categories_to_posts, {
|
66
64
|
category_id: "categories/1",
|
67
65
|
post_id: "posts/1",
|
68
66
|
})
|
69
67
|
end
|
68
|
+
|
69
|
+
context "when saving an object a second time" do
|
70
|
+
context "when the first time fails" do
|
71
|
+
before do
|
72
|
+
error = attempt_unpersistable_save
|
73
|
+
unless error.is_a?(Terrestrial::UpsertError)
|
74
|
+
raise "Unpersistable save did not raise an error. Expected this save to fail."
|
75
|
+
end
|
76
|
+
hansel.email = "hansel@gmail.com"
|
77
|
+
end
|
78
|
+
|
79
|
+
let(:unpersistable) { ->() {} }
|
80
|
+
|
81
|
+
it "successfully saves all attributes the second time" do
|
82
|
+
object_store[:users].save(hansel)
|
83
|
+
|
84
|
+
expect(datastore).to have_persisted(
|
85
|
+
:users,
|
86
|
+
hash_including(
|
87
|
+
id: hansel.id,
|
88
|
+
email: hansel.email,
|
89
|
+
first_name: hansel.first_name,
|
90
|
+
last_name: hansel.last_name,
|
91
|
+
)
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
def attempt_unpersistable_save
|
96
|
+
email = hansel.email
|
97
|
+
hansel.email = unpersistable
|
98
|
+
object_store[:users].save(hansel)
|
99
|
+
rescue Terrestrial::UpsertError => e
|
100
|
+
e
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
70
104
|
end
|
71
105
|
end
|
@@ -1,18 +1,16 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
-
require "support/
|
4
|
-
require "support/sequel_persistence_setup"
|
3
|
+
require "support/object_store_setup"
|
5
4
|
require "support/seed_data_setup"
|
6
5
|
require "terrestrial"
|
7
6
|
|
8
7
|
RSpec.describe "Object identity" do
|
9
|
-
include_context "
|
10
|
-
include_context "sequel persistence setup"
|
8
|
+
include_context "object store setup"
|
11
9
|
include_context "seed data setup"
|
12
10
|
|
13
|
-
subject(:
|
11
|
+
subject(:user_store) { object_store.fetch(:users) }
|
14
12
|
|
15
|
-
let(:user) {
|
13
|
+
let(:user) { user_store.where(id: "users/1").first }
|
16
14
|
let(:post) { user.posts.first }
|
17
15
|
|
18
16
|
context "when using arbitrary where query" do
|
@@ -46,7 +44,7 @@ RSpec.describe "Object identity" do
|
|
46
44
|
end
|
47
45
|
|
48
46
|
context "when eager loading" do
|
49
|
-
let(:user_query) {
|
47
|
+
let(:user_query) { user_store.where(id: "users/1") }
|
50
48
|
|
51
49
|
let(:eager_category) {
|
52
50
|
user_query
|
@@ -1,18 +1,16 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
-
require "support/
|
4
|
-
require "support/sequel_persistence_setup"
|
3
|
+
require "support/object_store_setup"
|
5
4
|
require "support/seed_data_setup"
|
6
5
|
require "terrestrial"
|
7
6
|
require "terrestrial/configurations/conventional_configuration"
|
8
7
|
|
9
8
|
RSpec.describe "Ordered associations" do
|
10
|
-
include_context "
|
11
|
-
include_context "sequel persistence setup"
|
9
|
+
include_context "object store setup"
|
12
10
|
include_context "seed data setup"
|
13
11
|
|
14
12
|
context "one to many association ordered by `created_at DESC`" do
|
15
|
-
let(:posts) {
|
13
|
+
let(:posts) { object_store[:users].first.posts }
|
16
14
|
|
17
15
|
before do
|
18
16
|
configs.fetch(:users).fetch(:associations).fetch(:posts).merge!(
|
@@ -40,7 +38,7 @@ RSpec.describe "Ordered associations" do
|
|
40
38
|
)
|
41
39
|
end
|
42
40
|
|
43
|
-
let(:categories) {
|
41
|
+
let(:categories) { object_store[:users].first.posts.first.categories }
|
44
42
|
|
45
43
|
it "enumerates the objects in order specified in the config" do
|
46
44
|
expect(categories.map(&:id)).to eq(
|
@@ -1,17 +1,15 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
-
require "support/
|
4
|
-
require "support/sequel_persistence_setup"
|
3
|
+
require "support/object_store_setup"
|
5
4
|
require "support/seed_data_setup"
|
6
5
|
require "terrestrial"
|
7
6
|
require "terrestrial/configurations/conventional_configuration"
|
8
7
|
|
9
8
|
RSpec.describe "Predefined subset queries" do
|
10
|
-
include_context "
|
11
|
-
include_context "sequel persistence setup"
|
9
|
+
include_context "object store setup"
|
12
10
|
include_context "seed data setup"
|
13
11
|
|
14
|
-
subject(:users) {
|
12
|
+
subject(:users) { object_store[:users] }
|
15
13
|
|
16
14
|
context "on the top level mapper" do
|
17
15
|
context "subset is defined with a block" do
|
@@ -41,7 +39,7 @@ RSpec.describe "Predefined subset queries" do
|
|
41
39
|
configs.fetch(:posts).merge!(
|
42
40
|
subsets: {
|
43
41
|
body_contains: ->(dataset, search_string) {
|
44
|
-
dataset.where(
|
42
|
+
dataset.where(body: /#{search_string}/)
|
45
43
|
},
|
46
44
|
},
|
47
45
|
)
|