sequel-packer 0.0.2 → 0.1.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -3
- data/README.md +125 -24
- data/lib/sequel/packer/version.rb +1 -1
- data/lib/sequel/packer.rb +98 -30
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1420dc6158b5ec7447d40e2818513a0b73c948fe1f2c9dced0732f1ad10e97db
|
4
|
+
data.tar.gz: e18293bafbe14d0af6c81ddde1e64e026498c61492bc26338bbc9fdbbd08507b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 43da46c32b0f2b068dc7c0c29022ea6ffebd6bf7becaae55185db95626779ed968c82f06d8edb424b6c33430edd58c53e44785ef0b1b9bdb240429b24b70aaa8
|
7
|
+
data.tar.gz: 40d48122b9ce084f0c6b585b805481f2ef057ce2158a505782e2d2b0d2f58db4d1c4fd32a22de4f7178cbe82fdf6013694020558e305a1bd4845086648675731
|
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,13 @@
|
|
1
|
+
### 0.1.0 (2020-05-11)
|
2
|
+
|
3
|
+
* Add traits.
|
4
|
+
|
1
5
|
### 0.0.2 (2020-05-11)
|
2
6
|
|
3
|
-
*
|
7
|
+
* Add support for `Sequel::Packer.field(key, &block)` and
|
4
8
|
`Sequel::Packer.field(&block)`
|
5
|
-
*
|
6
|
-
*
|
9
|
+
* Add validation to `Sequel::Packer::field` to detect incorrect usage.
|
10
|
+
* Add support for nested packing of associations using
|
7
11
|
`Sequel::Packer.field(association, packer_class)`
|
8
12
|
* Update README with usage instructions and basic API reference.
|
9
13
|
|
data/README.md
CHANGED
@@ -49,6 +49,8 @@ class Comment < Sequel::Model(:comments)
|
|
49
49
|
end
|
50
50
|
```
|
51
51
|
|
52
|
+
### Basic Fields
|
53
|
+
|
52
54
|
Suppose an endpoint wants to fetch all the ten most recent comments by a user.
|
53
55
|
After validating the user id, we end up with the Sequel dataset represting the
|
54
56
|
data we want to return:
|
@@ -77,17 +79,54 @@ This can then be used as follows:
|
|
77
79
|
```ruby
|
78
80
|
CommentPacker.new.pack(recent_comments)
|
79
81
|
=> [
|
80
|
-
{id: 536, "Great post, man!"},
|
81
|
-
{id: 436, "lol"},
|
82
|
-
{id: 413, "What a story..."},
|
82
|
+
{id: 536, content: "Great post, man!"},
|
83
|
+
{id: 436, content: "lol"},
|
84
|
+
{id: 413, content: "What a story..."},
|
83
85
|
]
|
84
86
|
```
|
85
87
|
|
86
|
-
|
87
|
-
with the authors of those comments.
|
88
|
+
### Packing associations by nesting Packers
|
88
89
|
|
89
|
-
|
90
|
-
for
|
90
|
+
Now, suppose that we want to fetch a post and all of its comments. We can do
|
91
|
+
this by defining another packer for Post that uses the CommentPacker:
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
class PostPacker < Sequel::Packer
|
95
|
+
model Post
|
96
|
+
|
97
|
+
field :id
|
98
|
+
field :title
|
99
|
+
field :content
|
100
|
+
field :comments, CommentPacker
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
Since `post.comments` is an array of Sequel::Models and not a primitive value,
|
105
|
+
we must tell the Packer how to serialize them using another packer. This is
|
106
|
+
what the second argument in `field :comments, CommentPacker` is doing.
|
107
|
+
|
108
|
+
We can then use this as follows:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
PostPacker.new.pack(Post.order(:id.desc).limit(1))
|
112
|
+
=> [
|
113
|
+
{
|
114
|
+
id: 682,
|
115
|
+
title: "Announcing sequel-packer",
|
116
|
+
content: "I've written a new gem...",
|
117
|
+
comments: [
|
118
|
+
{id: 536, content: "Great post, man!"},
|
119
|
+
{id: 541, content: "Incredible, this solves my EXACT problem!"},
|
120
|
+
...
|
121
|
+
],
|
122
|
+
}
|
123
|
+
]
|
124
|
+
```
|
125
|
+
|
126
|
+
### Traits
|
127
|
+
|
128
|
+
But suppose we want to be able to show who authored each comment on the post. We
|
129
|
+
first have to define a packer for users:
|
91
130
|
|
92
131
|
```ruby
|
93
132
|
class UserPacker < Sequel::Packer
|
@@ -96,37 +135,94 @@ class UserPacker < Sequel::Packer
|
|
96
135
|
field :id
|
97
136
|
field :name
|
98
137
|
end
|
138
|
+
```
|
99
139
|
|
140
|
+
We could now define a new packer, `CommentWithAuthorPacker`, and use that in the
|
141
|
+
PostPacker instead, but then we'd have to redeclare all the other fields we want
|
142
|
+
on a packed Comment:
|
143
|
+
|
144
|
+
```ruby
|
100
145
|
class CommentWithAuthorPacker < Sequel::Packer
|
101
146
|
model Comment
|
102
147
|
|
148
|
+
field :author, UserPacker
|
149
|
+
|
150
|
+
# Also defined in CommentPacker!
|
103
151
|
field :id
|
104
152
|
field :content
|
105
|
-
|
153
|
+
end
|
154
|
+
|
155
|
+
class PostPacker < Sequel::Packer
|
156
|
+
...
|
157
|
+
|
158
|
+
# Eww!
|
159
|
+
- field :comments, CommentPacker
|
160
|
+
+ field :comments, CommentWithAuthorPacker
|
106
161
|
end
|
107
162
|
```
|
108
163
|
|
109
|
-
|
164
|
+
Declaring these fields in two places could cause them to get out of sync
|
165
|
+
as more fields are added. Instead, we will use a _trait_. A _trait_ is a
|
166
|
+
way to define a set of fields that we only want to pack sometimes. Instead
|
167
|
+
of defining a totally new packer, we can extend the CommentPacker as follows:
|
110
168
|
|
111
169
|
```ruby
|
112
|
-
|
113
|
-
|
170
|
+
class CommentPacker < Sequel::Packer
|
171
|
+
model Comment
|
172
|
+
|
173
|
+
field :id
|
174
|
+
field :content
|
175
|
+
|
176
|
+
# Added!
|
177
|
+
trait :author do
|
178
|
+
field :author, UserPacker
|
179
|
+
end
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
183
|
+
To use a trait, simply pass it in when creating the packer instance:
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
# Without the trait
|
187
|
+
CommentPacker.new.pack(Comment.dataset)
|
188
|
+
=> [
|
189
|
+
{id: 536, content: "Great post, man!"},
|
190
|
+
...
|
191
|
+
]
|
192
|
+
|
193
|
+
# With the trait
|
194
|
+
CommentPacker.new(:author).pack(Comment.dataset)
|
114
195
|
=> [
|
115
196
|
{
|
116
|
-
id:
|
117
|
-
content: "Great
|
118
|
-
author: {id:
|
119
|
-
},
|
120
|
-
{
|
121
|
-
id: 162,
|
122
|
-
content: "Never seen anything like it",
|
123
|
-
author: {id: 382, name: "Donald Knuth"},
|
197
|
+
id: 536,
|
198
|
+
content: "Great post, man!",
|
199
|
+
author: {id: 1, name: "Paul Martinez"},
|
124
200
|
},
|
125
201
|
...
|
126
202
|
]
|
127
203
|
```
|
128
204
|
|
129
|
-
|
205
|
+
To use a trait when packing an association in another packer, simply include
|
206
|
+
the name of the trait as additional argument to `field`. Thus, to modify our
|
207
|
+
PostPacker to pack comments with their authors we make the following change:
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
class PostPacker < Sequel::Packer
|
211
|
+
model Post
|
212
|
+
|
213
|
+
field :id
|
214
|
+
field :title
|
215
|
+
field :content
|
216
|
+
|
217
|
+
- field :comments, CommentPacker
|
218
|
+
+ field :comments, CommentPacker, :author
|
219
|
+
end
|
220
|
+
```
|
221
|
+
|
222
|
+
While the basic Packer DSL is convenient, traits are the things that make
|
223
|
+
Packers so powerful. Each packer should define a small set of fields that every
|
224
|
+
endpoint needs, but then traits can be used to pack additional data only when
|
225
|
+
it's needed.
|
130
226
|
|
131
227
|
## API Reference
|
132
228
|
|
@@ -194,15 +290,15 @@ class MyPacker < Sequel::Packer
|
|
194
290
|
end
|
195
291
|
```
|
196
292
|
|
197
|
-
### `self.field(association, packer_class)
|
293
|
+
### `self.field(association, packer_class, *traits)
|
198
294
|
|
199
295
|
A Sequel association (defined in the model file using `one_to_many`, or
|
200
|
-
`many_to_one`, etc.), can be packed using another Packer class
|
201
|
-
output could be generated by doing:
|
296
|
+
`many_to_one`, etc.), can be packed using another Packer class, possibly with
|
297
|
+
multiple traits specified. A similar output could be generated by doing:
|
202
298
|
|
203
299
|
```ruby
|
204
300
|
field :association do |model|
|
205
|
-
packer_class.new.pack(model.association_dataset)
|
301
|
+
packer_class.new(*traits).pack(model.association_dataset)
|
206
302
|
end
|
207
303
|
```
|
208
304
|
|
@@ -222,6 +318,11 @@ field do |model, hash|
|
|
222
318
|
end
|
223
319
|
```
|
224
320
|
|
321
|
+
### `initialize(*traits)`
|
322
|
+
|
323
|
+
When creating an instance of a Packer class, pass in any traits desired to
|
324
|
+
specify what additional data should be packed, if any.
|
325
|
+
|
225
326
|
### `pack(dataset)`
|
226
327
|
|
227
328
|
After creating a new instance of a Packer class, call `packer.pack(dataset)` to
|
data/lib/sequel/packer.rb
CHANGED
@@ -7,6 +7,7 @@ module Sequel
|
|
7
7
|
|
8
8
|
def self.inherited(subclass)
|
9
9
|
subclass.instance_variable_set(:@fields, [])
|
10
|
+
subclass.instance_variable_set(:@traits, {})
|
10
11
|
end
|
11
12
|
|
12
13
|
def self.model(klass)
|
@@ -31,7 +32,45 @@ module Sequel
|
|
31
32
|
# field(&block)
|
32
33
|
ARBITRARY_MODIFICATION_FIELD = :arbitrary_modification_field
|
33
34
|
|
34
|
-
def self.field(field_name=nil, packer_class=nil, &block)
|
35
|
+
def self.field(field_name=nil, packer_class=nil, *traits, &block)
|
36
|
+
validate_field_args(field_name, packer_class, *traits, &block)
|
37
|
+
field_type = determine_field_type(field_name, packer_class, &block)
|
38
|
+
|
39
|
+
@fields << {
|
40
|
+
type: field_type,
|
41
|
+
name: field_name,
|
42
|
+
packer: packer_class,
|
43
|
+
packer_traits: traits,
|
44
|
+
block: block,
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
private_class_method def self.determine_field_type(
|
49
|
+
field_name=nil,
|
50
|
+
packer_class=nil,
|
51
|
+
&block
|
52
|
+
)
|
53
|
+
if block
|
54
|
+
if field_name
|
55
|
+
BLOCK_FIELD
|
56
|
+
else
|
57
|
+
ARBITRARY_MODIFICATION_FIELD
|
58
|
+
end
|
59
|
+
else
|
60
|
+
if packer_class
|
61
|
+
ASSOCIATION_FIELD
|
62
|
+
else
|
63
|
+
METHOD_FIELD
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private_class_method def self.validate_field_args(
|
69
|
+
field_name=nil,
|
70
|
+
packer_class=nil,
|
71
|
+
*traits,
|
72
|
+
&block
|
73
|
+
)
|
35
74
|
fail ModelNotYetDeclaredError if !@model
|
36
75
|
|
37
76
|
# This check applies to all invocations:
|
@@ -48,7 +87,7 @@ module Sequel
|
|
48
87
|
# field :foo {|model| ...}
|
49
88
|
# or field {|model, hash| ...}
|
50
89
|
#
|
51
|
-
if packer_class
|
90
|
+
if packer_class || traits.any?
|
52
91
|
raise(
|
53
92
|
FieldArgumentError,
|
54
93
|
'When passing a block to Sequel::Packer::field, either pass the ' +
|
@@ -90,7 +129,7 @@ module Sequel
|
|
90
129
|
)
|
91
130
|
end
|
92
131
|
|
93
|
-
if packer_class
|
132
|
+
if packer_class || traits.any?
|
94
133
|
if !@model.associations.include?(field_name)
|
95
134
|
raise(
|
96
135
|
FieldArgumentError,
|
@@ -123,6 +162,17 @@ module Sequel
|
|
123
162
|
"(#{association_model})",
|
124
163
|
)
|
125
164
|
end
|
165
|
+
|
166
|
+
packer_class_traits = packer_class.instance_variable_get(:@traits)
|
167
|
+
traits.each do |trait|
|
168
|
+
if !packer_class_traits.key?(trait)
|
169
|
+
raise(
|
170
|
+
FieldArgumentError,
|
171
|
+
"Trait :#{trait} isn't defined for #{packer_class} used to " +
|
172
|
+
"pack #{field_name} association.",
|
173
|
+
)
|
174
|
+
end
|
175
|
+
end
|
126
176
|
else
|
127
177
|
if @model.associations.include?(field_name)
|
128
178
|
raise(
|
@@ -134,37 +184,34 @@ module Sequel
|
|
134
184
|
end
|
135
185
|
end
|
136
186
|
end
|
187
|
+
end
|
137
188
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
end
|
145
|
-
else
|
146
|
-
if packer_class
|
147
|
-
ASSOCIATION_FIELD
|
148
|
-
else
|
149
|
-
METHOD_FIELD
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
@fields << {
|
154
|
-
type: field_type,
|
155
|
-
name: field_name,
|
156
|
-
packer: packer_class,
|
157
|
-
block: block,
|
158
|
-
}
|
189
|
+
def self.trait(name, &block)
|
190
|
+
raise ArgumentError, "Trait :#{name} already defined" if @traits.key?(name)
|
191
|
+
if !block_given?
|
192
|
+
raise ArgumentError, 'Must give a block when defining a trait'
|
193
|
+
end
|
194
|
+
@traits[name] = block
|
159
195
|
end
|
160
196
|
|
161
|
-
def initialize
|
197
|
+
def initialize(*traits)
|
162
198
|
@packers = nil
|
199
|
+
@fields = traits.any? ? packer_fields.dup : packer_fields
|
200
|
+
|
201
|
+
traits.each do |trait|
|
202
|
+
trait_block = trait_blocks[trait]
|
203
|
+
if !trait_block
|
204
|
+
raise ArgumentError, "Unknown trait for #{self.class}: :#{trait}"
|
205
|
+
end
|
206
|
+
|
207
|
+
self.instance_exec(&trait_block)
|
208
|
+
end
|
163
209
|
|
164
|
-
fields.each do |field_options|
|
210
|
+
@fields.each do |field_options|
|
165
211
|
if field_options[:type] == ASSOCIATION_FIELD
|
166
212
|
@packers ||= {}
|
167
|
-
@packers[field_options[:name]] =
|
213
|
+
@packers[field_options[:name]] =
|
214
|
+
field_options[:packer].new(*field_options[:packer_traits])
|
168
215
|
end
|
169
216
|
end
|
170
217
|
end
|
@@ -177,7 +224,7 @@ module Sequel
|
|
177
224
|
def pack_model(model)
|
178
225
|
h = {}
|
179
226
|
|
180
|
-
fields.each do |field_options|
|
227
|
+
@fields.each do |field_options|
|
181
228
|
field_name = field_options[:name]
|
182
229
|
|
183
230
|
case field_options[:type]
|
@@ -193,7 +240,7 @@ module Sequel
|
|
193
240
|
elsif associated_objects.is_a?(Array)
|
194
241
|
h[field_name] = @packers[field_name].pack_models(associated_objects)
|
195
242
|
else
|
196
|
-
@packers[field_name].pack_model(associated_objects)
|
243
|
+
h[field_name] = @packers[field_name].pack_model(associated_objects)
|
197
244
|
end
|
198
245
|
when ARBITRARY_MODIFICATION_FIELD
|
199
246
|
field_options[:block].call(model, h)
|
@@ -209,10 +256,31 @@ module Sequel
|
|
209
256
|
|
210
257
|
private
|
211
258
|
|
212
|
-
def
|
259
|
+
def field(field_name=nil, packer_class=nil, *traits, &block)
|
260
|
+
klass = self.class
|
261
|
+
klass.send(
|
262
|
+
:validate_field_args, field_name, packer_class, *traits, &block)
|
263
|
+
field_type =
|
264
|
+
klass.send(:determine_field_type, field_name, packer_class, &block)
|
265
|
+
|
266
|
+
@fields << {
|
267
|
+
type: field_type,
|
268
|
+
name: field_name,
|
269
|
+
packer: packer_class,
|
270
|
+
packer_traits: traits,
|
271
|
+
block: block,
|
272
|
+
}
|
273
|
+
end
|
274
|
+
|
275
|
+
def packer_fields
|
213
276
|
self.class.instance_variable_get(:@fields)
|
214
277
|
end
|
278
|
+
|
279
|
+
def trait_blocks
|
280
|
+
self.class.instance_variable_get(:@traits)
|
281
|
+
end
|
215
282
|
end
|
216
283
|
end
|
217
284
|
|
218
285
|
require "sequel/packer/version"
|
286
|
+
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel-packer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Julius Martinez
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-05-
|
11
|
+
date: 2020-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|