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,73 @@
|
|
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
|
+
require "sequel_mapper/configurations/conventional_configuration"
|
8
|
+
|
9
|
+
RSpec.describe "Predefined subset queries" do
|
10
|
+
include_context "mapper setup"
|
11
|
+
include_context "sequel persistence setup"
|
12
|
+
include_context "seed data setup"
|
13
|
+
|
14
|
+
subject(:users) { user_mapper }
|
15
|
+
|
16
|
+
subject(:user_mapper) {
|
17
|
+
SequelMapper.mapper(
|
18
|
+
config: mapper_config,
|
19
|
+
name: :users,
|
20
|
+
datastore: datastore,
|
21
|
+
)
|
22
|
+
}
|
23
|
+
|
24
|
+
let(:mapper_config) {
|
25
|
+
SequelMapper::Configurations::ConventionalConfiguration.new(datastore)
|
26
|
+
.setup_mapping(:users) { |users|
|
27
|
+
users.has_many :posts, foreign_key: :author_id
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
context "on the top level mapper" do
|
32
|
+
context "subset is defined with a block" do
|
33
|
+
before do
|
34
|
+
mapper_config.setup_mapping(:users) do |config|
|
35
|
+
config.subset(:tricketts) do |dataset|
|
36
|
+
dataset
|
37
|
+
.where(last_name: "Trickett")
|
38
|
+
.order(:first_name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it "maps the result of the subset" do
|
44
|
+
expect(users.subset(:tricketts).map(&:first_name)).to eq([
|
45
|
+
"Hansel",
|
46
|
+
"Jasper",
|
47
|
+
])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "on a has many association" do
|
53
|
+
before do
|
54
|
+
mapper_config.setup_mapping(:posts) do |config|
|
55
|
+
config.subset(:body_contains) do |dataset, search_string|
|
56
|
+
dataset.where("body like '%#{search_string}%'")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
let(:user) { users.first }
|
62
|
+
|
63
|
+
it "maps the datastore subset" do
|
64
|
+
expect(user.posts.subset(:body_contains, "purrr").map(&:id))
|
65
|
+
.to eq(["posts/2"])
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns an immutable collection" do
|
69
|
+
expect(user.posts.subset(:body_contains, "purrr").public_methods)
|
70
|
+
.not_to include(:push, :<<, :delete)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/spec/proxying_spec.rb
CHANGED
@@ -1,36 +1,30 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
+
require "support/mapper_setup"
|
4
|
+
require "support/sequel_persistence_setup"
|
5
|
+
require "support/seed_data_setup"
|
3
6
|
require "sequel_mapper"
|
4
|
-
require "support/graph_fixture"
|
5
7
|
|
6
8
|
RSpec.describe "Proxying associations" do
|
7
|
-
|
9
|
+
include_context "mapper setup"
|
10
|
+
include_context "sequel persistence setup"
|
11
|
+
include_context "seed data setup"
|
8
12
|
|
9
13
|
context "of type `has_many`" do
|
10
|
-
subject(:
|
11
|
-
SequelMapper::Graph.new(
|
12
|
-
top_level_namespace: :users,
|
13
|
-
datastore: datastore,
|
14
|
-
relation_mappings: relation_mappings,
|
15
|
-
)
|
16
|
-
}
|
14
|
+
subject(:mapper) { user_mapper }
|
17
15
|
|
18
16
|
let(:user) {
|
19
|
-
|
17
|
+
mapper.where(id: "users/1").first
|
20
18
|
}
|
21
19
|
|
22
20
|
let(:posts) { user.posts }
|
23
21
|
|
24
|
-
def identity
|
25
|
-
->(x){x}
|
26
|
-
end
|
27
|
-
|
28
22
|
describe "limiting datastore reads" do
|
29
23
|
context "when loading the root node" do
|
30
24
|
it "only performs one read" do
|
31
|
-
|
32
|
-
|
33
|
-
|
25
|
+
expect {
|
26
|
+
user
|
27
|
+
}.to change { query_counter.read_count }.by(1)
|
34
28
|
end
|
35
29
|
end
|
36
30
|
|
@@ -49,9 +43,21 @@ RSpec.describe "Proxying associations" do
|
|
49
43
|
|
50
44
|
it "does a single additional read for the assocation collection" do
|
51
45
|
expect {
|
52
|
-
user.posts.
|
46
|
+
user.posts.each { |x| x }
|
53
47
|
}.to change { query_counter.read_count }.by(1)
|
54
48
|
end
|
49
|
+
|
50
|
+
context "when doing this more than once" do
|
51
|
+
before do
|
52
|
+
posts.each { |x| x }
|
53
|
+
end
|
54
|
+
|
55
|
+
it "performs no additional reads" do
|
56
|
+
expect {
|
57
|
+
user.posts.each { |x| x }
|
58
|
+
}.not_to change { query_counter.read_count }
|
59
|
+
end
|
60
|
+
end
|
55
61
|
end
|
56
62
|
|
57
63
|
context "when getting a reference to a many to many assocation" do
|
@@ -73,7 +79,7 @@ RSpec.describe "Proxying associations" do
|
|
73
79
|
post = user.posts.first
|
74
80
|
|
75
81
|
expect {
|
76
|
-
post.categories.
|
82
|
+
post.categories.each { |x| x }
|
77
83
|
}.to change { query_counter.read_count }.by(1)
|
78
84
|
end
|
79
85
|
end
|
data/spec/querying_spec.rb
CHANGED
@@ -1,51 +1,48 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
+
require "support/mapper_setup"
|
4
|
+
require "support/sequel_persistence_setup"
|
5
|
+
require "support/seed_data_setup"
|
3
6
|
require "sequel_mapper"
|
4
|
-
require "support/graph_fixture"
|
5
7
|
|
6
8
|
RSpec.describe "Querying" do
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
datastore: datastore,
|
13
|
-
relation_mappings: relation_mappings,
|
14
|
-
)
|
15
|
-
}
|
9
|
+
include_context "mapper setup"
|
10
|
+
include_context "sequel persistence setup"
|
11
|
+
include_context "seed data setup"
|
12
|
+
|
13
|
+
subject(:mapper) { user_mapper }
|
16
14
|
|
17
15
|
let(:user) {
|
18
|
-
|
16
|
+
mapper.where(id: "users/1").first
|
19
17
|
}
|
20
18
|
|
21
19
|
let(:query_criteria) {
|
22
20
|
{
|
23
|
-
body: "
|
21
|
+
body: "I do it three times purrr day",
|
24
22
|
}
|
25
23
|
}
|
26
24
|
|
25
|
+
let(:filtered_posts) {
|
26
|
+
user.posts.where(query_criteria)
|
27
|
+
}
|
28
|
+
|
27
29
|
describe "arbitrary where query" do
|
28
30
|
it "returns a filtered version of the association" do
|
29
|
-
expect(
|
30
|
-
user.posts
|
31
|
-
.where(query_criteria)
|
32
|
-
.map(&:id)
|
33
|
-
).to eq(["post/2"])
|
31
|
+
expect(filtered_posts.map(&:id)).to eq(["posts/2"])
|
34
32
|
end
|
35
33
|
|
36
|
-
it "
|
34
|
+
it "delegates the query to the datastore, performs two additiona reads" do
|
37
35
|
expect {
|
38
|
-
|
39
|
-
.where(query_criteria)
|
40
|
-
.map(&:id)
|
36
|
+
filtered_posts.map(&:id)
|
41
37
|
}.to change { query_counter.read_count }.by(2)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "returns another collection" do
|
41
|
+
expect(filtered_posts).not_to be(user.posts)
|
42
|
+
end
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
expect(graph.send(:identity_map).values.map(&:id)).to match_array([
|
46
|
-
"user/1",
|
47
|
-
"post/2",
|
48
|
-
])
|
44
|
+
it "returns an immutable collection" do
|
45
|
+
expect(filtered_posts.public_methods).not_to include(:push, :<<, :delete)
|
49
46
|
end
|
50
47
|
end
|
51
48
|
end
|
@@ -0,0 +1,35 @@
|
|
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 "spec_helper"
|
9
|
+
|
10
|
+
require "support/mapper_setup"
|
11
|
+
require "support/sequel_persistence_setup"
|
12
|
+
require "support/seed_data_setup"
|
13
|
+
require "sequel_mapper"
|
14
|
+
|
15
|
+
RSpec.describe "README examples" do
|
16
|
+
include_context "sequel persistence setup"
|
17
|
+
|
18
|
+
readme_contents = File.read("README.md")
|
19
|
+
|
20
|
+
code_samples = readme_contents
|
21
|
+
.split("```ruby")
|
22
|
+
.drop(1)
|
23
|
+
.map { |s| s.split("```").first }
|
24
|
+
|
25
|
+
code_samples.each_with_index do |code_sample, i|
|
26
|
+
it "executes without error" do
|
27
|
+
begin
|
28
|
+
Module.new.module_eval(code_sample)
|
29
|
+
rescue => e
|
30
|
+
File.open("./example#{i}.rb", "w") { |f| f.puts(code_sample) }
|
31
|
+
binding.pry if ENV["DEBUG"]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "sequel_mapper/abstract_record"
|
4
|
+
|
5
|
+
RSpec.describe SequelMapper::AbstractRecord do
|
6
|
+
subject(:record) {
|
7
|
+
SequelMapper::AbstractRecord.new(namespace, identity, raw_data)
|
8
|
+
}
|
9
|
+
|
10
|
+
let(:namespace) { double(:namespace) }
|
11
|
+
|
12
|
+
let(:identity) {
|
13
|
+
{ id: id }
|
14
|
+
}
|
15
|
+
|
16
|
+
let(:raw_data) {
|
17
|
+
{
|
18
|
+
name: name,
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
let(:id) { double(:id) }
|
23
|
+
let(:name) { double(:name) }
|
24
|
+
|
25
|
+
describe "#namespace" do
|
26
|
+
it "returns the namespace" do
|
27
|
+
expect(record.namespace).to eq(namespace)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#identity" do
|
32
|
+
it "returns the identity" do
|
33
|
+
expect(record.identity).to eq(identity)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#fetch" do
|
38
|
+
it "delegates to the underlying Hash representation" do
|
39
|
+
expect(record.fetch(:id)).to eq(id)
|
40
|
+
expect(record.fetch(:name)).to eq(name)
|
41
|
+
expect(record.fetch(:not_there, "nope")).to eq("nope")
|
42
|
+
expect(record.fetch(:not_there) { "lord no" }).to eq("lord no")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#to_h" do
|
47
|
+
it "returns a raw_data merged with identity" do
|
48
|
+
expect(record.to_h).to eq(
|
49
|
+
id: id,
|
50
|
+
name: name,
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "#if_upsert" do
|
56
|
+
it "returns self" do
|
57
|
+
expect(
|
58
|
+
record.if_upsert { |_| }
|
59
|
+
).to be(record)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#if_delete" do
|
64
|
+
it "returns self" do
|
65
|
+
expect(
|
66
|
+
record.if_delete { |_| }
|
67
|
+
).to be(record)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#merge" do
|
72
|
+
let(:extra_data) {
|
73
|
+
{
|
74
|
+
location: location,
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
let(:location) { double(:location) }
|
79
|
+
|
80
|
+
it "returns a new record with same identity" do
|
81
|
+
expect(
|
82
|
+
record.merge(extra_data).identity
|
83
|
+
).to eq(identity)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "returns a new record with same namespace" do
|
87
|
+
expect(
|
88
|
+
record.merge(extra_data).namespace
|
89
|
+
).to eq(namespace)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "returns a new record with merged data" do
|
93
|
+
merged_record = record.merge(extra_data)
|
94
|
+
|
95
|
+
expect(merged_record.to_h).to eq(
|
96
|
+
id: id,
|
97
|
+
name: name,
|
98
|
+
location: location,
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "does not mutate the original record" do
|
103
|
+
expect {
|
104
|
+
record.merge(extra_data)
|
105
|
+
}.not_to change { record.to_h }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "#==" do
|
110
|
+
context "super class contract" do
|
111
|
+
let(:comparitor) { record.merge({}) }
|
112
|
+
|
113
|
+
it "raises NotImplementedError" do
|
114
|
+
expect{
|
115
|
+
record == comparitor
|
116
|
+
}.to raise_error(NotImplementedError)
|
117
|
+
end
|
118
|
+
|
119
|
+
context "when subclassed" do
|
120
|
+
subject(:record) {
|
121
|
+
record_subclass.new(namespace, identity, raw_data)
|
122
|
+
}
|
123
|
+
|
124
|
+
let(:record_subclass) {
|
125
|
+
Class.new(SequelMapper::AbstractRecord) {
|
126
|
+
protected
|
127
|
+
|
128
|
+
def operation
|
129
|
+
:do_a_thing
|
130
|
+
end
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
context "when comparitor is of the wrong type" do
|
135
|
+
it "is not equal" do
|
136
|
+
expect(record.==(Object.new)).to be(false)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context "when the operation type is equal" do
|
141
|
+
context "when the combined `raw_data` and `identity` are equal" do
|
142
|
+
let(:comparitor) { record.merge({}) }
|
143
|
+
|
144
|
+
it "is equal" do
|
145
|
+
expect(record.==(comparitor)).to be(true)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context "when the combined `raw_data` and `identity` are not equal" do
|
150
|
+
let(:comparitor) { record.merge(something_else: "i'm different") }
|
151
|
+
|
152
|
+
it "is not equal" do
|
153
|
+
expect(record.==(comparitor)).to be(false)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context "when the operation name differs" do
|
159
|
+
let(:comparitor) {
|
160
|
+
record_class_with_different_operation.new(namespace, identity, raw_data)
|
161
|
+
}
|
162
|
+
|
163
|
+
let(:record_class_with_different_operation) {
|
164
|
+
Class.new(SequelMapper::AbstractRecord) {
|
165
|
+
protected
|
166
|
+
def operation
|
167
|
+
:do_a_different_thing
|
168
|
+
end
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
it "is not equal" do
|
173
|
+
expect(record.==(comparitor)).to be(false)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
-
require "sequel_mapper/
|
3
|
+
require "sequel_mapper/collection_mutability_proxy"
|
4
4
|
|
5
|
-
RSpec.describe SequelMapper::
|
5
|
+
RSpec.describe SequelMapper::CollectionMutabilityProxy do
|
6
6
|
let(:proxy) {
|
7
|
-
SequelMapper::
|
7
|
+
SequelMapper::CollectionMutabilityProxy.new(lazy_enum)
|
8
8
|
}
|
9
9
|
|
10
10
|
let(:lazy_enum) { data_set.each.lazy }
|
@@ -59,14 +59,14 @@ RSpec.describe SequelMapper::AssociationProxy do
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
-
describe "#
|
62
|
+
describe "#delete" do
|
63
63
|
it "returns self" do
|
64
|
-
expect(proxy.
|
64
|
+
expect(proxy.delete(3)).to be(proxy)
|
65
65
|
end
|
66
66
|
|
67
67
|
context "after removing a element from the enumeration" do
|
68
68
|
before do
|
69
|
-
proxy.
|
69
|
+
proxy.delete(3)
|
70
70
|
end
|
71
71
|
|
72
72
|
it "skips that element in the enumeration" do
|