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,19 @@
1
+ RSpec::Matchers.define :have_persisted do |relation_name, data|
2
+ match do |datastore|
3
+ datastore[relation_name].find { |record|
4
+ if data.respond_to?(:===)
5
+ data === record
6
+ else
7
+ data == record
8
+ end
9
+ }
10
+ end
11
+
12
+ failure_message do |datastore|
13
+ "expected #{datastore[relation_name]} to have persisted #{data.inspect} in #{relation_name}"
14
+ end
15
+
16
+ failure_message_when_negated do |datastore|
17
+ failure_message.gsub("to have", "not to have")
18
+ end
19
+ end
@@ -0,0 +1,234 @@
1
+ require "sequel_mapper"
2
+ require "sequel_mapper/mapper_facade"
3
+ require "sequel_mapper/relation_mapping"
4
+ require "sequel_mapper/queryable_lazy_dataset_loader"
5
+ require "sequel_mapper/collection_mutability_proxy"
6
+ require "sequel_mapper/lazy_object_proxy"
7
+ require "sequel_mapper/dataset"
8
+ require "sequel_mapper/query_order"
9
+ require "sequel_mapper/one_to_many_association"
10
+ require "sequel_mapper/many_to_one_association"
11
+ require "sequel_mapper/many_to_many_association"
12
+ require "sequel_mapper/subset_queries_proxy"
13
+ require "support/object_graph_setup"
14
+
15
+ RSpec.shared_context "mapper setup" do
16
+ include_context "object graph setup"
17
+
18
+ let(:mappers) {
19
+ {
20
+ users: user_mapper,
21
+ }
22
+ }
23
+
24
+ let(:user_mapper) {
25
+ SequelMapper.mapper(
26
+ config: mappings,
27
+ name: :users,
28
+ datastore: datastore,
29
+ )
30
+ }
31
+
32
+ let(:mappings) {
33
+ Hash[
34
+ configs.map { |name, config|
35
+ fields = config.fetch(:fields) + config.fetch(:associations).keys
36
+
37
+ associations = config.fetch(:associations).map { |assoc_name, assoc_config|
38
+ [
39
+ assoc_name,
40
+ case assoc_config.fetch(:type)
41
+ when :one_to_many
42
+ SequelMapper::OneToManyAssociation.new(
43
+ **assoc_defaults.merge(
44
+ assoc_config.dup.tap { |h| h.delete(:type) }
45
+ )
46
+ )
47
+ when :many_to_one
48
+ SequelMapper::ManyToOneAssociation.new(
49
+ assoc_config.dup.tap { |h| h.delete(:type) }
50
+ )
51
+ when :many_to_many
52
+ SequelMapper::ManyToManyAssociation.new(
53
+ **assoc_defaults
54
+ .merge(
55
+ join_mapping_name: assoc_config.fetch(:join_mapping_name),
56
+ join_dataset: datastore[
57
+ configs
58
+ .fetch(assoc_config.fetch(:join_mapping_name))
59
+ .fetch(:namespace)
60
+ ],
61
+ )
62
+ .merge(
63
+ assoc_config.dup.tap { |h|
64
+ h.delete(:type)
65
+ h.delete(:join_namespace)
66
+ }
67
+ )
68
+ )
69
+ else
70
+ raise "Association type not supported"
71
+ end
72
+ ]
73
+ }
74
+
75
+ [
76
+ name,
77
+ SequelMapper::RelationMapping.new(
78
+ name: name,
79
+ namespace: config.fetch(:namespace),
80
+ fields: config.fetch(:fields),
81
+ primary_key: config.fetch(:primary_key),
82
+ serializer: serializers.fetch(config.fetch(:serializer)).call(fields),
83
+ associations: Hash[associations],
84
+ factory: factories.fetch(name),
85
+ subsets: SequelMapper::SubsetQueriesProxy.new(config.fetch(:subsets, {}))
86
+ )
87
+ ]
88
+ }
89
+ ]
90
+ }
91
+
92
+ def assoc_defaults
93
+ {
94
+ order: SequelMapper::QueryOrder.new(fields: [], direction: "ASC")
95
+ }
96
+ end
97
+
98
+ let(:has_many_proxy_factory) {
99
+ ->(query:, loader:, mapping_name:) {
100
+ SequelMapper::CollectionMutabilityProxy.new(
101
+ SequelMapper::QueryableLazyDatasetLoader.new(
102
+ query,
103
+ loader,
104
+ mappings.fetch(mapping_name).subsets,
105
+ )
106
+ )
107
+ }
108
+ }
109
+
110
+ let(:many_to_one_proxy_factory) {
111
+ ->(query:, loader:, preloaded_data:) {
112
+ SequelMapper::LazyObjectProxy.new(
113
+ ->{ loader.call(query.first) },
114
+ preloaded_data,
115
+ )
116
+ }
117
+ }
118
+
119
+ let(:serializers) {
120
+ {
121
+ default: default_serializer,
122
+ null: null_serializer,
123
+ }
124
+ }
125
+
126
+ let(:configs) {
127
+ {
128
+ users: {
129
+ namespace: :users,
130
+ primary_key: [:id],
131
+ fields: [
132
+ :id,
133
+ :first_name,
134
+ :last_name,
135
+ :email,
136
+ ],
137
+ factory: :user,
138
+ serializer: :default,
139
+ associations: {
140
+ posts: {
141
+ type: :one_to_many,
142
+ mapping_name: :posts,
143
+ foreign_key: :author_id,
144
+ key: :id,
145
+ proxy_factory: has_many_proxy_factory,
146
+ }
147
+ },
148
+ },
149
+
150
+ posts: {
151
+ namespace: :posts,
152
+ primary_key: [:id],
153
+ fields: [
154
+ :id,
155
+ :subject,
156
+ :body,
157
+ :created_at,
158
+ ],
159
+ factory: :post,
160
+ serializer: :default,
161
+ associations: {
162
+ comments: {
163
+ type: :one_to_many,
164
+ mapping_name: :comments,
165
+ foreign_key: :post_id,
166
+ key: :id,
167
+ proxy_factory: has_many_proxy_factory,
168
+ },
169
+ categories: {
170
+ type: :many_to_many,
171
+ mapping_name: :categories,
172
+ key: :id,
173
+ foreign_key: :post_id,
174
+ association_foreign_key: :category_id,
175
+ association_key: :id,
176
+ join_mapping_name: :categories_to_posts,
177
+ proxy_factory: has_many_proxy_factory,
178
+ },
179
+ },
180
+ },
181
+
182
+ comments: {
183
+ namespace: :comments,
184
+ primary_key: [:id],
185
+ fields: [
186
+ :id,
187
+ :body,
188
+ ],
189
+ factory: :comment,
190
+ serializer: :default,
191
+ associations: {
192
+ commenter: {
193
+ type: :many_to_one,
194
+ mapping_name: :users,
195
+ key: :id,
196
+ foreign_key: :commenter_id,
197
+ proxy_factory: many_to_one_proxy_factory,
198
+ },
199
+ },
200
+ },
201
+
202
+ categories: {
203
+ namespace: :categories,
204
+ primary_key: [:id],
205
+ fields: [
206
+ :id,
207
+ :name,
208
+ ],
209
+ factory: :comment,
210
+ serializer: :default,
211
+ associations: {
212
+ posts: {
213
+ type: :many_to_many,
214
+ mapping_name: :posts,
215
+ key: :id,
216
+ foreign_key: :category_id,
217
+ association_foreign_key: :post_id,
218
+ association_key: :id,
219
+ join_mapping_name: :categories_to_posts,
220
+ proxy_factory: has_many_proxy_factory,
221
+ },
222
+ },
223
+ },
224
+
225
+ categories_to_posts: {
226
+ namespace: :categories_to_posts,
227
+ primary_key: [:category_id, :post_id],
228
+ fields: [],
229
+ serializer: :null,
230
+ associations: {},
231
+ }
232
+ }
233
+ }
234
+ end
@@ -113,7 +113,6 @@ class SequelMapper::MockSequel
113
113
  end
114
114
 
115
115
  def each(&block)
116
- puts "iterating over #{matching_rows}"
117
116
  database.log_read
118
117
 
119
118
  matching_rows.each(&block)
@@ -0,0 +1,106 @@
1
+ require "sequel_mapper/serializer"
2
+ require "sequel_mapper/struct_factory"
3
+
4
+ RSpec.shared_context "object graph setup" do
5
+
6
+ before do
7
+ setup_circular_references_avoiding_stack_overflow
8
+ end
9
+
10
+ def setup_circular_references_avoiding_stack_overflow
11
+ biscuits_post_comment.commenter = hansel
12
+ cat_biscuits_category.posts = [ biscuits_post ]
13
+ end
14
+
15
+ User ||= Struct.new(:id, :first_name, :last_name, :email, :posts, :comments)
16
+ Post ||= Struct.new(:id, :author, :subject, :body, :comments, :categories, :created_at)
17
+ Comment ||= Struct.new(:id, :post, :commenter, :body)
18
+ Category ||= Struct.new(:id, :name, :posts)
19
+
20
+ let(:factories) {
21
+ {
22
+ users: SequelMapper::StructFactory.new(User),
23
+ posts: SequelMapper::StructFactory.new(Post),
24
+ comments: SequelMapper::StructFactory.new(Comment),
25
+ categories: SequelMapper::StructFactory.new(Category),
26
+ categories_to_posts: ->(x){x},
27
+ noop: ->(x){x},
28
+ }
29
+ }
30
+
31
+ let(:default_serializer) {
32
+ ->(fields) {
33
+ ->(object) {
34
+ SequelMapper::Serializer.new(fields, object).to_h
35
+ }
36
+ }
37
+ }
38
+
39
+ let(:null_serializer) {
40
+ ->(_fields) {
41
+ ->(x){x}
42
+ }
43
+ }
44
+
45
+ let(:hansel) {
46
+ factories.fetch(:users).call(
47
+ id: "users/1",
48
+ first_name: "Hansel",
49
+ last_name: "Trickett",
50
+ email: "hansel@tricketts.org",
51
+ posts: [
52
+ biscuits_post,
53
+ sleep_post,
54
+ ],
55
+ )
56
+ }
57
+
58
+ let(:biscuits_post) {
59
+ factories.fetch(:posts).call(
60
+ id: "posts/1",
61
+ subject: "Biscuits",
62
+ body: "I like them",
63
+ comments: [
64
+ biscuits_post_comment,
65
+ ],
66
+ categories: [
67
+ cat_biscuits_category,
68
+ ],
69
+ created_at: Time.parse("2015-09-05T15:00:00+01:00"),
70
+ )
71
+ }
72
+
73
+ let(:sleep_post) {
74
+ factories.fetch(:posts).call(
75
+ id: "posts/2",
76
+ subject: "Sleeping",
77
+ body: "I do it three times purrr day",
78
+ comments: [],
79
+ categories: [
80
+ chilling_category,
81
+ ],
82
+ created_at: Time.parse("2015-09-02T15:00:00+01:00"),
83
+ )
84
+ }
85
+
86
+ let(:biscuits_post_comment) {
87
+ factories.fetch(:comments).call(
88
+ id: "comments/1",
89
+ body: "oh noes",
90
+ )
91
+ }
92
+
93
+ let(:cat_biscuits_category) {
94
+ factories.fetch(:categories).call(
95
+ id: "categories/1",
96
+ name: "Cat biscuits",
97
+ )
98
+ }
99
+
100
+ let(:chilling_category) {
101
+ factories.fetch(:categories).call(
102
+ id: "categories/2",
103
+ name: "Chillaxing",
104
+ )
105
+ }
106
+ end
@@ -0,0 +1,122 @@
1
+ require "support/object_graph_setup"
2
+ RSpec.shared_context "seed data setup" do
3
+ include_context "object graph setup"
4
+
5
+ before {
6
+ insert_records(datastore, seeded_records)
7
+ }
8
+
9
+ let(:seeded_records) {
10
+ [
11
+ [ :users, hansel_record ],
12
+ [ :users, jasper_record ],
13
+ [ :users, poppy_record ],
14
+ [ :posts, biscuits_post_record ],
15
+ [ :posts, sleep_post_record ],
16
+ [ :posts, catch_frogs_post_record ],
17
+ [ :comments, biscuits_post_comment_record ],
18
+ [ :categories, cat_biscuits_category_record ],
19
+ [ :categories, eating_and_sleeping_category_record ],
20
+ *categories_to_posts_records.map { |record|
21
+ [ :categories_to_posts, record ]
22
+ },
23
+ ]
24
+ }
25
+
26
+ let(:hansel_record) {
27
+ {
28
+ id: "users/1",
29
+ first_name: "Hansel",
30
+ last_name: "Trickett",
31
+ email: "hansel@tricketts.org",
32
+ }
33
+ }
34
+
35
+ let(:jasper_record) {
36
+ {
37
+ id: "users/2",
38
+ first_name: "Jasper",
39
+ last_name: "Trickett",
40
+ email: "jasper@tricketts.org",
41
+ }
42
+ }
43
+
44
+ let(:poppy_record) {
45
+ {
46
+ id: "users/3",
47
+ first_name: "Poppy",
48
+ last_name: "Herzog",
49
+ email: "poppy@herzog.info",
50
+ }
51
+ }
52
+
53
+ let(:biscuits_post_record) {
54
+ {
55
+ id: "posts/1",
56
+ subject: "Biscuits",
57
+ body: "I like them",
58
+ author_id: "users/1",
59
+ created_at: Time.parse("2015-09-05T15:00:00+01:00"),
60
+ }
61
+ }
62
+
63
+ let(:sleep_post_record) {
64
+ {
65
+ id: "posts/2",
66
+ subject: "Sleeping",
67
+ body: "I do it three times purrr day",
68
+ author_id: "users/1",
69
+ created_at: Time.parse("2015-09-02T15:00:00+01:00"),
70
+ }
71
+ }
72
+
73
+ let(:catch_frogs_post_record) {
74
+ {
75
+ id: "posts/3",
76
+ subject: "Catching frongs",
77
+ body: "I love them while at the same time I hate them",
78
+ author_id: "users/2",
79
+ created_at: Time.parse("2015-09-03T15:00:00+01:00"),
80
+ }
81
+ }
82
+
83
+ let(:biscuits_post_comment_record) {
84
+ {
85
+ id: "comments/1",
86
+ body: "oh noes",
87
+ post_id: "posts/1",
88
+ commenter_id: "users/1",
89
+ }
90
+ }
91
+
92
+ let(:cat_biscuits_category_record) {
93
+ {
94
+ id: "categories/1",
95
+ name: "Cat biscuits",
96
+ }
97
+ }
98
+
99
+ let(:eating_and_sleeping_category_record) {
100
+ {
101
+ id: "categories/2",
102
+ name: "Eating and sleeping",
103
+ }
104
+ }
105
+
106
+ let(:categories_to_posts_records) {
107
+ [
108
+ {
109
+ post_id: "posts/1",
110
+ category_id: "categories/1",
111
+ },
112
+ {
113
+ post_id: "posts/1",
114
+ category_id: "categories/2",
115
+ },
116
+ {
117
+ post_id: "posts/2",
118
+ category_id: "categories/2",
119
+ },
120
+ ]
121
+ }
122
+ end