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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/CODE_OF_CONDUCT.md +28 -0
  4. data/Gemfile.lock +32 -2
  5. data/MissingFeatures.md +64 -0
  6. data/README.md +141 -72
  7. data/Rakefile +29 -0
  8. data/TODO.md +16 -11
  9. data/features/env.rb +57 -0
  10. data/features/example.feature +121 -0
  11. data/features/step_definitions/example_steps.rb +46 -0
  12. data/lib/sequel_mapper.rb +6 -2
  13. data/lib/sequel_mapper/abstract_record.rb +53 -0
  14. data/lib/sequel_mapper/association_loaders.rb +52 -0
  15. data/lib/sequel_mapper/collection_mutability_proxy.rb +77 -0
  16. data/lib/sequel_mapper/configurations/conventional_association_configuration.rb +187 -0
  17. data/lib/sequel_mapper/configurations/conventional_configuration.rb +269 -0
  18. data/lib/sequel_mapper/dataset.rb +37 -0
  19. data/lib/sequel_mapper/deleted_record.rb +16 -0
  20. data/lib/sequel_mapper/dirty_map.rb +31 -0
  21. data/lib/sequel_mapper/graph_loader.rb +48 -0
  22. data/lib/sequel_mapper/graph_serializer.rb +107 -0
  23. data/lib/sequel_mapper/identity_map.rb +22 -0
  24. data/lib/sequel_mapper/lazy_object_proxy.rb +51 -0
  25. data/lib/sequel_mapper/many_to_many_association.rb +181 -0
  26. data/lib/sequel_mapper/many_to_one_association.rb +60 -0
  27. data/lib/sequel_mapper/mapper_facade.rb +180 -0
  28. data/lib/sequel_mapper/one_to_many_association.rb +51 -0
  29. data/lib/sequel_mapper/public_conveniencies.rb +27 -0
  30. data/lib/sequel_mapper/query_order.rb +32 -0
  31. data/lib/sequel_mapper/queryable_lazy_dataset_loader.rb +70 -0
  32. data/lib/sequel_mapper/relation_mapping.rb +35 -0
  33. data/lib/sequel_mapper/serializer.rb +18 -0
  34. data/lib/sequel_mapper/short_inspection_string.rb +18 -0
  35. data/lib/sequel_mapper/subset_queries_proxy.rb +11 -0
  36. data/lib/sequel_mapper/upserted_record.rb +15 -0
  37. data/lib/sequel_mapper/version.rb +1 -1
  38. data/sequel_mapper.gemspec +3 -0
  39. data/spec/config_override_spec.rb +167 -0
  40. data/spec/custom_serializers_spec.rb +77 -0
  41. data/spec/deletion_spec.rb +104 -0
  42. data/spec/graph_persistence_spec.rb +83 -88
  43. data/spec/graph_traversal_spec.rb +32 -31
  44. data/spec/new_graph_persistence_spec.rb +69 -0
  45. data/spec/object_identity_spec.rb +70 -0
  46. data/spec/ordered_association_spec.rb +46 -16
  47. data/spec/persistence_efficiency_spec.rb +186 -0
  48. data/spec/predefined_queries_spec.rb +73 -0
  49. data/spec/proxying_spec.rb +25 -19
  50. data/spec/querying_spec.rb +24 -27
  51. data/spec/readme_examples_spec.rb +35 -0
  52. data/spec/sequel_mapper/abstract_record_spec.rb +179 -0
  53. data/spec/sequel_mapper/{association_proxy_spec.rb → collection_mutability_proxy_spec.rb} +6 -6
  54. data/spec/sequel_mapper/deleted_record_spec.rb +59 -0
  55. data/spec/sequel_mapper/lazy_object_proxy_spec.rb +140 -0
  56. data/spec/sequel_mapper/public_conveniencies_spec.rb +49 -0
  57. data/spec/sequel_mapper/queryable_lazy_dataset_loader_spec.rb +103 -0
  58. data/spec/sequel_mapper/upserted_record_spec.rb +59 -0
  59. data/spec/spec_helper.rb +7 -10
  60. data/spec/support/blog_schema.rb +29 -0
  61. data/spec/support/have_persisted_matcher.rb +19 -0
  62. data/spec/support/mapper_setup.rb +234 -0
  63. data/spec/support/mock_sequel.rb +0 -1
  64. data/spec/support/object_graph_setup.rb +106 -0
  65. data/spec/support/seed_data_setup.rb +122 -0
  66. data/spec/support/sequel_persistence_setup.rb +19 -0
  67. data/spec/support/sequel_test_support.rb +159 -0
  68. metadata +121 -15
  69. data/lib/sequel_mapper/association_proxy.rb +0 -54
  70. data/lib/sequel_mapper/belongs_to_association_proxy.rb +0 -27
  71. data/lib/sequel_mapper/graph.rb +0 -174
  72. data/lib/sequel_mapper/queryable_association_proxy.rb +0 -23
  73. data/spec/sequel_mapper/belongs_to_association_proxy_spec.rb +0 -65
  74. data/spec/support/graph_fixture.rb +0 -331
  75. 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
@@ -1,14 +1,6 @@
1
1
  require "pry"
2
- require "sequel"
3
- require "logger"
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
+ }