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,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