terrestrial 0.1.1 → 0.3.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.
- 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
|
)
|