terrestrial 0.1.0 → 0.1.1
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/.gitignore +1 -9
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/CODE_OF_CONDUCT.md +28 -0
- data/Gemfile.lock +73 -0
- data/LICENSE.txt +22 -0
- data/MissingFeatures.md +64 -0
- data/README.md +161 -16
- data/Rakefile +30 -0
- data/TODO.md +41 -0
- data/features/env.rb +60 -0
- data/features/example.feature +120 -0
- data/features/step_definitions/example_steps.rb +46 -0
- data/lib/terrestrial/abstract_record.rb +99 -0
- data/lib/terrestrial/association_loaders.rb +52 -0
- data/lib/terrestrial/collection_mutability_proxy.rb +81 -0
- data/lib/terrestrial/configurations/conventional_association_configuration.rb +186 -0
- data/lib/terrestrial/configurations/conventional_configuration.rb +302 -0
- data/lib/terrestrial/dataset.rb +49 -0
- data/lib/terrestrial/deleted_record.rb +20 -0
- data/lib/terrestrial/dirty_map.rb +42 -0
- data/lib/terrestrial/graph_loader.rb +63 -0
- data/lib/terrestrial/graph_serializer.rb +91 -0
- data/lib/terrestrial/identity_map.rb +22 -0
- data/lib/terrestrial/lazy_collection.rb +74 -0
- data/lib/terrestrial/lazy_object_proxy.rb +55 -0
- data/lib/terrestrial/many_to_many_association.rb +138 -0
- data/lib/terrestrial/many_to_one_association.rb +66 -0
- data/lib/terrestrial/mapper_facade.rb +137 -0
- data/lib/terrestrial/one_to_many_association.rb +66 -0
- data/lib/terrestrial/public_conveniencies.rb +139 -0
- data/lib/terrestrial/query_order.rb +32 -0
- data/lib/terrestrial/relation_mapping.rb +50 -0
- data/lib/terrestrial/serializer.rb +18 -0
- data/lib/terrestrial/short_inspection_string.rb +18 -0
- data/lib/terrestrial/struct_factory.rb +17 -0
- data/lib/terrestrial/subset_queries_proxy.rb +11 -0
- data/lib/terrestrial/upserted_record.rb +15 -0
- data/lib/terrestrial/version.rb +1 -1
- data/lib/terrestrial.rb +5 -2
- data/sequel_mapper.gemspec +31 -0
- data/spec/config_override_spec.rb +193 -0
- data/spec/custom_serializers_spec.rb +49 -0
- data/spec/deletion_spec.rb +101 -0
- data/spec/graph_persistence_spec.rb +313 -0
- data/spec/graph_traversal_spec.rb +121 -0
- data/spec/new_graph_persistence_spec.rb +71 -0
- data/spec/object_identity_spec.rb +70 -0
- data/spec/ordered_association_spec.rb +51 -0
- data/spec/persistence_efficiency_spec.rb +224 -0
- data/spec/predefined_queries_spec.rb +62 -0
- data/spec/proxying_spec.rb +88 -0
- data/spec/querying_spec.rb +48 -0
- data/spec/readme_examples_spec.rb +35 -0
- data/spec/sequel_mapper/abstract_record_spec.rb +244 -0
- data/spec/sequel_mapper/collection_mutability_proxy_spec.rb +135 -0
- data/spec/sequel_mapper/deleted_record_spec.rb +59 -0
- data/spec/sequel_mapper/dirty_map_spec.rb +214 -0
- data/spec/sequel_mapper/lazy_collection_spec.rb +119 -0
- data/spec/sequel_mapper/lazy_object_proxy_spec.rb +140 -0
- data/spec/sequel_mapper/public_conveniencies_spec.rb +58 -0
- data/spec/sequel_mapper/upserted_record_spec.rb +59 -0
- data/spec/spec_helper.rb +36 -0
- data/spec/support/blog_schema.rb +38 -0
- data/spec/support/have_persisted_matcher.rb +19 -0
- data/spec/support/mapper_setup.rb +221 -0
- data/spec/support/mock_sequel.rb +193 -0
- data/spec/support/object_graph_setup.rb +139 -0
- data/spec/support/seed_data_setup.rb +165 -0
- data/spec/support/sequel_persistence_setup.rb +19 -0
- data/spec/support/sequel_test_support.rb +166 -0
- metadata +207 -13
- data/.travis.yml +0 -4
- data/bin/console +0 -14
- data/bin/setup +0 -7
- data/terrestrial.gemspec +0 -23
@@ -0,0 +1,313 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "support/have_persisted_matcher"
|
4
|
+
require "support/mapper_setup"
|
5
|
+
require "support/sequel_persistence_setup"
|
6
|
+
require "support/seed_data_setup"
|
7
|
+
require "terrestrial"
|
8
|
+
|
9
|
+
RSpec.describe "Graph persistence" do
|
10
|
+
include_context "mapper setup"
|
11
|
+
include_context "sequel persistence setup"
|
12
|
+
include_context "seed data setup"
|
13
|
+
|
14
|
+
subject(:mapper) { mappers.fetch(:users) }
|
15
|
+
|
16
|
+
let(:user) {
|
17
|
+
mapper.where(id: "users/1").first
|
18
|
+
}
|
19
|
+
|
20
|
+
context "without associations" do
|
21
|
+
let(:modified_email) { "bestie+modified@gmail.com" }
|
22
|
+
|
23
|
+
it "saves the root object" do
|
24
|
+
user.email = modified_email
|
25
|
+
mapper.save(user)
|
26
|
+
|
27
|
+
expect(datastore).to have_persisted(
|
28
|
+
:users,
|
29
|
+
hash_including(
|
30
|
+
id: "users/1",
|
31
|
+
email: modified_email,
|
32
|
+
)
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "doesn't send associated objects to the database as columns" do
|
37
|
+
user.email = modified_email
|
38
|
+
mapper.save(user)
|
39
|
+
|
40
|
+
expect(datastore).not_to have_persisted(
|
41
|
+
:users,
|
42
|
+
hash_including(
|
43
|
+
posts: anything,
|
44
|
+
)
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
# TODO move to a dirty tracking spec?
|
49
|
+
context "when mutating entity fields in place" do
|
50
|
+
it "saves the object" do
|
51
|
+
user.email << "MUTATED"
|
52
|
+
|
53
|
+
mapper.save(user)
|
54
|
+
|
55
|
+
expect(datastore).to have_persisted(
|
56
|
+
:users,
|
57
|
+
hash_including(
|
58
|
+
id: "users/1",
|
59
|
+
email: /MUTATED$/,
|
60
|
+
)
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "modify shallow has many associated object" do
|
67
|
+
let(:post) { user.posts.first }
|
68
|
+
let(:modified_post_body) { "modified ur body" }
|
69
|
+
|
70
|
+
it "saves the associated object" do
|
71
|
+
post.body = modified_post_body
|
72
|
+
mapper.save(user)
|
73
|
+
|
74
|
+
expect(datastore).to have_persisted(
|
75
|
+
:posts,
|
76
|
+
hash_including(
|
77
|
+
id: post.id,
|
78
|
+
subject: post.subject,
|
79
|
+
author_id: user.id,
|
80
|
+
body: modified_post_body,
|
81
|
+
)
|
82
|
+
)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "modify deeply nested has many associated object" do
|
87
|
+
let(:comment) {
|
88
|
+
user.posts.first.comments.first
|
89
|
+
}
|
90
|
+
|
91
|
+
let(:modified_comment_body) { "body moving, body moving" }
|
92
|
+
|
93
|
+
it "saves the associated object" do
|
94
|
+
comment.body = modified_comment_body
|
95
|
+
mapper.save(user)
|
96
|
+
|
97
|
+
expect(datastore).to have_persisted(
|
98
|
+
:comments,
|
99
|
+
hash_including(
|
100
|
+
{
|
101
|
+
id: "comments/1",
|
102
|
+
post_id: "posts/1",
|
103
|
+
commenter_id: "users/1",
|
104
|
+
body: modified_comment_body,
|
105
|
+
}
|
106
|
+
)
|
107
|
+
)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context "add a node to a has many association" do
|
112
|
+
let(:new_post_attrs) {
|
113
|
+
{
|
114
|
+
id: "posts/neu",
|
115
|
+
subject: "I am new",
|
116
|
+
body: "new body",
|
117
|
+
comments: [],
|
118
|
+
categories: [],
|
119
|
+
created_at: Time.now,
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
let(:new_post) {
|
124
|
+
Post.new(new_post_attrs)
|
125
|
+
}
|
126
|
+
|
127
|
+
it "adds the object to the graph" do
|
128
|
+
user.posts.push(new_post)
|
129
|
+
|
130
|
+
expect(user.posts).to include(new_post)
|
131
|
+
end
|
132
|
+
|
133
|
+
it "persists the object" do
|
134
|
+
user.posts.push(new_post)
|
135
|
+
|
136
|
+
mapper.save(user)
|
137
|
+
|
138
|
+
expect(datastore).to have_persisted(
|
139
|
+
:posts,
|
140
|
+
hash_including(
|
141
|
+
id: "posts/neu",
|
142
|
+
author_id: user.id,
|
143
|
+
subject: "I am new",
|
144
|
+
)
|
145
|
+
)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context "delete an object from a has many association" do
|
150
|
+
let(:post) { user.posts.first }
|
151
|
+
|
152
|
+
it "delete the object from the graph" do
|
153
|
+
user.posts.delete(post)
|
154
|
+
|
155
|
+
expect(user.posts.map(&:id)).not_to include(post.id)
|
156
|
+
end
|
157
|
+
|
158
|
+
it "delete the object from the datastore on save" do
|
159
|
+
user.posts.delete(post)
|
160
|
+
mapper.save(user)
|
161
|
+
|
162
|
+
expect(datastore).not_to have_persisted(
|
163
|
+
:posts,
|
164
|
+
hash_including(
|
165
|
+
id: post.id,
|
166
|
+
)
|
167
|
+
)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
context "modify a many to many relationship" do
|
172
|
+
let(:post) { user.posts.first }
|
173
|
+
|
174
|
+
context "delete a node" do
|
175
|
+
it "mutates the graph" do
|
176
|
+
category = post.categories.first
|
177
|
+
post.categories.delete(category)
|
178
|
+
|
179
|
+
expect(post.categories.map(&:id)).not_to include(category.id)
|
180
|
+
end
|
181
|
+
|
182
|
+
it "deletes the 'join table' record" do
|
183
|
+
category = post.categories.first
|
184
|
+
post.categories.delete(category)
|
185
|
+
mapper.save(user)
|
186
|
+
|
187
|
+
expect(datastore).not_to have_persisted(
|
188
|
+
:categories_to_posts,
|
189
|
+
{
|
190
|
+
post_id: post.id,
|
191
|
+
category_id: category.id,
|
192
|
+
}
|
193
|
+
)
|
194
|
+
end
|
195
|
+
|
196
|
+
it "does not delete the object" do
|
197
|
+
category = post.categories.first
|
198
|
+
post.categories.delete(category)
|
199
|
+
mapper.save(user)
|
200
|
+
|
201
|
+
expect(datastore).to have_persisted(
|
202
|
+
:categories,
|
203
|
+
hash_including(
|
204
|
+
id: category.id,
|
205
|
+
)
|
206
|
+
)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context "add a node" do
|
211
|
+
let(:post_with_one_category) { user.posts.to_a.last }
|
212
|
+
let(:new_category) { user.posts.first.categories.to_a.first }
|
213
|
+
|
214
|
+
it "mutates the graph" do
|
215
|
+
post_with_one_category.categories.push(new_category)
|
216
|
+
|
217
|
+
expect(post_with_one_category.categories.map(&:id))
|
218
|
+
.to match_array(["categories/1", "categories/2"])
|
219
|
+
end
|
220
|
+
|
221
|
+
it "persists the change" do
|
222
|
+
post_with_one_category.categories.push(new_category)
|
223
|
+
mapper.save(user)
|
224
|
+
|
225
|
+
expect(datastore).to have_persisted(
|
226
|
+
:categories_to_posts,
|
227
|
+
{
|
228
|
+
post_id: post_with_one_category.id,
|
229
|
+
category_id: new_category.id,
|
230
|
+
}
|
231
|
+
)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
context "modify a node" do
|
236
|
+
let(:category) { user.posts.first.categories.first }
|
237
|
+
let(:modified_category_name) { "modified category" }
|
238
|
+
|
239
|
+
it "mutates the graph" do
|
240
|
+
category.name = modified_category_name
|
241
|
+
|
242
|
+
expect(post.categories.first.name)
|
243
|
+
.to eq(modified_category_name)
|
244
|
+
end
|
245
|
+
|
246
|
+
it "persists the change" do
|
247
|
+
category.name = modified_category_name
|
248
|
+
mapper.save(user)
|
249
|
+
|
250
|
+
expect(datastore).to have_persisted(
|
251
|
+
:categories,
|
252
|
+
{
|
253
|
+
id: category.id,
|
254
|
+
name: modified_category_name,
|
255
|
+
}
|
256
|
+
)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
context "node loaded as root has undefined one to many association" do
|
261
|
+
let(:post_mapper) { mappers[:posts] }
|
262
|
+
let(:post) { post_mapper.where(id: "posts/1").first }
|
263
|
+
|
264
|
+
it "persists the changes to the root node" do
|
265
|
+
post.body = "modified body"
|
266
|
+
|
267
|
+
post_mapper.save(post)
|
268
|
+
|
269
|
+
expect(datastore).to have_persisted(
|
270
|
+
:posts,
|
271
|
+
hash_including(
|
272
|
+
id: "posts/1",
|
273
|
+
body: "modified body",
|
274
|
+
)
|
275
|
+
)
|
276
|
+
end
|
277
|
+
|
278
|
+
it "does not overwrite unused foreign key" do
|
279
|
+
post.body = "modified body"
|
280
|
+
|
281
|
+
post_mapper.save(post)
|
282
|
+
|
283
|
+
expect(datastore).to have_persisted(
|
284
|
+
:posts,
|
285
|
+
hash_including(
|
286
|
+
id: "posts/1",
|
287
|
+
author_id: "users/1",
|
288
|
+
)
|
289
|
+
)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
context "when a save operation fails (some object is not persistable)" do
|
295
|
+
let(:unpersistable_object) { ->() { } }
|
296
|
+
|
297
|
+
it "rolls back the transaction" do
|
298
|
+
pre_change = datastore[:users].to_a.map(&:to_a).sort
|
299
|
+
|
300
|
+
begin
|
301
|
+
user.first_name = "this will be rolled back"
|
302
|
+
user.posts.first.subject = unpersistable_object
|
303
|
+
|
304
|
+
mapper.save(user)
|
305
|
+
rescue Sequel::Error
|
306
|
+
end
|
307
|
+
|
308
|
+
post_change = datastore[:users].to_a.map(&:to_a).sort
|
309
|
+
|
310
|
+
expect(pre_change).to eq(post_change)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "support/mapper_setup"
|
4
|
+
require "support/sequel_persistence_setup"
|
5
|
+
require "support/seed_data_setup"
|
6
|
+
require "terrestrial"
|
7
|
+
|
8
|
+
RSpec.describe "Graph traversal" do
|
9
|
+
include_context "mapper setup"
|
10
|
+
include_context "sequel persistence setup"
|
11
|
+
include_context "seed data setup"
|
12
|
+
|
13
|
+
describe "associations" do
|
14
|
+
subject(:mapper) { user_mapper }
|
15
|
+
|
16
|
+
let(:user_query) {
|
17
|
+
mapper.where(id: "users/1")
|
18
|
+
}
|
19
|
+
|
20
|
+
let(:user) { user_query.first }
|
21
|
+
|
22
|
+
it "finds data via the storage adapter" do
|
23
|
+
expect(user_query.count).to eq(1)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "maps the raw data from the store into domain objects" do
|
27
|
+
expect(user_query.first.id).to eq("users/1")
|
28
|
+
expect(user_query.first.first_name).to eq("Hansel")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "handles has_many associations" do
|
32
|
+
post = user.posts.first
|
33
|
+
|
34
|
+
expect(post.subject).to eq("Biscuits")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "handles nested has_many associations" do
|
38
|
+
expect(
|
39
|
+
user
|
40
|
+
.posts.first
|
41
|
+
.comments.first
|
42
|
+
.body
|
43
|
+
).to eq("oh noes")
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "lazy loading" do
|
47
|
+
let(:post_factory) { double(:post_factory, call: nil) }
|
48
|
+
|
49
|
+
it "loads has many associations lazily" do
|
50
|
+
posts = user_query.first.posts
|
51
|
+
|
52
|
+
expect(post_factory).not_to have_received(:call)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it "maps belongs to associations" do
|
57
|
+
post = user.posts.first
|
58
|
+
comment = post.comments.first
|
59
|
+
|
60
|
+
expect(comment.commenter.id).to eq("users/1")
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "identity map" do
|
64
|
+
it "always returns (a proxy of) the same object for a given id" do
|
65
|
+
post = user.posts.first
|
66
|
+
comment = post.comments.first
|
67
|
+
|
68
|
+
expect(comment.commenter.__getobj__)
|
69
|
+
.to be(user)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it "maps deeply nested belongs to associations" do
|
74
|
+
expect(user_query.first.posts.first.comments.first.commenter.id)
|
75
|
+
.to eq("users/1")
|
76
|
+
end
|
77
|
+
|
78
|
+
it "maps has many to many associations as has many through" do
|
79
|
+
expect(user_query.first.posts.first.categories.map(&:id))
|
80
|
+
.to match_array(["categories/1", "categories/2"])
|
81
|
+
|
82
|
+
expect(user_query.first.posts.first.categories.to_a.last.posts.map(&:id))
|
83
|
+
.to match_array(["posts/1", "posts/2", "posts/3"])
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "eager_loading" do
|
87
|
+
it "returns the expected objects" do
|
88
|
+
expect(
|
89
|
+
user_query
|
90
|
+
.eager_load(:posts => { :categories => { :posts => [] }})
|
91
|
+
.flat_map(&:posts)
|
92
|
+
.flat_map(&:categories)
|
93
|
+
.map(&:posts)
|
94
|
+
.map { |collection| collection.map(&:id) }
|
95
|
+
).to eq([["posts/1"]] + [["posts/1", "posts/2", "posts/3"]] * 2)
|
96
|
+
end
|
97
|
+
|
98
|
+
context "when traversing beyond the eager loaded data" do
|
99
|
+
it "returns the expected objects" do
|
100
|
+
expect(
|
101
|
+
user_query
|
102
|
+
.eager_load(:posts => { :categories => { :posts => [] }})
|
103
|
+
.flat_map(&:posts)
|
104
|
+
.flat_map(&:categories)
|
105
|
+
.flat_map(&:posts)
|
106
|
+
.flat_map(&:categories)
|
107
|
+
.flat_map(&:posts)
|
108
|
+
.flat_map(&:categories)
|
109
|
+
.uniq
|
110
|
+
.map(&:id)
|
111
|
+
).to eq([
|
112
|
+
"categories/1",
|
113
|
+
"categories/2",
|
114
|
+
"categories/3",
|
115
|
+
"categories/4",
|
116
|
+
])
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "support/mapper_setup"
|
3
|
+
require "support/sequel_persistence_setup"
|
4
|
+
require "support/have_persisted_matcher"
|
5
|
+
|
6
|
+
RSpec.describe "Persist a new graph in empty datastore" do
|
7
|
+
include_context "mapper setup"
|
8
|
+
include_context "sequel persistence setup"
|
9
|
+
|
10
|
+
context "given a graph of new objects" do
|
11
|
+
it "persists the root node" do
|
12
|
+
user_mapper.save(hansel)
|
13
|
+
|
14
|
+
expect(datastore).to have_persisted(:users, {
|
15
|
+
id: hansel.id,
|
16
|
+
first_name: hansel.first_name,
|
17
|
+
last_name: hansel.last_name,
|
18
|
+
email: hansel.email,
|
19
|
+
})
|
20
|
+
end
|
21
|
+
|
22
|
+
it "persists one to many related nodes 1 level deep" do
|
23
|
+
user_mapper.save(hansel)
|
24
|
+
|
25
|
+
expect(datastore).to have_persisted(:posts, hash_including(
|
26
|
+
id: "posts/1",
|
27
|
+
subject: "Biscuits",
|
28
|
+
body: "I like them",
|
29
|
+
author_id: "users/1",
|
30
|
+
))
|
31
|
+
|
32
|
+
expect(datastore).to have_persisted(:posts, hash_including(
|
33
|
+
id: "posts/2",
|
34
|
+
subject: "Sleeping",
|
35
|
+
body: "I do it three times purrr day",
|
36
|
+
author_id: "users/1",
|
37
|
+
))
|
38
|
+
end
|
39
|
+
|
40
|
+
context "deep node with two foreign keys" do
|
41
|
+
it "persists the node with both foreign keys" do
|
42
|
+
user_mapper.save(hansel)
|
43
|
+
|
44
|
+
expect(datastore).to have_persisted(:comments, {
|
45
|
+
id: "comments/1",
|
46
|
+
body: "oh noes",
|
47
|
+
post_id: "posts/1",
|
48
|
+
commenter_id: "users/1",
|
49
|
+
})
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "persists many to many related nodes" do
|
54
|
+
user_mapper.save(hansel)
|
55
|
+
|
56
|
+
expect(datastore).to have_persisted(:categories, {
|
57
|
+
id: "categories/1",
|
58
|
+
name: "Cat biscuits",
|
59
|
+
})
|
60
|
+
end
|
61
|
+
|
62
|
+
it "persists a 'join table' to faciliate many to many" do
|
63
|
+
user_mapper.save(hansel)
|
64
|
+
|
65
|
+
expect(datastore).to have_persisted(:categories_to_posts, {
|
66
|
+
category_id: "categories/1",
|
67
|
+
post_id: "posts/1",
|
68
|
+
})
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "support/mapper_setup"
|
4
|
+
require "support/sequel_persistence_setup"
|
5
|
+
require "support/seed_data_setup"
|
6
|
+
require "terrestrial"
|
7
|
+
|
8
|
+
RSpec.describe "Object identity" do
|
9
|
+
include_context "mapper setup"
|
10
|
+
include_context "sequel persistence setup"
|
11
|
+
include_context "seed data setup"
|
12
|
+
|
13
|
+
subject(:mapper) { mappers.fetch(:users) }
|
14
|
+
|
15
|
+
let(:user) { mapper.where(id: "users/1").first }
|
16
|
+
let(:post) { user.posts.first }
|
17
|
+
|
18
|
+
context "when using arbitrary where query" do
|
19
|
+
it "returns the same object for a row's primary key" do
|
20
|
+
expect(
|
21
|
+
user.posts.where(id: post.id).first
|
22
|
+
).to be(post)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "when traversing deep into the graph" do
|
27
|
+
context "via has many through" do
|
28
|
+
it "returns the same object for a row's primary key" do
|
29
|
+
expect(
|
30
|
+
user.posts.first.categories.first.posts
|
31
|
+
.find { |cat_post| cat_post.id == post.id }
|
32
|
+
).to be(post)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "via a belongs to" do
|
37
|
+
it "returns the same object for a row's primary once loaded" do
|
38
|
+
# TODO: Add another method to avoid using #__getobj__
|
39
|
+
expect(
|
40
|
+
user.posts.first.comments
|
41
|
+
.find { |comment| comment.commenter.id == user.id }
|
42
|
+
.commenter
|
43
|
+
.__getobj__
|
44
|
+
).to be(user)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "when eager loading" do
|
49
|
+
let(:user_query) { mapper.where(id: "users/1") }
|
50
|
+
|
51
|
+
let(:eager_category) {
|
52
|
+
user_query
|
53
|
+
.eager_load(:posts => { :categories => { :posts => [] }})
|
54
|
+
.first
|
55
|
+
.posts
|
56
|
+
.first
|
57
|
+
.categories
|
58
|
+
.first
|
59
|
+
}
|
60
|
+
|
61
|
+
it "returns the same object for a row's primary once loaded" do
|
62
|
+
expect(
|
63
|
+
eager_category
|
64
|
+
.posts
|
65
|
+
.find { |cat_post| cat_post.id == post.id }
|
66
|
+
).to be(post)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "support/mapper_setup"
|
4
|
+
require "support/sequel_persistence_setup"
|
5
|
+
require "support/seed_data_setup"
|
6
|
+
require "terrestrial"
|
7
|
+
require "terrestrial/configurations/conventional_configuration"
|
8
|
+
|
9
|
+
RSpec.describe "Ordered associations" do
|
10
|
+
include_context "mapper setup"
|
11
|
+
include_context "sequel persistence setup"
|
12
|
+
include_context "seed data setup"
|
13
|
+
|
14
|
+
context "one to many association ordered by `created_at DESC`" do
|
15
|
+
let(:posts) { user_mapper.first.posts }
|
16
|
+
|
17
|
+
before do
|
18
|
+
configs.fetch(:users).fetch(:associations).fetch(:posts).merge!(
|
19
|
+
order: Terrestrial::QueryOrder.new(
|
20
|
+
fields: [:created_at],
|
21
|
+
direction: "DESC",
|
22
|
+
)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "enumerates the objects in order specified in the config" do
|
27
|
+
expect(posts.map(&:id)).to eq(
|
28
|
+
posts.to_a.sort_by(&:created_at).reverse.map(&:id)
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "many to many associatin ordered by reverse alphabetical name" do
|
34
|
+
before do
|
35
|
+
configs.fetch(:posts).fetch(:associations).fetch(:categories).merge!(
|
36
|
+
order: Terrestrial::QueryOrder.new(
|
37
|
+
fields: [:name],
|
38
|
+
direction: "DESC",
|
39
|
+
)
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
let(:categories) { user_mapper.first.posts.first.categories }
|
44
|
+
|
45
|
+
it "enumerates the objects in order specified in the config" do
|
46
|
+
expect(categories.map(&:id)).to eq(
|
47
|
+
categories.to_a.sort_by(&:name).reverse.map(&:id)
|
48
|
+
)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|