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,59 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "sequel_mapper/deleted_record"
|
4
|
+
|
5
|
+
RSpec.describe SequelMapper::DeletedRecord do
|
6
|
+
subject(:record) {
|
7
|
+
SequelMapper::DeletedRecord.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 "#if_delete" do
|
26
|
+
it "invokes the callback" do
|
27
|
+
expect { |callback|
|
28
|
+
record.if_delete(&callback)
|
29
|
+
}.to yield_with_args(record)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#==" do
|
34
|
+
context "with another record that deletes" do
|
35
|
+
let(:comparitor) {
|
36
|
+
record.merge({})
|
37
|
+
}
|
38
|
+
|
39
|
+
it "is equal" do
|
40
|
+
expect(record.==(comparitor)).to be(true)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with another record that does not delete" do
|
45
|
+
let(:comparitor) {
|
46
|
+
Class.new(SequelMapper::AbstractRecord) do
|
47
|
+
protected
|
48
|
+
def operation
|
49
|
+
:something_else
|
50
|
+
end
|
51
|
+
end
|
52
|
+
}
|
53
|
+
|
54
|
+
it "is not equal" do
|
55
|
+
expect(record.==(comparitor)).to be(false)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "sequel_mapper/lazy_object_proxy"
|
4
|
+
|
5
|
+
RSpec.describe SequelMapper::LazyObjectProxy do
|
6
|
+
subject(:proxy) {
|
7
|
+
SequelMapper::LazyObjectProxy.new(
|
8
|
+
object_loader,
|
9
|
+
known_fields,
|
10
|
+
)
|
11
|
+
}
|
12
|
+
|
13
|
+
let(:id) { double(:id) }
|
14
|
+
let(:known_fields) { { id: id } }
|
15
|
+
let(:object_loader) { double(:object_loader, call: proxied_object) }
|
16
|
+
let(:proxied_object) { double(:proxied_object, name: name) }
|
17
|
+
let(:name) { double(:name) }
|
18
|
+
|
19
|
+
describe "#__getobj__" do
|
20
|
+
it "loads the object" do
|
21
|
+
proxy.__getobj__
|
22
|
+
|
23
|
+
expect(object_loader).to have_received(:call)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "returns the proxied object" do
|
27
|
+
expect(proxy.__getobj__).to be(proxied_object)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "when no method is called on it" do
|
32
|
+
it "does not call the loader" do
|
33
|
+
proxy
|
34
|
+
|
35
|
+
expect(object_loader).not_to have_received(:call)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "when a missing method is called on the proxy" do
|
40
|
+
it "is a true decorator" do
|
41
|
+
expect(proxied_object).to receive(:arbitrary_message)
|
42
|
+
|
43
|
+
proxy.arbitrary_message
|
44
|
+
end
|
45
|
+
|
46
|
+
it "loads the object" do
|
47
|
+
proxy.name
|
48
|
+
|
49
|
+
expect(object_loader).to have_received(:call)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "returns delegates the message to the object" do
|
53
|
+
args = [ double, double ]
|
54
|
+
proxy.name(*args)
|
55
|
+
|
56
|
+
expect(proxied_object).to have_received(:name).with(*args)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "returns the objects return value" do
|
60
|
+
expect(proxy.name).to eq(name)
|
61
|
+
end
|
62
|
+
|
63
|
+
context "when calling a method twice" do
|
64
|
+
it "loads the object once" do
|
65
|
+
proxy.name
|
66
|
+
proxy.name
|
67
|
+
|
68
|
+
expect(object_loader).to have_received(:call)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "#loaded?" do
|
74
|
+
context "before the object is loaded" do
|
75
|
+
it "returns false" do
|
76
|
+
expect(proxy).not_to be_loaded
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "after the object is loaded" do
|
81
|
+
def force_object_load(object)
|
82
|
+
object.__getobj__
|
83
|
+
end
|
84
|
+
|
85
|
+
before { force_object_load(proxy) }
|
86
|
+
|
87
|
+
it "returns true" do
|
88
|
+
expect(proxy).to be_loaded
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "known fields" do
|
94
|
+
context "when fields are known before load (such as from foreign key)" do
|
95
|
+
it "does not load the object when that field is accessed" do
|
96
|
+
proxy.id
|
97
|
+
|
98
|
+
expect(proxy).not_to be_loaded
|
99
|
+
end
|
100
|
+
|
101
|
+
it "returns the given value" do
|
102
|
+
expect(proxy.id).to be(id)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "#respond_to?" do
|
108
|
+
context "when method corresponds to a known field" do
|
109
|
+
it "does not the load the object" do
|
110
|
+
proxy.respond_to?(:id)
|
111
|
+
|
112
|
+
expect(proxy).not_to be_loaded
|
113
|
+
end
|
114
|
+
|
115
|
+
it "repsonds to the method" do
|
116
|
+
expect(proxy).to respond_to(:id)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "when the method is not a known field" do
|
121
|
+
it "loads the object" do
|
122
|
+
proxy.respond_to?(:something_arbitrary)
|
123
|
+
|
124
|
+
expect(proxy).to be_loaded
|
125
|
+
end
|
126
|
+
|
127
|
+
context "when lazy proxied object does respond to the method" do
|
128
|
+
it "responds to the method" do
|
129
|
+
expect(proxy).to respond_to(:name)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "when lazy proxied object does not respond to the method" do
|
134
|
+
it "does not respond to the method" do
|
135
|
+
expect(proxy).not_to respond_to(:something_arbitrary)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "sequel_mapper/public_conveniencies"
|
4
|
+
|
5
|
+
RSpec.describe SequelMapper::PublicConveniencies do
|
6
|
+
subject(:conveniences) {
|
7
|
+
Module.new.extend(SequelMapper::PublicConveniencies)
|
8
|
+
}
|
9
|
+
|
10
|
+
describe "#mapper" do
|
11
|
+
let(:datastore) {
|
12
|
+
{
|
13
|
+
things: [ thing_record ],
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
let(:mapper_config) {
|
18
|
+
{
|
19
|
+
things: double(
|
20
|
+
:thing_config,
|
21
|
+
namespace: :things,
|
22
|
+
associations: [],
|
23
|
+
primary_key: [],
|
24
|
+
factory: ->(x){x}
|
25
|
+
)
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
let(:mapping_name) { :things }
|
30
|
+
|
31
|
+
let(:thing_record) {
|
32
|
+
{
|
33
|
+
id: "THE THING",
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
it "returns a mapper for the specified mapping" do
|
38
|
+
expect(
|
39
|
+
conveniences
|
40
|
+
.mapper(
|
41
|
+
config: mapper_config,
|
42
|
+
datastore: datastore,
|
43
|
+
name: mapping_name,
|
44
|
+
)
|
45
|
+
.all.first.fetch(:id)
|
46
|
+
).to eq("THE THING")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "sequel_mapper/queryable_lazy_dataset_loader"
|
4
|
+
|
5
|
+
RSpec.describe SequelMapper::QueryableLazyDatasetLoader do
|
6
|
+
let(:proxy) {
|
7
|
+
SequelMapper::QueryableLazyDatasetLoader.new(
|
8
|
+
database_enum,
|
9
|
+
loader,
|
10
|
+
mapper,
|
11
|
+
)
|
12
|
+
}
|
13
|
+
|
14
|
+
let(:row1) { double(:row1) }
|
15
|
+
let(:row2) { double(:row2) }
|
16
|
+
let(:object1) { double(:object1) }
|
17
|
+
let(:object2) { double(:object2) }
|
18
|
+
let(:row_object_map) {
|
19
|
+
{
|
20
|
+
row1 => object1,
|
21
|
+
row2 => object2,
|
22
|
+
}
|
23
|
+
}
|
24
|
+
let(:collection_size) { row_object_map.size }
|
25
|
+
|
26
|
+
let(:database_enum) { [row1, row2].each.lazy }
|
27
|
+
|
28
|
+
let(:mapper) { double(:mapper) }
|
29
|
+
|
30
|
+
let(:loader_count) { @loader_count }
|
31
|
+
let(:loader) {
|
32
|
+
@loader_count = 0
|
33
|
+
|
34
|
+
->(row) {
|
35
|
+
@loader_count = @loader_count + 1
|
36
|
+
row_object_map.fetch(row)
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
describe "#each" do
|
41
|
+
it "iterates over all elements of the database_enum" do
|
42
|
+
elements = []
|
43
|
+
proxy.each { |x| elements.push(x) }
|
44
|
+
|
45
|
+
expect(elements).to eq([object1, object2])
|
46
|
+
end
|
47
|
+
|
48
|
+
context "when the collection is not loaded" do
|
49
|
+
it "loads the collection on first call" do
|
50
|
+
proxy.each { |x| x }
|
51
|
+
|
52
|
+
expect(loader_count).to eq(collection_size)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "when the collection has already loaded (second call to #each)" do
|
57
|
+
before do
|
58
|
+
proxy.each { |x| x }
|
59
|
+
end
|
60
|
+
|
61
|
+
it "does not load a second time" do
|
62
|
+
expect {
|
63
|
+
proxy.each { |x| x }
|
64
|
+
}.not_to change { loader_count }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "when #first has been called beforehand" do
|
69
|
+
before do
|
70
|
+
proxy.first
|
71
|
+
end
|
72
|
+
|
73
|
+
it "does not reload the first element of the collection" do
|
74
|
+
proxy.each { |x| x }
|
75
|
+
|
76
|
+
expect(loader_count).to eq(collection_size)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "iterates over all elements" do
|
80
|
+
elements = []
|
81
|
+
proxy.each { |x| elements.push(x) }
|
82
|
+
|
83
|
+
expect(elements).to eq([object1, object2])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "when drop has been called beforehand" do
|
88
|
+
it "loads each object just once" do
|
89
|
+
proxy.drop(1).each { |x| x }
|
90
|
+
|
91
|
+
expect(loader_count).to eq(collection_size)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "#first" do
|
97
|
+
it "loads only the first object" do
|
98
|
+
proxy.first
|
99
|
+
|
100
|
+
expect(loader_count).to eq(1)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "sequel_mapper/upserted_record"
|
4
|
+
|
5
|
+
RSpec.describe SequelMapper::UpsertedRecord do
|
6
|
+
subject(:record) {
|
7
|
+
SequelMapper::UpsertedRecord.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 "#if_upsert" do
|
26
|
+
it "invokes the callback" do
|
27
|
+
expect { |callback|
|
28
|
+
record.if_upsert(&callback)
|
29
|
+
}.to yield_with_args(record)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#==" do
|
34
|
+
context "with another record that upserts" do
|
35
|
+
let(:comparitor) {
|
36
|
+
record.merge({})
|
37
|
+
}
|
38
|
+
|
39
|
+
it "is equal" do
|
40
|
+
expect(record.==(comparitor)).to be(true)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with another record that does not upsert" do
|
45
|
+
let(:comparitor) {
|
46
|
+
Class.new(SequelMapper::AbstractRecord) do
|
47
|
+
protected
|
48
|
+
def operation
|
49
|
+
:something_else
|
50
|
+
end
|
51
|
+
end
|
52
|
+
}
|
53
|
+
|
54
|
+
it "is not equal" do
|
55
|
+
expect(record.==(comparitor)).to be(false)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,14 +1,6 @@
|
|
1
1
|
require "pry"
|
2
|
-
require "
|
3
|
-
require "
|
4
|
-
|
5
|
-
`psql postgres --command "CREATE DATABASE $PGDATABASE;"`
|
6
|
-
|
7
|
-
DB = Sequel.postgres(
|
8
|
-
host: ENV.fetch("PGHOST"),
|
9
|
-
user: ENV.fetch("PGUSER"),
|
10
|
-
database: ENV.fetch("PGDATABASE"),
|
11
|
-
)
|
2
|
+
require "support/sequel_test_support"
|
3
|
+
require "support/blog_schema"
|
12
4
|
|
13
5
|
RSpec.configure do |config|
|
14
6
|
config.expect_with :rspec do |expectations|
|
@@ -36,4 +28,9 @@ RSpec.configure do |config|
|
|
36
28
|
# config.order = :random
|
37
29
|
|
38
30
|
# Kernel.srand config.seed
|
31
|
+
|
32
|
+
config.before(:suite) do
|
33
|
+
SequelMapper::SequelTestSupport.drop_tables
|
34
|
+
SequelMapper::SequelTestSupport.create_tables(BLOG_SCHEMA)
|
35
|
+
end
|
39
36
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
BLOG_SCHEMA = {
|
2
|
+
users: [
|
3
|
+
{ name: :id, type: :String },
|
4
|
+
{ name: :first_name, type: :String },
|
5
|
+
{ name: :last_name, type: :String },
|
6
|
+
{ name: :email, type: :String },
|
7
|
+
],
|
8
|
+
posts: [
|
9
|
+
{ name: :id, type: :String },
|
10
|
+
{ name: :subject, type: :String },
|
11
|
+
{ name: :body, type: :String },
|
12
|
+
{ name: :author_id, type: :String },
|
13
|
+
{ name: :created_at, type: :DateTime },
|
14
|
+
],
|
15
|
+
comments: [
|
16
|
+
{ name: :id, type: :String },
|
17
|
+
{ name: :body, type: :String },
|
18
|
+
{ name: :post_id, type: :String },
|
19
|
+
{ name: :commenter_id, type: :String },
|
20
|
+
],
|
21
|
+
categories: [
|
22
|
+
{ name: :id, type: :String },
|
23
|
+
{ name: :name, type: :String },
|
24
|
+
],
|
25
|
+
categories_to_posts: [
|
26
|
+
{ name: :post_id, type: :String },
|
27
|
+
{ name: :category_id, type: :String },
|
28
|
+
],
|
29
|
+
}
|