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,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
@@ -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
- include SequelMapper::GraphFixture
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(:graph) {
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
- graph.where(id: "user/1").first
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
- user
32
-
33
- expect(query_counter.read_count).to eq(1)
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.map(&identity)
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.map(&:name).to_a
82
+ post.categories.each { |x| x }
77
83
  }.to change { query_counter.read_count }.by(1)
78
84
  end
79
85
  end
@@ -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
- include SequelMapper::GraphFixture
8
-
9
- subject(:graph) {
10
- SequelMapper::Graph.new(
11
- top_level_namespace: :users,
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
- graph.where(id: "user/1").first
16
+ mapper.where(id: "users/1").first
19
17
  }
20
18
 
21
19
  let(:query_criteria) {
22
20
  {
23
- body: "Lazy load all the things!",
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 "sends the query directly to the datastore" do
34
+ it "delegates the query to the datastore, performs two additiona reads" do
37
35
  expect {
38
- user.posts
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
- # TODO: this is a quick hack to assert that no superfluous records where
44
- # loaded. Figure out a better way to check efficiency
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/association_proxy"
3
+ require "sequel_mapper/collection_mutability_proxy"
4
4
 
5
- RSpec.describe SequelMapper::AssociationProxy do
5
+ RSpec.describe SequelMapper::CollectionMutabilityProxy do
6
6
  let(:proxy) {
7
- SequelMapper::AssociationProxy.new(lazy_enum)
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 "#remove" do
62
+ describe "#delete" do
63
63
  it "returns self" do
64
- expect(proxy.remove(3)).to be(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.remove(3)
69
+ proxy.delete(3)
70
70
  end
71
71
 
72
72
  it "skips that element in the enumeration" do