sequel_mapper 0.0.1 → 0.0.3
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/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
|