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.
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
+ }