sequel_mapper 0.0.1 → 0.0.3
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/CODE_OF_CONDUCT.md +28 -0
- data/Gemfile.lock +32 -2
- data/MissingFeatures.md +64 -0
- data/README.md +141 -72
- data/Rakefile +29 -0
- data/TODO.md +16 -11
- data/features/env.rb +57 -0
- data/features/example.feature +121 -0
- data/features/step_definitions/example_steps.rb +46 -0
- data/lib/sequel_mapper.rb +6 -2
- data/lib/sequel_mapper/abstract_record.rb +53 -0
- data/lib/sequel_mapper/association_loaders.rb +52 -0
- data/lib/sequel_mapper/collection_mutability_proxy.rb +77 -0
- data/lib/sequel_mapper/configurations/conventional_association_configuration.rb +187 -0
- data/lib/sequel_mapper/configurations/conventional_configuration.rb +269 -0
- data/lib/sequel_mapper/dataset.rb +37 -0
- data/lib/sequel_mapper/deleted_record.rb +16 -0
- data/lib/sequel_mapper/dirty_map.rb +31 -0
- data/lib/sequel_mapper/graph_loader.rb +48 -0
- data/lib/sequel_mapper/graph_serializer.rb +107 -0
- data/lib/sequel_mapper/identity_map.rb +22 -0
- data/lib/sequel_mapper/lazy_object_proxy.rb +51 -0
- data/lib/sequel_mapper/many_to_many_association.rb +181 -0
- data/lib/sequel_mapper/many_to_one_association.rb +60 -0
- data/lib/sequel_mapper/mapper_facade.rb +180 -0
- data/lib/sequel_mapper/one_to_many_association.rb +51 -0
- data/lib/sequel_mapper/public_conveniencies.rb +27 -0
- data/lib/sequel_mapper/query_order.rb +32 -0
- data/lib/sequel_mapper/queryable_lazy_dataset_loader.rb +70 -0
- data/lib/sequel_mapper/relation_mapping.rb +35 -0
- data/lib/sequel_mapper/serializer.rb +18 -0
- data/lib/sequel_mapper/short_inspection_string.rb +18 -0
- data/lib/sequel_mapper/subset_queries_proxy.rb +11 -0
- data/lib/sequel_mapper/upserted_record.rb +15 -0
- data/lib/sequel_mapper/version.rb +1 -1
- data/sequel_mapper.gemspec +3 -0
- data/spec/config_override_spec.rb +167 -0
- data/spec/custom_serializers_spec.rb +77 -0
- data/spec/deletion_spec.rb +104 -0
- data/spec/graph_persistence_spec.rb +83 -88
- data/spec/graph_traversal_spec.rb +32 -31
- data/spec/new_graph_persistence_spec.rb +69 -0
- data/spec/object_identity_spec.rb +70 -0
- data/spec/ordered_association_spec.rb +46 -16
- data/spec/persistence_efficiency_spec.rb +186 -0
- data/spec/predefined_queries_spec.rb +73 -0
- data/spec/proxying_spec.rb +25 -19
- data/spec/querying_spec.rb +24 -27
- data/spec/readme_examples_spec.rb +35 -0
- data/spec/sequel_mapper/abstract_record_spec.rb +179 -0
- data/spec/sequel_mapper/{association_proxy_spec.rb → collection_mutability_proxy_spec.rb} +6 -6
- data/spec/sequel_mapper/deleted_record_spec.rb +59 -0
- data/spec/sequel_mapper/lazy_object_proxy_spec.rb +140 -0
- data/spec/sequel_mapper/public_conveniencies_spec.rb +49 -0
- data/spec/sequel_mapper/queryable_lazy_dataset_loader_spec.rb +103 -0
- data/spec/sequel_mapper/upserted_record_spec.rb +59 -0
- data/spec/spec_helper.rb +7 -10
- data/spec/support/blog_schema.rb +29 -0
- data/spec/support/have_persisted_matcher.rb +19 -0
- data/spec/support/mapper_setup.rb +234 -0
- data/spec/support/mock_sequel.rb +0 -1
- data/spec/support/object_graph_setup.rb +106 -0
- data/spec/support/seed_data_setup.rb +122 -0
- data/spec/support/sequel_persistence_setup.rb +19 -0
- data/spec/support/sequel_test_support.rb +159 -0
- metadata +121 -15
- data/lib/sequel_mapper/association_proxy.rb +0 -54
- data/lib/sequel_mapper/belongs_to_association_proxy.rb +0 -27
- data/lib/sequel_mapper/graph.rb +0 -174
- data/lib/sequel_mapper/queryable_association_proxy.rb +0 -23
- data/spec/sequel_mapper/belongs_to_association_proxy_spec.rb +0 -65
- data/spec/support/graph_fixture.rb +0 -331
- data/spec/support/query_counter.rb +0 -29
@@ -0,0 +1,18 @@
|
|
1
|
+
module SequelMapper
|
2
|
+
class Serializer
|
3
|
+
def initialize(field_names, object)
|
4
|
+
@field_names = field_names
|
5
|
+
@object = object
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :field_names, :object
|
9
|
+
|
10
|
+
def to_h
|
11
|
+
Hash[
|
12
|
+
field_names.map { |field_name|
|
13
|
+
[field_name, object.public_send(field_name)]
|
14
|
+
}
|
15
|
+
]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module SequelMapper
|
2
|
+
module ShortInspectionString
|
3
|
+
def inspect
|
4
|
+
"\#<#{self.class.name}:#{self.object_id.<<(1).to_s(16)} " +
|
5
|
+
inspectable_properties.map { |property|
|
6
|
+
[
|
7
|
+
property,
|
8
|
+
instance_variable_get("@#{property}").inspect
|
9
|
+
].join("=")
|
10
|
+
}
|
11
|
+
.join(" ") + ">"
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspectable_properties
|
15
|
+
[]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/sequel_mapper.gemspec
CHANGED
@@ -22,7 +22,10 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.add_development_dependency "rake", "~> 10.0"
|
23
23
|
spec.add_development_dependency "pry", "~> 0.10.1"
|
24
24
|
spec.add_development_dependency "rspec", "~> 3.1"
|
25
|
+
spec.add_development_dependency "cucumber"
|
25
26
|
spec.add_development_dependency "pg", "~> 0.17.1"
|
26
27
|
|
27
28
|
spec.add_dependency "sequel", "~> 4.16"
|
29
|
+
spec.add_dependency "activesupport", "~> 4.0"
|
30
|
+
spec.add_dependency "fetchable", "~> 1.0"
|
28
31
|
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "support/mapper_setup"
|
4
|
+
require "support/sequel_persistence_setup"
|
5
|
+
require "support/seed_data_setup"
|
6
|
+
require "sequel_mapper"
|
7
|
+
|
8
|
+
require "sequel_mapper/configurations/conventional_configuration"
|
9
|
+
|
10
|
+
RSpec.describe "Configuration override" do
|
11
|
+
include_context "mapper setup"
|
12
|
+
include_context "sequel persistence setup"
|
13
|
+
include_context "seed data setup"
|
14
|
+
|
15
|
+
subject(:user_mapper) {
|
16
|
+
SequelMapper.mapper(
|
17
|
+
config: mapper_config,
|
18
|
+
name: :users,
|
19
|
+
datastore: datastore,
|
20
|
+
)
|
21
|
+
}
|
22
|
+
|
23
|
+
let(:mapper_config) {
|
24
|
+
SequelMapper::Configurations::ConventionalConfiguration.new(datastore)
|
25
|
+
.setup_mapping(:users) { |users|
|
26
|
+
users.has_many :posts, foreign_key: :author_id
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
let(:user) {
|
31
|
+
user_mapper.where(id: "users/1").first
|
32
|
+
}
|
33
|
+
|
34
|
+
context "override the root mapper factory" do
|
35
|
+
context "with a Struct class" do
|
36
|
+
before do
|
37
|
+
mapper_config.setup_mapping(:users) do |config|
|
38
|
+
config.class(user_subclass)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
let(:user_subclass) { Class.new(User) }
|
43
|
+
|
44
|
+
it "uses the class from the override" do
|
45
|
+
expect(user.class).to be(user_subclass)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "override an association" do
|
51
|
+
context "with a callable factory" do
|
52
|
+
before do
|
53
|
+
mapper_config.setup_mapping(:posts) do |config|
|
54
|
+
config.factory(override_post_factory)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
let(:post_subclass) { Class.new(Post) }
|
59
|
+
|
60
|
+
let(:override_post_factory) {
|
61
|
+
SequelMapper::StructFactory.new(post_subclass)
|
62
|
+
}
|
63
|
+
|
64
|
+
let(:posts) {
|
65
|
+
user.posts
|
66
|
+
}
|
67
|
+
|
68
|
+
it "uses the specified factory" do
|
69
|
+
expect(posts.first.class).to be(post_subclass)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "override table names" do
|
75
|
+
context "for just the top level mapping" do
|
76
|
+
before do
|
77
|
+
datastore.rename_table(:users, unconventional_table_name)
|
78
|
+
end
|
79
|
+
|
80
|
+
after do
|
81
|
+
datastore.rename_table(unconventional_table_name, :users)
|
82
|
+
end
|
83
|
+
|
84
|
+
let(:mapper_config) {
|
85
|
+
SequelMapper::Configurations::ConventionalConfiguration
|
86
|
+
.new(datastore)
|
87
|
+
.setup_mapping(:users) do |config|
|
88
|
+
config.relation_name unconventional_table_name
|
89
|
+
end
|
90
|
+
}
|
91
|
+
|
92
|
+
|
93
|
+
let(:datastore) { db_connection }
|
94
|
+
|
95
|
+
let(:unconventional_table_name) {
|
96
|
+
:users_is_called_this_weird_thing_perhaps_for_legacy_reasons
|
97
|
+
}
|
98
|
+
|
99
|
+
it "maps data from the specified relation" do
|
100
|
+
expect(
|
101
|
+
user_mapper.map(&:id)
|
102
|
+
).to eq(["users/1", "users/2", "users/3"])
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "for associated collections" do
|
107
|
+
before do
|
108
|
+
rename_all_the_tables
|
109
|
+
setup_the_strange_table_name_mappings
|
110
|
+
end
|
111
|
+
|
112
|
+
after do
|
113
|
+
undo_rename_all_the_tables
|
114
|
+
end
|
115
|
+
|
116
|
+
def rename_all_the_tables
|
117
|
+
strange_table_name_map.each do |name, new_name|
|
118
|
+
datastore.rename_table(name, new_name)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def undo_rename_all_the_tables
|
123
|
+
strange_table_name_map.each do |original_name, strange_name|
|
124
|
+
datastore.rename_table(strange_name, original_name)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def setup_the_strange_table_name_mappings
|
129
|
+
mapper_config
|
130
|
+
.setup_mapping(:users) do |config|
|
131
|
+
config.relation_name strange_table_name_map.fetch(:users)
|
132
|
+
config.has_many(:posts, foreign_key: :author_id)
|
133
|
+
end
|
134
|
+
.setup_mapping(:posts) do |config|
|
135
|
+
config.relation_name strange_table_name_map.fetch(:posts)
|
136
|
+
config.belongs_to(:author, mapping_name: :users)
|
137
|
+
config.has_many_through(:categories, through_mapping_name: strange_table_name_map.fetch(:categories_to_posts))
|
138
|
+
end
|
139
|
+
.setup_mapping(:categories) do |config|
|
140
|
+
config.relation_name strange_table_name_map.fetch(:categories)
|
141
|
+
config.has_many_through(:posts, through_mapping_name: strange_table_name_map.fetch(:categories_to_posts))
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
let(:strange_table_name_map) {
|
146
|
+
{
|
147
|
+
:users => :users_table_that_has_silly_name_perhaps_for_legacy_reasons,
|
148
|
+
:posts => :thank_you_past_self_for_this_excellent_name,
|
149
|
+
:categories => :these_are_the_categories_for_real,
|
150
|
+
:categories_to_posts => :this_one_is_just_full_of_bees,
|
151
|
+
}
|
152
|
+
}
|
153
|
+
|
154
|
+
it "maps data from the specified relation into a has many collection" do
|
155
|
+
expect(
|
156
|
+
user.posts.map(&:id)
|
157
|
+
).to eq(["posts/1", "posts/2"])
|
158
|
+
end
|
159
|
+
|
160
|
+
it "maps data from the specified relation into a `belongs_to` field" do
|
161
|
+
expect(
|
162
|
+
user.posts.first.author.__getobj__.object_id
|
163
|
+
).to eq(user.object_id)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,77 @@
|
|
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 "sequel_mapper"
|
8
|
+
|
9
|
+
require "sequel_mapper/configurations/conventional_configuration"
|
10
|
+
|
11
|
+
RSpec.describe "Config override" do
|
12
|
+
include_context "mapper setup"
|
13
|
+
include_context "sequel persistence setup"
|
14
|
+
include_context "seed data setup"
|
15
|
+
|
16
|
+
|
17
|
+
subject(:user_mapper) {
|
18
|
+
SequelMapper.mapper(
|
19
|
+
config: mapper_config,
|
20
|
+
name: :users,
|
21
|
+
datastore: datastore,
|
22
|
+
)
|
23
|
+
}
|
24
|
+
|
25
|
+
let(:mapper_config) {
|
26
|
+
SequelMapper::Configurations::ConventionalConfiguration.new(datastore)
|
27
|
+
.setup_mapping(:users) { |users|
|
28
|
+
users.has_many :posts, foreign_key: :author_id
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
let(:user) { user_mapper.where(id: "users/1").first }
|
33
|
+
|
34
|
+
context "with an object that has private fields" do
|
35
|
+
let(:user_class) {
|
36
|
+
Class.new(User) {
|
37
|
+
private :first_name
|
38
|
+
private :last_name
|
39
|
+
|
40
|
+
def full_name
|
41
|
+
[first_name, last_name].join(" ")
|
42
|
+
end
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
let(:user_serializer) {
|
47
|
+
->(object) {
|
48
|
+
object.to_h.merge(
|
49
|
+
first_name: "I am a custom serializer",
|
50
|
+
last_name: "and i don't care about facts",
|
51
|
+
)
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
before do
|
56
|
+
mapper_config.setup_mapping(:users) do |config|
|
57
|
+
config.class(user_class)
|
58
|
+
config.serializer(user_serializer)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "when saving the object" do
|
63
|
+
it "uses the custom serializer" do
|
64
|
+
user.first_name = "This won't work"
|
65
|
+
user.last_name = "because the serialzer is weird"
|
66
|
+
|
67
|
+
user_mapper.save(user)
|
68
|
+
|
69
|
+
expect(datastore).to have_persisted(:users, hash_including(
|
70
|
+
id: user.id,
|
71
|
+
first_name: "I am a custom serializer",
|
72
|
+
last_name: "and i don't care about facts",
|
73
|
+
))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "support/mapper_setup"
|
4
|
+
require "support/sequel_persistence_setup"
|
5
|
+
require "support/seed_data_setup"
|
6
|
+
require "support/have_persisted_matcher"
|
7
|
+
require "sequel_mapper"
|
8
|
+
|
9
|
+
RSpec.describe "Deletion" do
|
10
|
+
include_context "mapper setup"
|
11
|
+
include_context "sequel persistence setup"
|
12
|
+
include_context "seed data setup"
|
13
|
+
|
14
|
+
subject(:mapper) { user_mapper }
|
15
|
+
|
16
|
+
let(:user) {
|
17
|
+
mapper.where(id: "users/1").first
|
18
|
+
}
|
19
|
+
|
20
|
+
let(:reloaded_user) {
|
21
|
+
mapper.where(id: "users/1").first
|
22
|
+
}
|
23
|
+
|
24
|
+
describe "Deleting the root" do
|
25
|
+
it "deletes the root object" do
|
26
|
+
mapper.delete(user)
|
27
|
+
|
28
|
+
expect(datastore).not_to have_persisted(
|
29
|
+
:users,
|
30
|
+
hash_including(id: "users/1")
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
context "when much of the graph has been loaded" do
|
35
|
+
before do
|
36
|
+
user.posts.flat_map(&:comments)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "deletes the root object" do
|
40
|
+
mapper.delete(user)
|
41
|
+
|
42
|
+
expect(datastore).not_to have_persisted(
|
43
|
+
:users,
|
44
|
+
hash_including(id: "users/1")
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "does not delete the child objects" do
|
49
|
+
expect {
|
50
|
+
mapper.delete(user)
|
51
|
+
}.not_to change { [datastore[:posts], datastore[:comments]].map(&:count) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# context "deleting multiple" do
|
56
|
+
# it "is not currently supported"
|
57
|
+
# end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "Deleting a child object (one to many)" do
|
61
|
+
let(:post) {
|
62
|
+
user.posts.find { |post| post.id == "posts/1" }
|
63
|
+
}
|
64
|
+
|
65
|
+
it "deletes the specified node" do
|
66
|
+
user.posts.delete(post)
|
67
|
+
mapper.save(user)
|
68
|
+
|
69
|
+
expect(datastore).not_to have_persisted(
|
70
|
+
:posts,
|
71
|
+
hash_including(id: "posts/1")
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "does not delete the parent object" do
|
76
|
+
user.posts.delete(post)
|
77
|
+
mapper.save(user)
|
78
|
+
|
79
|
+
expect(datastore).to have_persisted(
|
80
|
+
:users,
|
81
|
+
hash_including(id: "users/1")
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "does not delete the sibling objects" do
|
86
|
+
user.posts.delete(post)
|
87
|
+
mapper.save(user)
|
88
|
+
|
89
|
+
expect(reloaded_user.posts.count).to be > 0
|
90
|
+
end
|
91
|
+
|
92
|
+
it "does not cascade delete" do
|
93
|
+
user.posts.delete(post)
|
94
|
+
mapper.save(user)
|
95
|
+
|
96
|
+
expect(datastore).to have_persisted(
|
97
|
+
:comments,
|
98
|
+
hash_including(
|
99
|
+
post_id: "posts/1",
|
100
|
+
)
|
101
|
+
)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -1,34 +1,33 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
+
require "support/have_persisted_matcher"
|
4
|
+
require "support/mapper_setup"
|
5
|
+
require "support/sequel_persistence_setup"
|
6
|
+
require "support/seed_data_setup"
|
3
7
|
require "sequel_mapper"
|
4
|
-
require "support/graph_fixture"
|
5
8
|
|
6
9
|
RSpec.describe "Graph persistence" do
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
datastore: datastore,
|
13
|
-
relation_mappings: relation_mappings,
|
14
|
-
)
|
15
|
-
}
|
10
|
+
include_context "mapper setup"
|
11
|
+
include_context "sequel persistence setup"
|
12
|
+
include_context "seed data setup"
|
13
|
+
|
14
|
+
subject(:mapper) { mappers.fetch(:users) }
|
16
15
|
|
17
16
|
let(:user) {
|
18
|
-
|
17
|
+
mapper.where(id: "users/1").first
|
19
18
|
}
|
20
19
|
|
21
|
-
context "without
|
20
|
+
context "without associations" do
|
22
21
|
let(:modified_email) { "bestie+modified@gmail.com" }
|
23
22
|
|
24
23
|
it "saves the root object" do
|
25
24
|
user.email = modified_email
|
26
|
-
|
25
|
+
mapper.save(user)
|
27
26
|
|
28
27
|
expect(datastore).to have_persisted(
|
29
28
|
:users,
|
30
29
|
hash_including(
|
31
|
-
id: "
|
30
|
+
id: "users/1",
|
32
31
|
email: modified_email,
|
33
32
|
)
|
34
33
|
)
|
@@ -36,7 +35,7 @@ RSpec.describe "Graph persistence" do
|
|
36
35
|
|
37
36
|
it "doesn't send associated objects to the database as columns" do
|
38
37
|
user.email = modified_email
|
39
|
-
|
38
|
+
mapper.save(user)
|
40
39
|
|
41
40
|
expect(datastore).not_to have_persisted(
|
42
41
|
:users,
|
@@ -45,6 +44,23 @@ RSpec.describe "Graph persistence" do
|
|
45
44
|
)
|
46
45
|
)
|
47
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
|
48
64
|
end
|
49
65
|
|
50
66
|
context "modify shallow has many associated object" do
|
@@ -53,14 +69,14 @@ RSpec.describe "Graph persistence" do
|
|
53
69
|
|
54
70
|
it "saves the associated object" do
|
55
71
|
post.body = modified_post_body
|
56
|
-
|
72
|
+
mapper.save(user)
|
57
73
|
|
58
74
|
expect(datastore).to have_persisted(
|
59
75
|
:posts,
|
60
76
|
hash_including(
|
61
77
|
id: post.id,
|
62
78
|
subject: post.subject,
|
63
|
-
author_id:
|
79
|
+
author_id: user.id,
|
64
80
|
body: modified_post_body,
|
65
81
|
)
|
66
82
|
)
|
@@ -69,22 +85,22 @@ RSpec.describe "Graph persistence" do
|
|
69
85
|
|
70
86
|
context "modify deeply nested has many associated object" do
|
71
87
|
let(:comment) {
|
72
|
-
user.posts.first.comments.
|
88
|
+
user.posts.first.comments.first
|
73
89
|
}
|
74
90
|
|
75
91
|
let(:modified_comment_body) { "body moving, body moving" }
|
76
92
|
|
77
93
|
it "saves the associated object" do
|
78
94
|
comment.body = modified_comment_body
|
79
|
-
|
95
|
+
mapper.save(user)
|
80
96
|
|
81
97
|
expect(datastore).to have_persisted(
|
82
98
|
:comments,
|
83
99
|
hash_including(
|
84
100
|
{
|
85
|
-
id: "
|
86
|
-
post_id: "
|
87
|
-
commenter_id: "
|
101
|
+
id: "comments/1",
|
102
|
+
post_id: "posts/1",
|
103
|
+
commenter_id: "users/1",
|
88
104
|
body: modified_comment_body,
|
89
105
|
}
|
90
106
|
)
|
@@ -92,41 +108,6 @@ RSpec.describe "Graph persistence" do
|
|
92
108
|
end
|
93
109
|
end
|
94
110
|
|
95
|
-
context "modify the foreign_key of an object" do
|
96
|
-
let(:original_author) { user }
|
97
|
-
let(:new_author) { graph.where(id: "user/2").first }
|
98
|
-
let(:post) { original_author.posts.first }
|
99
|
-
|
100
|
-
it "persists the change in ownership" do
|
101
|
-
post.author = new_author
|
102
|
-
graph.save(user)
|
103
|
-
|
104
|
-
expect(datastore).to have_persisted(
|
105
|
-
:posts,
|
106
|
-
hash_including(
|
107
|
-
id: post.id,
|
108
|
-
author_id: new_author.id,
|
109
|
-
)
|
110
|
-
)
|
111
|
-
end
|
112
|
-
|
113
|
-
it "removes the object form the original graph" do
|
114
|
-
post.author = new_author
|
115
|
-
graph.save(user)
|
116
|
-
|
117
|
-
expect(original_author.posts.to_a.map(&:id))
|
118
|
-
.not_to include("posts/1")
|
119
|
-
end
|
120
|
-
|
121
|
-
it "adds the object to the appropriate graph" do
|
122
|
-
post.author = new_author
|
123
|
-
graph.save(user)
|
124
|
-
|
125
|
-
expect(new_author.posts.to_a.map(&:id))
|
126
|
-
.to include("post/1")
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
111
|
context "add a node to a has many assocation" do
|
131
112
|
let(:new_post_attrs) {
|
132
113
|
{
|
@@ -141,7 +122,7 @@ RSpec.describe "Graph persistence" do
|
|
141
122
|
|
142
123
|
let(:new_post) {
|
143
124
|
SequelMapper::StructFactory.new(
|
144
|
-
|
125
|
+
Post
|
145
126
|
).call(new_post_attrs)
|
146
127
|
}
|
147
128
|
|
@@ -153,7 +134,8 @@ RSpec.describe "Graph persistence" do
|
|
153
134
|
|
154
135
|
it "persists the object" do
|
155
136
|
user.posts.push(new_post)
|
156
|
-
|
137
|
+
|
138
|
+
mapper.save(user)
|
157
139
|
|
158
140
|
expect(datastore).to have_persisted(
|
159
141
|
:posts,
|
@@ -166,18 +148,18 @@ RSpec.describe "Graph persistence" do
|
|
166
148
|
end
|
167
149
|
end
|
168
150
|
|
169
|
-
context "
|
151
|
+
context "delete an object from a has many association" do
|
170
152
|
let(:post) { user.posts.first }
|
171
153
|
|
172
|
-
it "
|
173
|
-
user.posts.
|
154
|
+
it "delete the object from the graph" do
|
155
|
+
user.posts.delete(post)
|
174
156
|
|
175
157
|
expect(user.posts.map(&:id)).not_to include(post.id)
|
176
158
|
end
|
177
159
|
|
178
|
-
it "
|
179
|
-
user.posts.
|
180
|
-
|
160
|
+
it "delete the object from the datastore on save" do
|
161
|
+
user.posts.delete(post)
|
162
|
+
mapper.save(user)
|
181
163
|
|
182
164
|
expect(datastore).not_to have_persisted(
|
183
165
|
:posts,
|
@@ -188,21 +170,21 @@ RSpec.describe "Graph persistence" do
|
|
188
170
|
end
|
189
171
|
end
|
190
172
|
|
191
|
-
context "modify a many to many
|
173
|
+
context "modify a many to many relationship" do
|
192
174
|
let(:post) { user.posts.first }
|
193
175
|
|
194
|
-
context "
|
176
|
+
context "delete a node" do
|
195
177
|
it "mutates the graph" do
|
196
178
|
category = post.categories.first
|
197
|
-
post.categories.
|
179
|
+
post.categories.delete(category)
|
198
180
|
|
199
181
|
expect(post.categories.map(&:id)).not_to include(category.id)
|
200
182
|
end
|
201
183
|
|
202
|
-
it "
|
184
|
+
it "deletes the 'join table' record" do
|
203
185
|
category = post.categories.first
|
204
|
-
post.categories.
|
205
|
-
|
186
|
+
post.categories.delete(category)
|
187
|
+
mapper.save(user)
|
206
188
|
|
207
189
|
expect(datastore).not_to have_persisted(
|
208
190
|
:categories_to_posts,
|
@@ -212,6 +194,19 @@ RSpec.describe "Graph persistence" do
|
|
212
194
|
}
|
213
195
|
)
|
214
196
|
end
|
197
|
+
|
198
|
+
it "does not delete the object" do
|
199
|
+
category = post.categories.first
|
200
|
+
post.categories.delete(category)
|
201
|
+
mapper.save(user)
|
202
|
+
|
203
|
+
expect(datastore).to have_persisted(
|
204
|
+
:categories,
|
205
|
+
hash_including(
|
206
|
+
id: category.id,
|
207
|
+
)
|
208
|
+
)
|
209
|
+
end
|
215
210
|
end
|
216
211
|
|
217
212
|
context "add a node" do
|
@@ -222,12 +217,12 @@ RSpec.describe "Graph persistence" do
|
|
222
217
|
post_with_one_category.categories.push(new_category)
|
223
218
|
|
224
219
|
expect(post_with_one_category.categories.map(&:id))
|
225
|
-
.to match_array(["
|
220
|
+
.to match_array(["categories/1", "categories/2"])
|
226
221
|
end
|
227
222
|
|
228
223
|
it "persists the change" do
|
229
224
|
post_with_one_category.categories.push(new_category)
|
230
|
-
|
225
|
+
mapper.save(user)
|
231
226
|
|
232
227
|
expect(datastore).to have_persisted(
|
233
228
|
:categories_to_posts,
|
@@ -252,7 +247,7 @@ RSpec.describe "Graph persistence" do
|
|
252
247
|
|
253
248
|
it "persists the change" do
|
254
249
|
category.name = modified_category_name
|
255
|
-
|
250
|
+
mapper.save(user)
|
256
251
|
|
257
252
|
expect(datastore).to have_persisted(
|
258
253
|
:categories,
|
@@ -265,23 +260,23 @@ RSpec.describe "Graph persistence" do
|
|
265
260
|
end
|
266
261
|
end
|
267
262
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
data === record
|
273
|
-
else
|
274
|
-
data == record
|
275
|
-
end
|
276
|
-
}
|
263
|
+
context "when a save operation fails (some object is not persistable)" do
|
264
|
+
before do
|
265
|
+
user.posts.first.subject = "UNRELATED CHANGE THAT WILL FAIL"
|
266
|
+
user.email = unpersistable_object
|
277
267
|
end
|
278
268
|
|
279
|
-
|
280
|
-
|
281
|
-
|
269
|
+
let(:unpersistable_object) { ->() { } }
|
270
|
+
|
271
|
+
it "rolls back the transation" do
|
272
|
+
begin
|
273
|
+
mapper.save(user)
|
274
|
+
rescue Sequel::Error
|
275
|
+
end
|
282
276
|
|
283
|
-
|
284
|
-
|
277
|
+
expect(datastore).not_to have_persisted(:posts, hash_including(
|
278
|
+
subject: "UNRELATED CHANGE THAT WILL FAIL"
|
279
|
+
))
|
285
280
|
end
|
286
281
|
end
|
287
282
|
end
|