sequel-packer 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|