sequel-packer 0.4.0 → 0.5.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 +8 -0
- data/README.md +306 -72
- data/lib/sequel/packer.rb +194 -63
- data/lib/sequel/packer/version.rb +1 -1
- 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: fa3f2c4a293512f8a86e4acaf01c77e0554a42eb5f8847a6c33bfe8bff03330b
|
4
|
+
data.tar.gz: 8abeec112ff65793ced679bbf1f7aa370c9f3f6f08bdb63b1d5adb7cfc259833
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea64c4d227fd832b232d2833ccae7948208c012cd2b890f44bed919af86c6f828dbfd28de0d925fed3e218f96837b409251bc6f9ea7e8d0ff7802da63d21313f
|
7
|
+
data.tar.gz: 93bd50aee1d410b117ee2c2993d85dc6c53bae5e5204b6be37d413ab160f92a120725cb6cfc6f6c00744a9c12ff1b8db30aea7f8c0593fc9a7fb3831cca258c0
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
### 0.5.0 (2020-05-17)
|
2
|
+
|
3
|
+
* Add `**context` argument to `#pack` method, exposed as `@context` in blocks
|
4
|
+
passed to `field` and `trait`.
|
5
|
+
* Add `::with_context(&block)`, for accessing `@context` to use in additional
|
6
|
+
DSL calls, or modify data fetching.
|
7
|
+
* Update README some re-organization and table of contents.
|
8
|
+
|
1
9
|
### 0.4.0 (2020-05-17)
|
2
10
|
|
3
11
|
* **_BREAKING CHANGE:_** `#pack_models` and `#pack_model` have been changed to
|
data/README.md
CHANGED
@@ -3,22 +3,128 @@
|
|
3
3
|
`Sequel::Packer` is a Ruby JSON serialization library to be used with the Sequel
|
4
4
|
ORM offering the following features:
|
5
5
|
|
6
|
-
*
|
6
|
+
* **Declarative:** Define the shape of your serialized data with a simple,
|
7
7
|
straightforward DSL.
|
8
|
-
*
|
8
|
+
* **Flexible:** Certain contexts require different data. Packers provide an easy
|
9
9
|
way to opt-in to serializing certain data only when you need it. The library
|
10
10
|
also provides convenient escape hatches when you need to do something not
|
11
11
|
explicitly supported by the API.
|
12
|
-
*
|
12
|
+
* **Reusable:** The Packer library naturally composes well with itself. Nested
|
13
13
|
data can be serialized in the same way no matter what endpoint it's fetched
|
14
14
|
from.
|
15
|
-
*
|
15
|
+
* **Efficient:** When not using Sequel's
|
16
16
|
[`TacticalEagerLoading`](https://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/TacticalEagerLoading.html)
|
17
17
|
plugin, the Packer library will intelligently determine which associations
|
18
18
|
and nested associations it needs to eager load in order to avoid any N+1 query
|
19
19
|
issues.
|
20
20
|
|
21
|
-
|
21
|
+
- [Example](#example)
|
22
|
+
- [Getting Started](#getting-started)
|
23
|
+
- [Installation](#installation)
|
24
|
+
- [Example Schema](#example-schema)
|
25
|
+
- [Basic Fields](#basic-fields)
|
26
|
+
- [Packing Associations by Nesting Packers](#packing-associations-by-nesting-packers)
|
27
|
+
- [Traits](#traits)
|
28
|
+
- [API Reference](#api-reference)
|
29
|
+
- [Using a Packer](#using-a-packer)
|
30
|
+
- [Defining a Packer](#defining-a-packer)
|
31
|
+
- [`self.model(sequel_model_class)`](#selfmodelsequel_model_class)
|
32
|
+
- [`self.field(column_name)` (or `self.field(method_name)`)](#selffieldcolumn_name-or-selffieldmethod_name)
|
33
|
+
- [`self.field(key, &block)`](#selffieldkey-block)
|
34
|
+
- [`self.field(association, subpacker, *traits)`](#selffieldassociation-subpacker-traits)
|
35
|
+
- [`self.field(&block)`](#selffieldblock)
|
36
|
+
- [`self.trait(trait_name, &block)`](#selftraittrait_name-block)
|
37
|
+
- [`self.eager(*associations)`](#selfeagerassociations)
|
38
|
+
- [`self.set_association_packer(association, subpacker, *traits)`](#selfset_association_packerassociation-subpacker-traits)
|
39
|
+
- [`self.pack_association(association, models)`](#selfpack_associationassociation-models)
|
40
|
+
- [`self.precompute(&block)`](#selfprecomputeblock)
|
41
|
+
- [Context](#context)
|
42
|
+
- [`self.with_context(&block)`](#selfwith_contextblock)
|
43
|
+
- [Contributing](#contributing)
|
44
|
+
- [Development](#development)
|
45
|
+
- [Releases](#releases)
|
46
|
+
- [License](#license)
|
47
|
+
|
48
|
+
## Example
|
49
|
+
|
50
|
+
`Sequel::Packer` uses your existing `Sequel::Model` declarations and leverages
|
51
|
+
the use of associations to efficiently serialize data.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class User < Sequel::Model(:users)
|
55
|
+
one_to_many :posts
|
56
|
+
end
|
57
|
+
class Post < Sequel::Model(:posts); end
|
58
|
+
```
|
59
|
+
|
60
|
+
Packer definitions use a simple domain-specific language (DSL) to declare which
|
61
|
+
fields to serialize:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
class PostPacker < Sequel::Packer
|
65
|
+
model Post
|
66
|
+
|
67
|
+
field :id
|
68
|
+
field :title
|
69
|
+
|
70
|
+
trait :truncated_content do
|
71
|
+
field :truncated_content do |post|
|
72
|
+
post.content[0..Post::PREVIEW_LENGTH]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class UserPacker < Sequel::Packer
|
78
|
+
model User
|
79
|
+
|
80
|
+
field :id
|
81
|
+
field :name
|
82
|
+
|
83
|
+
trait :posts do
|
84
|
+
field :posts, PostPacker, :truncated_content
|
85
|
+
end
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
Once defined, Packers are easy to use; just call `.pack` and pass in a Sequel
|
90
|
+
dataset, an array of models, or a single model, and get back Ruby hashes.
|
91
|
+
Simply call `to_json` on the result!
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
UserPacker.pack(User.dataset)
|
95
|
+
=> [
|
96
|
+
{id: 1, name: 'Paul'},
|
97
|
+
{id: 2, name: 'Julius'},
|
98
|
+
...
|
99
|
+
]
|
100
|
+
|
101
|
+
UserPacker.pack(User[1], :posts)
|
102
|
+
=> {
|
103
|
+
id: 1,
|
104
|
+
name: 'Paul',
|
105
|
+
posts: [
|
106
|
+
{
|
107
|
+
id: 15,
|
108
|
+
title: 'Announcing Sequel::Packer!',
|
109
|
+
truncated_content: 'Sequel::Packer is a new gem...',
|
110
|
+
},
|
111
|
+
{
|
112
|
+
id: 21,
|
113
|
+
title: 'Postgres Internals',
|
114
|
+
truncated_content: 'I never quite understood autovacuum...',
|
115
|
+
},
|
116
|
+
...
|
117
|
+
],
|
118
|
+
}
|
119
|
+
```
|
120
|
+
|
121
|
+
## Getting Started
|
122
|
+
|
123
|
+
This section will explain the basic use of `Sequel::Packer`. Check out the [API
|
124
|
+
Reference](#api-reference) for an exhaustive coverage of the API and more
|
125
|
+
detailed documentation.
|
126
|
+
|
127
|
+
### Installation
|
22
128
|
|
23
129
|
Add this line to your application's Gemfile:
|
24
130
|
|
@@ -34,9 +140,9 @@ Or install it yourself as:
|
|
34
140
|
|
35
141
|
$ gem install sequel-packer
|
36
142
|
|
37
|
-
|
143
|
+
### Example Schema
|
38
144
|
|
39
|
-
|
145
|
+
Most of the following examples will use the following database schema:
|
40
146
|
|
41
147
|
```ruby
|
42
148
|
DB.create_table(:users) do
|
@@ -72,7 +178,7 @@ end
|
|
72
178
|
### Basic Fields
|
73
179
|
|
74
180
|
Suppose an endpoint wants to fetch all the ten most recent comments by a user.
|
75
|
-
After validating the user id, we end up with the Sequel dataset
|
181
|
+
After validating the user id, we end up with the Sequel dataset representing the
|
76
182
|
data we want to return:
|
77
183
|
|
78
184
|
```ruby
|
@@ -97,7 +203,7 @@ end
|
|
97
203
|
This can then be used as follows:
|
98
204
|
|
99
205
|
```ruby
|
100
|
-
CommentPacker.
|
206
|
+
CommentPacker.pack(recent_comments)
|
101
207
|
=> [
|
102
208
|
{id: 536, content: "Great post, man!"},
|
103
209
|
{id: 436, content: "lol"},
|
@@ -105,10 +211,10 @@ CommentPacker.new.pack(recent_comments)
|
|
105
211
|
]
|
106
212
|
```
|
107
213
|
|
108
|
-
### Packing
|
214
|
+
### Packing Associations by Nesting Packers
|
109
215
|
|
110
216
|
Now, suppose that we want to fetch a post and all of its comments. We can do
|
111
|
-
this by defining another packer for Post that uses the CommentPacker
|
217
|
+
this by defining another packer for `Post` that uses the `CommentPacker`:
|
112
218
|
|
113
219
|
```ruby
|
114
220
|
class PostPacker < Sequel::Packer
|
@@ -121,14 +227,15 @@ class PostPacker < Sequel::Packer
|
|
121
227
|
end
|
122
228
|
```
|
123
229
|
|
124
|
-
Since `post.comments` is an array of Sequel::Models and not a primitive value,
|
125
|
-
we must tell the Packer how to serialize them using another packer.
|
126
|
-
|
230
|
+
Since `post.comments` is an array of `Sequel::Models` and not a primitive value,
|
231
|
+
we must tell the Packer how to serialize them using another packer. The second
|
232
|
+
argument in `field :comments, CommentPacker` tells the `PostPacker` to use the
|
233
|
+
pack those comments using the `CommentPacker`.
|
127
234
|
|
128
235
|
We can then use this as follows:
|
129
236
|
|
130
237
|
```ruby
|
131
|
-
PostPacker.
|
238
|
+
PostPacker.pack(Post[validated_id])
|
132
239
|
=> [
|
133
240
|
{
|
134
241
|
id: 682,
|
@@ -158,8 +265,8 @@ end
|
|
158
265
|
```
|
159
266
|
|
160
267
|
We could now define a new packer, `CommentWithAuthorPacker`, and use that in the
|
161
|
-
PostPacker instead, but then we'd have to redeclare all the other fields we
|
162
|
-
on a packed Comment
|
268
|
+
`PostPacker` instead, but then we'd have to redeclare all the other fields we
|
269
|
+
want on a packed `Comment`:
|
163
270
|
|
164
271
|
```ruby
|
165
272
|
class CommentWithAuthorPacker < Sequel::Packer
|
@@ -184,7 +291,7 @@ end
|
|
184
291
|
Declaring these fields in two places could cause them to get out of sync
|
185
292
|
as more fields are added. Instead, we will use a _trait_. A _trait_ is a
|
186
293
|
way to define a set of fields that we only want to pack sometimes. Instead
|
187
|
-
of defining a totally new packer, we can extend the CommentPacker as follows:
|
294
|
+
of defining a totally new packer, we can extend the `CommentPacker` as follows:
|
188
295
|
|
189
296
|
```ruby
|
190
297
|
class CommentPacker < Sequel::Packer
|
@@ -199,18 +306,18 @@ class CommentPacker < Sequel::Packer
|
|
199
306
|
end
|
200
307
|
```
|
201
308
|
|
202
|
-
To use a trait, simply pass it in when
|
309
|
+
To use a trait, simply pass it in when calling `pack`:
|
203
310
|
|
204
311
|
```ruby
|
205
312
|
# Without the trait
|
206
|
-
CommentPacker.
|
313
|
+
CommentPacker.pack(Comment.dataset)
|
207
314
|
=> [
|
208
315
|
{id: 536, content: "Great post, man!"},
|
209
316
|
...
|
210
317
|
]
|
211
318
|
|
212
319
|
# With the trait
|
213
|
-
CommentPacker.
|
320
|
+
CommentPacker.pack(Comment.dataset, :author)
|
214
321
|
=> [
|
215
322
|
{
|
216
323
|
id: 536,
|
@@ -249,13 +356,64 @@ Custom packers are written by creating subclasses of `Sequel::Packer`. This
|
|
249
356
|
class defines a DSL for declaring how a Sequel Model will be converted into a
|
250
357
|
plain Ruby hash.
|
251
358
|
|
252
|
-
###
|
359
|
+
### Using a Packer
|
360
|
+
|
361
|
+
Using a Packer is dead simple. There's a single class method:
|
362
|
+
|
363
|
+
```ruby
|
364
|
+
self.pack(data, *traits, **context)
|
365
|
+
```
|
366
|
+
|
367
|
+
`data` can be in the form of a Sequel dataset, an array of Sequel models, or
|
368
|
+
a single Sequel model. No matter which form the data is passed in, the Packer
|
369
|
+
class will ensure nested data is efficiently loaded.
|
370
|
+
|
371
|
+
To pack additional fields defined in a trait, pass the name of the trait as an
|
372
|
+
additional argument, e.g., `UserPacker.pack(users, :recent_posts)` to include
|
373
|
+
recent posts with each user.
|
374
|
+
|
375
|
+
Finally, additional context can be provided to the Packer by passing additional
|
376
|
+
keyword arguments to `pack`. This context is handled opaquely by the Packer, but
|
377
|
+
it can be accessed in the blocks passed to `field` declarations. Common uses of
|
378
|
+
`context` include passing in the current user making a request, or passing in
|
379
|
+
additional precomputed data.
|
380
|
+
|
381
|
+
The implementation of `pack` is very simple. It creates an instance of a Packer,
|
382
|
+
by passing in the traits and the context, then calls `pack` on that instance,
|
383
|
+
and passes in the data:
|
384
|
+
|
385
|
+
```ruby
|
386
|
+
def self.pack(data, *traits, **context)
|
387
|
+
return nil if !data # small easy optimization to avoid unnecessary work
|
388
|
+
new(*traits, **context).pack(data)
|
389
|
+
end
|
390
|
+
```
|
391
|
+
|
392
|
+
It simply combines a constructor and single exposed instance method:
|
393
|
+
|
394
|
+
#### `initialize(*traits, **context)`
|
395
|
+
|
396
|
+
#### `pack(data)`
|
397
|
+
|
398
|
+
One instantiated, the same Packer could be used to pack data multiple times.
|
399
|
+
This is unlikely to be needed, but the functionality is there.
|
400
|
+
|
401
|
+
### Defining a Packer
|
402
|
+
|
403
|
+
#### `self.model(sequel_model_class)`
|
253
404
|
|
254
405
|
The beginning of each Packer class must begin with `model MySequelModel`, which
|
255
406
|
specifies which Sequel Model this Packer class will serialize. This is mostly
|
256
|
-
to catch certain errors at load time, rather than at run time
|
407
|
+
to catch certain errors at load time, rather than at run time:
|
257
408
|
|
258
|
-
|
409
|
+
```ruby
|
410
|
+
class UserPacker < Sequel::Packer
|
411
|
+
model User
|
412
|
+
...
|
413
|
+
end
|
414
|
+
```
|
415
|
+
|
416
|
+
#### `self.field(column_name)` (or `self.field(method_name)`)
|
259
417
|
|
260
418
|
Defining the shape of the outputted data is done using the `field` method, which
|
261
419
|
exists in four different variants. This first variant is the simplest. It simply
|
@@ -283,7 +441,7 @@ Then when `User.create(first_name: "Paul", last_name: "Martinez")` gets packed
|
|
283
441
|
with `field :full_name` specified, the outputted hash will contain
|
284
442
|
`full_name: "Paul Martinez"`.
|
285
443
|
|
286
|
-
|
444
|
+
#### `self.field(key, &block)`
|
287
445
|
|
288
446
|
A block can be passed to `field` to perform arbitrary computation and store the
|
289
447
|
result under the specified `key`. The block will be passed the model as a single
|
@@ -309,7 +467,7 @@ class MyPacker < Sequel::Packer
|
|
309
467
|
end
|
310
468
|
```
|
311
469
|
|
312
|
-
|
470
|
+
#### `self.field(association, subpacker, *traits)`
|
313
471
|
|
314
472
|
A Sequel association (defined in the model file using `one_to_many`, or
|
315
473
|
`many_to_one`, etc.), can be packed using another Packer class, possibly with
|
@@ -317,15 +475,16 @@ multiple traits specified. A similar output could be generated by doing:
|
|
317
475
|
|
318
476
|
```ruby
|
319
477
|
field :association do |model|
|
320
|
-
|
478
|
+
subpacker.pack(model.association_dataset, *traits)
|
321
479
|
end
|
322
480
|
```
|
323
481
|
|
324
|
-
|
325
|
-
|
326
|
-
|
482
|
+
This form is very inefficient though, because it would result in a new subpacker
|
483
|
+
getting instantiated for every packed model. Additionally, unless the subpacker
|
484
|
+
is declared up-front, the Packer won't know to eager load that association,
|
485
|
+
potentially resulting in many unnecessary database queries.
|
327
486
|
|
328
|
-
|
487
|
+
#### `self.field(&block)`
|
329
488
|
|
330
489
|
Passing a block but no `key` to `field` allows for arbitrary manipulation of the
|
331
490
|
packed hash. The block will be passed the model and the partially packed hash.
|
@@ -338,7 +497,7 @@ field do |model, hash|
|
|
338
497
|
end
|
339
498
|
```
|
340
499
|
|
341
|
-
|
500
|
+
#### `self.trait(trait_name, &block)`
|
342
501
|
|
343
502
|
Define optional serialization behavior by defining additional fields within a
|
344
503
|
`trait` block. Traits can be opted into when initializing a packer by passing
|
@@ -347,15 +506,19 @@ the name of the trait as an argument:
|
|
347
506
|
```ruby
|
348
507
|
class MyPacker < Sequel::Packer
|
349
508
|
model MyObj
|
509
|
+
field :id
|
510
|
+
|
350
511
|
trait :my_trait do
|
351
|
-
field :
|
512
|
+
field :trait_field
|
352
513
|
end
|
353
514
|
end
|
354
515
|
|
355
|
-
# packed objects don't have
|
356
|
-
MyPacker.
|
357
|
-
|
358
|
-
|
516
|
+
# packed objects don't have trait_field
|
517
|
+
MyPacker.pack(dataset)
|
518
|
+
=> [{id: 1}, {id: 2}, ...]
|
519
|
+
# packed objects do have trait_field
|
520
|
+
MyPacker.pack(dataset, :my_trait)
|
521
|
+
=> [{id: 1, trait_field: 'foo'}, {id: 2, trait_field: 'bar'}, ...]
|
359
522
|
```
|
360
523
|
|
361
524
|
Traits can also be used when packing associations by passing the name of the
|
@@ -368,7 +531,7 @@ class MyOtherPacker < Sequel::Packer
|
|
368
531
|
end
|
369
532
|
```
|
370
533
|
|
371
|
-
|
534
|
+
#### `self.eager(*associations)`
|
372
535
|
|
373
536
|
When packing an association, a Packer will automatically ensure that association
|
374
537
|
is eager loaded, but there may be cases when an association will be accessed
|
@@ -392,7 +555,7 @@ class UserPacker < Sequel::Packer
|
|
392
555
|
end
|
393
556
|
end
|
394
557
|
|
395
|
-
UserPacker.
|
558
|
+
UserPacker.pack(User.dataset)
|
396
559
|
=> [
|
397
560
|
{id: 123, num_posts: 7},
|
398
561
|
{id: 456, num_posts: 3},
|
@@ -400,7 +563,7 @@ UserPacker.new.pack(User.dataset)
|
|
400
563
|
]
|
401
564
|
```
|
402
565
|
|
403
|
-
|
566
|
+
Using `eager` can help prevent N+1 query problems when not using Sequel's
|
404
567
|
[`TacticalEagerLoading`](https://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/TacticalEagerLoading.html)
|
405
568
|
plugin.
|
406
569
|
|
@@ -421,22 +584,28 @@ class UserPacker < Sequel::Packer
|
|
421
584
|
end
|
422
585
|
```
|
423
586
|
|
424
|
-
|
425
|
-
|
426
|
-
|
587
|
+
**IMPORTANT NOTE:** Eager procs are not guaranteed to be executed when passing
|
588
|
+
in models, rather than a dataset, to `pack`. Specifically, if the models already
|
589
|
+
have fetched the association, the Packer won't refetch it. Because of this, it's
|
590
|
+
good practice to use `set_association_packer` and `pack_association` (see next
|
591
|
+
section) in a `field` block and duplicate the filtering action.
|
592
|
+
|
593
|
+
Also keep in mind that this limits the association that gets used by ALL fields,
|
594
|
+
so if another field actually needs access to all the users posts, it might not
|
595
|
+
make sense to use `eager`.
|
427
596
|
|
428
|
-
|
429
|
-
multiple procs, each proc will get applied to the dataset, likely resulting
|
430
|
-
overly restrictive filtering.
|
597
|
+
Additionally, it's important to note that if `eager` is called multiple times,
|
598
|
+
with multiple procs, each proc will get applied to the dataset, likely resulting
|
599
|
+
in overly restrictive filtering.
|
431
600
|
|
432
|
-
|
601
|
+
#### `self.set_association_packer(association, subpacker, *traits)`
|
433
602
|
|
434
603
|
See `self.pack_association(association, models)` below.
|
435
604
|
|
436
|
-
|
605
|
+
#### `self.pack_association(association, models)`
|
437
606
|
|
438
607
|
The simplest way to pack an association is to use
|
439
|
-
`self.field(association,
|
608
|
+
`self.field(association, subpacker, *traits)`, but sometimes this doesn't do
|
440
609
|
exactly what we want. We may want to pack the association under a different key
|
441
610
|
than the name of the association. Or we may only want to pack some of the
|
442
611
|
associated models (and it may be difficult or impossible to express which subset
|
@@ -475,7 +644,7 @@ but if it is passed a single model, it will return just that packed model.
|
|
475
644
|
|
476
645
|
Examples:
|
477
646
|
|
478
|
-
|
647
|
+
##### Use a different field name than the name of the association
|
479
648
|
```ruby
|
480
649
|
set_association_packer :ugly_internal_names, InternalPacker
|
481
650
|
field :nice_external_names do |model|
|
@@ -483,7 +652,7 @@ field :nice_external_names do |model|
|
|
483
652
|
end
|
484
653
|
```
|
485
654
|
|
486
|
-
|
655
|
+
##### Pack a single instance of a `one_to_many` association
|
487
656
|
```ruby
|
488
657
|
class PostPacker < Sequel::Packer
|
489
658
|
set_association_packer :comments, CommentPacker
|
@@ -493,7 +662,7 @@ class PostPacker < Sequel::Packer
|
|
493
662
|
end
|
494
663
|
```
|
495
664
|
|
496
|
-
|
665
|
+
#### `self.precompute(&block)`
|
497
666
|
|
498
667
|
Occasionally packing a model may require a computation that doesn't fit in with
|
499
668
|
the rest of the Packer paradigm. This may be a Sequel query that is particularly
|
@@ -532,40 +701,105 @@ class VideoUploadPacker < Sequel::Packer
|
|
532
701
|
end
|
533
702
|
```
|
534
703
|
|
704
|
+
#### Instance method versions
|
535
705
|
|
536
|
-
|
706
|
+
In addition to the class method versions of `field`, `eager`,
|
707
|
+
`set_association_packer`, and `precompute`, there are also regular instance method
|
708
|
+
versions which take the exact same arguments. When writing a `trait` block, the
|
709
|
+
block is evaulated in the context of a new Packer instance and actually calls the
|
710
|
+
instance method versions instead.
|
537
711
|
|
538
|
-
|
539
|
-
specify what additional data should be packed, if any.
|
712
|
+
### Context
|
540
713
|
|
541
|
-
|
714
|
+
In addition to the data to be packed, and a set of traits, the `pack` method
|
715
|
+
also accepts arbitrary keyword arguments. This is referred to as `context` is
|
716
|
+
handled opaquely by the Packer. The data passed in here is saved as the
|
717
|
+
`@context` instance variable, which is then accessible from within the blocks
|
718
|
+
passed to `field`, `trait`, and `precompute`, for whatever purpose. It is also
|
719
|
+
automatically passed to any nested subpackers.
|
542
720
|
|
543
|
-
|
544
|
-
|
721
|
+
The most common usage for context would be to pass in the current user making
|
722
|
+
a request. It could then be used to pack permission levels about records, for
|
723
|
+
example.
|
545
724
|
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
dataset or an array of models, `pack` will return an array of hashes, and when
|
550
|
-
passed just a single model, it will return a single hash.
|
725
|
+
```ruby
|
726
|
+
class PostPacker < Sequel::Packer
|
727
|
+
model Post
|
551
728
|
|
552
|
-
|
729
|
+
eager :permissions
|
730
|
+
field :access_level do |post|
|
731
|
+
user_permission = post.permissions.find do |perm|
|
732
|
+
perm.user_id == @context[:user].id
|
733
|
+
end
|
553
734
|
|
554
|
-
|
555
|
-
|
556
|
-
|
735
|
+
user_permission.access_level
|
736
|
+
end
|
737
|
+
end
|
738
|
+
```
|
557
739
|
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
740
|
+
You might notice something inefficient about the above code. Even though we only
|
741
|
+
want to look at the user's permission record, we fetch ALL of the permission
|
742
|
+
records for each Post. Ideally we would filter the `permissions` association
|
743
|
+
dataset when we call `eager`, but we don't have access to `@context` at that
|
744
|
+
point. This leads to the final DSL method available when writing a Packer:
|
745
|
+
|
746
|
+
#### `self.with_context(&block)`
|
747
|
+
|
748
|
+
You can pass a block to `with_context` that will be executed as soon as a Packer
|
749
|
+
instance is constructed. The block can access `@context` and can also call the
|
750
|
+
standard Packer DSL methods, `field`, `eager`, etc.
|
751
|
+
|
752
|
+
The above example could then be made more efficient as follows:
|
753
|
+
|
754
|
+
```ruby
|
755
|
+
class PostPacker < Sequel::Packer
|
756
|
+
model Post
|
757
|
+
|
758
|
+
- eager :permissions
|
759
|
+
+ with_context do
|
760
|
+
+ eager permissions: (proc {|ds| ds.where(user_id: @context[:user].id)})
|
761
|
+
+ end
|
762
|
+
end
|
763
|
+
```
|
764
|
+
|
765
|
+
A very tricky usage of `with_context` (and not recommended...) would be to
|
766
|
+
control the traits used on subpackers:
|
767
|
+
|
768
|
+
```ruby
|
769
|
+
class UserPacker < Sequel::Packer
|
770
|
+
model User
|
771
|
+
|
772
|
+
with_context do
|
773
|
+
field :comments, CommentPacker, *@context[:comment_traits]
|
774
|
+
end
|
775
|
+
end
|
776
|
+
|
777
|
+
UserPacker.pack(User.dataset, comment_traits: [])
|
778
|
+
=> [{comments: [{id: 7}, ...]}]
|
779
|
+
UserPacker.pack(User.dataset, comment_traits: [:author])
|
780
|
+
=> [{comments: [{id: 7, author: {id: 1, ...}}, ...]}]
|
781
|
+
UserPacker.pack(User.dataset, comment_traits: [:num_likes])
|
782
|
+
=> [{comments: [{id: 7, likes: 53}, ...]}]
|
783
|
+
```
|
563
784
|
|
564
785
|
## Contributing
|
565
786
|
|
566
787
|
Bug reports and pull requests are welcome on GitHub at
|
567
788
|
https://github.com/PaulJuliusMartinez/sequel-packer.
|
568
789
|
|
790
|
+
### Development
|
791
|
+
|
792
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
793
|
+
`rake test` to run the tests. You can also run `bin/console` for an interactive
|
794
|
+
prompt that will allow you to experiment.
|
795
|
+
|
796
|
+
### Releases
|
797
|
+
|
798
|
+
To release a new version, update the version number in
|
799
|
+
`lib/sequel/packer/version.rb`, update the `CHANGELOG.md` with new changes, then
|
800
|
+
run `rake release`, which which will create a git tag for the version, push git
|
801
|
+
commits and tags, and push the `.gem` file to
|
802
|
+
[rubygems.org](https://rubygems.org).
|
569
803
|
|
570
804
|
## License
|
571
805
|
|
data/lib/sequel/packer.rb
CHANGED
@@ -9,15 +9,33 @@ module Sequel
|
|
9
9
|
class AssociationDoesNotExistError < StandardError; end
|
10
10
|
class InvalidAssociationPackerError < StandardError; end
|
11
11
|
class UnknownTraitError < StandardError; end
|
12
|
+
class UnnecessaryWithContextError < StandardError; end
|
13
|
+
class NoAssociationSubpackerDefinedError < StandardError; end
|
12
14
|
|
15
|
+
# Think of this method as the "initialize" method for a Packer class.
|
16
|
+
# Every Packer class keeps track of the fields, traits, and other various
|
17
|
+
# operations defined using the DSL internally.
|
13
18
|
def self.inherited(subclass)
|
14
|
-
subclass.instance_variable_set(:@
|
15
|
-
subclass.instance_variable_set(:@
|
16
|
-
subclass.instance_variable_set(:@
|
17
|
-
subclass.instance_variable_set(:@
|
18
|
-
subclass.instance_variable_set(
|
19
|
+
subclass.instance_variable_set(:@model, @model)
|
20
|
+
subclass.instance_variable_set(:@class_fields, @class_fields&.dup || [])
|
21
|
+
subclass.instance_variable_set(:@class_traits, @class_traits&.dup || {})
|
22
|
+
subclass.instance_variable_set(:@class_packers, @class_packers&.dup || {})
|
23
|
+
subclass.instance_variable_set(
|
24
|
+
:@class_eager_hash,
|
25
|
+
EagerHash.deep_dup(@class_eager_hash),
|
26
|
+
)
|
27
|
+
subclass.instance_variable_set(
|
28
|
+
:@class_precomputations,
|
29
|
+
@class_precomputations&.dup || [],
|
30
|
+
)
|
31
|
+
subclass.instance_variable_set(
|
32
|
+
:@class_with_contexts,
|
33
|
+
@class_with_contexts&.dup || [],
|
34
|
+
)
|
19
35
|
end
|
20
36
|
|
37
|
+
# Declare the type of Sequel::Model this Packer will be used for. Used to
|
38
|
+
# validate associations at declaration time.
|
21
39
|
def self.model(klass)
|
22
40
|
if !(klass < Sequel::Model)
|
23
41
|
fail(
|
@@ -35,18 +53,36 @@ module Sequel
|
|
35
53
|
METHOD_FIELD = :method_field
|
36
54
|
# field(:foo, &block)
|
37
55
|
BLOCK_FIELD = :block_field
|
38
|
-
# field(:association,
|
56
|
+
# field(:association, subpacker)
|
39
57
|
ASSOCIATION_FIELD = :association_field
|
40
58
|
# field(&block)
|
41
59
|
ARBITRARY_MODIFICATION_FIELD = :arbitrary_modification_field
|
42
60
|
|
43
|
-
|
61
|
+
# Declare a field to be packed in the output hash. This method can be called
|
62
|
+
# in multiple ways:
|
63
|
+
#
|
64
|
+
# field(:field_name)
|
65
|
+
# - Calls the method :field_name on a model and stores the result under the
|
66
|
+
# key :field_name in the packed hash.
|
67
|
+
#
|
68
|
+
# field(:field_name, &block)
|
69
|
+
# - Yields the model to the block and stores the result under the key
|
70
|
+
# :field_name in the packed hash.
|
71
|
+
#
|
72
|
+
# field(:association, subpacker, *traits)
|
73
|
+
# - Packs model.association using the designated subpacker with the
|
74
|
+
# specified traits.
|
75
|
+
#
|
76
|
+
# field(&block)
|
77
|
+
# - Yields the model and the partially packed hash to the block, allowing
|
78
|
+
# for arbitrary modification of the output hash.
|
79
|
+
def self.field(field_name=nil, subpacker=nil, *traits, &block)
|
44
80
|
Validation.check_field_arguments(
|
45
|
-
@model, field_name,
|
46
|
-
field_type = determine_field_type(field_name,
|
81
|
+
@model, field_name, subpacker, traits, &block)
|
82
|
+
field_type = determine_field_type(field_name, subpacker, block)
|
47
83
|
|
48
84
|
if field_type == ASSOCIATION_FIELD
|
49
|
-
set_association_packer(field_name,
|
85
|
+
set_association_packer(field_name, subpacker, *traits)
|
50
86
|
end
|
51
87
|
|
52
88
|
@class_fields << {
|
@@ -56,15 +92,10 @@ module Sequel
|
|
56
92
|
}
|
57
93
|
end
|
58
94
|
|
59
|
-
|
60
|
-
Validation.check_association_packer(
|
61
|
-
@model, association, packer_class, traits)
|
62
|
-
@class_packers[association] = [packer_class, traits]
|
63
|
-
end
|
64
|
-
|
95
|
+
# Helper for determing a field type from the arguments to field.
|
65
96
|
private_class_method def self.determine_field_type(
|
66
97
|
field_name,
|
67
|
-
|
98
|
+
subpacker,
|
68
99
|
block
|
69
100
|
)
|
70
101
|
if block
|
@@ -74,7 +105,7 @@ module Sequel
|
|
74
105
|
ARBITRARY_MODIFICATION_FIELD
|
75
106
|
end
|
76
107
|
else
|
77
|
-
if
|
108
|
+
if subpacker
|
78
109
|
ASSOCIATION_FIELD
|
79
110
|
else
|
80
111
|
METHOD_FIELD
|
@@ -82,6 +113,17 @@ module Sequel
|
|
82
113
|
end
|
83
114
|
end
|
84
115
|
|
116
|
+
# Register that nested models related to the packed model by association
|
117
|
+
# should be packed using the given subpacker with the specified traits.
|
118
|
+
def self.set_association_packer(association, subpacker, *traits)
|
119
|
+
Validation.check_association_packer(
|
120
|
+
@model, association, subpacker, traits)
|
121
|
+
@class_packers[association] = [subpacker, traits]
|
122
|
+
end
|
123
|
+
|
124
|
+
# Define a trait, a set of optional fields that can be packed in certain
|
125
|
+
# situations. The block can call main Packer DSL methods: field,
|
126
|
+
# set_association_packer, eager, or precompute.
|
85
127
|
def self.trait(name, &block)
|
86
128
|
if @class_traits.key?(name)
|
87
129
|
raise ArgumentError, "Trait :#{name} already defined"
|
@@ -92,6 +134,14 @@ module Sequel
|
|
92
134
|
@class_traits[name] = block
|
93
135
|
end
|
94
136
|
|
137
|
+
# Specify additional eager loading that should take place when fetching data
|
138
|
+
# to be packed. Commonly used to add filters to association datasets via
|
139
|
+
# eager procs.
|
140
|
+
#
|
141
|
+
# Users should not assume when using eager procs that the proc actually gets
|
142
|
+
# executed. If models with their associations already loaded are passed to
|
143
|
+
# pack then the proc will never get processed. Any filtering logic should be
|
144
|
+
# duplicated within a field block.
|
95
145
|
def self.eager(*associations)
|
96
146
|
@class_eager_hash = EagerHash.merge!(
|
97
147
|
@class_eager_hash,
|
@@ -99,6 +149,12 @@ module Sequel
|
|
99
149
|
)
|
100
150
|
end
|
101
151
|
|
152
|
+
# Declare an arbitrary operation to be performed one all the data has been
|
153
|
+
# fetched. The block will be executed once and be passed all of the models
|
154
|
+
# that will be packed by this Packer, even if this Packer is nested as a
|
155
|
+
# subpacker of other packers. The block can save the result of the
|
156
|
+
# computation in an instance variable which can then be accessed in the
|
157
|
+
# blocks passed to field.
|
102
158
|
def self.precompute(&block)
|
103
159
|
if !block
|
104
160
|
raise ArgumentError, 'Sequel::Packer.precompute must be passed a block'
|
@@ -106,25 +162,55 @@ module Sequel
|
|
106
162
|
@class_precomputations << block
|
107
163
|
end
|
108
164
|
|
109
|
-
|
110
|
-
|
165
|
+
# Declare a block to be called after a Packer has been initialized with
|
166
|
+
# context. The block can call the common Packer DSL methods. It is most
|
167
|
+
# commonly used to pass eager procs that depend on the Packer context to
|
168
|
+
# eager.
|
169
|
+
def self.with_context(&block)
|
170
|
+
if !block
|
171
|
+
raise ArgumentError, 'Sequel::Packer.with_context must be passed a block'
|
172
|
+
end
|
173
|
+
@class_with_contexts << block
|
174
|
+
end
|
111
175
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
176
|
+
# Pack the given data with the specified traits and additional context.
|
177
|
+
# Context is automatically passed down to any subpackers.
|
178
|
+
#
|
179
|
+
# Data can be provided as a Sequel::Dataset, an array of Sequel::Models, a
|
180
|
+
# single Sequel::Model, or nil. Even when passing models that have already
|
181
|
+
# been materialized, eager loading will be used to efficiently fetch
|
182
|
+
# associations.
|
183
|
+
#
|
184
|
+
# Returns an array of packed hashes, or a single packed hash if a single
|
185
|
+
# model was passed in. Returns nil if nil was passed in.
|
186
|
+
def self.pack(data, *traits, **context)
|
187
|
+
return nil if !data
|
188
|
+
new(*traits, **context).pack(data)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Initialize a Packer instance with the given traits and additional context.
|
192
|
+
# This Packer can then pack multiple datasets or models via the pack method.
|
193
|
+
def initialize(*traits, **context)
|
194
|
+
@context = context
|
195
|
+
|
196
|
+
@subpackers = {}
|
197
|
+
|
198
|
+
# Technically we only need to duplicate these fields if we modify any of
|
199
|
+
# them, but manually implementing some sort of copy-on-write functionality
|
200
|
+
# is messy and error prone.
|
201
|
+
@instance_fields = class_fields.dup
|
202
|
+
@instance_packers = class_packers.dup
|
203
|
+
@instance_eager_hash = EagerHash.deep_dup(class_eager_hash)
|
204
|
+
@instance_precomputations = class_precomputations.dup
|
205
|
+
|
206
|
+
class_with_contexts.each do |with_context_block|
|
207
|
+
self.instance_exec(&with_context_block)
|
123
208
|
end
|
124
209
|
|
125
210
|
# Evaluate trait blocks, which might add new fields to @instance_fields,
|
126
|
-
# new packers to @instance_packers,
|
127
|
-
# @instance_eager_hash
|
211
|
+
# new packers to @instance_packers, new associations to
|
212
|
+
# @instance_eager_hash, and/or new precomputations to
|
213
|
+
# @instance_precomputations.
|
128
214
|
traits.each do |trait|
|
129
215
|
trait_block = class_traits[trait]
|
130
216
|
if !trait_block
|
@@ -135,10 +221,9 @@ module Sequel
|
|
135
221
|
end
|
136
222
|
|
137
223
|
# Create all the subpackers, and merge in their eager hashes.
|
138
|
-
@instance_packers.each do |association, (
|
139
|
-
association_packer =
|
224
|
+
@instance_packers.each do |association, (subpacker, traits)|
|
225
|
+
association_packer = subpacker.new(*traits, @context)
|
140
226
|
|
141
|
-
@subpackers ||= {}
|
142
227
|
@subpackers[association] = association_packer
|
143
228
|
|
144
229
|
@instance_eager_hash = EagerHash.merge!(
|
@@ -148,38 +233,38 @@ module Sequel
|
|
148
233
|
end
|
149
234
|
end
|
150
235
|
|
151
|
-
|
152
|
-
|
236
|
+
# Pack the given data with the traits and additional context specified when
|
237
|
+
# the Packer instance was created.
|
238
|
+
#
|
239
|
+
# Data can be provided as a Sequel::Dataset, an array of Sequel::Models, a
|
240
|
+
# single Sequel::Model, or nil. Even when passing models that have already
|
241
|
+
# been materialized, eager loading will be used to efficiently fetch
|
242
|
+
# associations.
|
243
|
+
#
|
244
|
+
# Returns an array of packed hashes, or a single packed hash if a single
|
245
|
+
# model was passed in. Returns nil if nil was passed in.
|
246
|
+
def pack(data)
|
247
|
+
case data
|
153
248
|
when Sequel::Dataset
|
154
|
-
if @instance_eager_hash
|
155
|
-
|
156
|
-
end
|
157
|
-
models = to_be_packed.all
|
249
|
+
data = data.eager(@instance_eager_hash) if @instance_eager_hash
|
250
|
+
models = data.all
|
158
251
|
|
159
252
|
run_precomputations(models)
|
160
253
|
pack_models(models)
|
161
254
|
when Sequel::Model
|
162
255
|
if @instance_eager_hash
|
163
|
-
EagerLoading.eager_load(
|
164
|
-
class_model,
|
165
|
-
[to_be_packed],
|
166
|
-
@instance_eager_hash
|
167
|
-
)
|
256
|
+
EagerLoading.eager_load(class_model, [data], @instance_eager_hash)
|
168
257
|
end
|
169
258
|
|
170
|
-
run_precomputations([
|
171
|
-
pack_model(
|
259
|
+
run_precomputations([data])
|
260
|
+
pack_model(data)
|
172
261
|
when Array
|
173
262
|
if @instance_eager_hash
|
174
|
-
EagerLoading.eager_load(
|
175
|
-
class_model,
|
176
|
-
to_be_packed,
|
177
|
-
@instance_eager_hash
|
178
|
-
)
|
263
|
+
EagerLoading.eager_load(class_model, data, @instance_eager_hash)
|
179
264
|
end
|
180
265
|
|
181
|
-
run_precomputations(
|
182
|
-
pack_models(
|
266
|
+
run_precomputations(data)
|
267
|
+
pack_models(data)
|
183
268
|
when NilClass
|
184
269
|
nil
|
185
270
|
end
|
@@ -187,6 +272,8 @@ module Sequel
|
|
187
272
|
|
188
273
|
private
|
189
274
|
|
275
|
+
# Run any blocks declared using precompute on the given models, as well as
|
276
|
+
# any precompute blocks declared by subpackers.
|
190
277
|
def run_precomputations(models)
|
191
278
|
@instance_packers.each do |association, _|
|
192
279
|
subpacker = @subpackers[association]
|
@@ -208,12 +295,15 @@ module Sequel
|
|
208
295
|
end
|
209
296
|
end
|
210
297
|
|
298
|
+
# Check if a Packer has any precompute blocks declared, to avoid the
|
299
|
+
# overhead of flattening the child associations.
|
211
300
|
def has_precomputations?
|
212
301
|
return true if @instance_precomputations.any?
|
213
302
|
return false if !@subpackers
|
214
303
|
@subpackers.values.any? {|sp| sp.send(:has_precomputations?)}
|
215
304
|
end
|
216
305
|
|
306
|
+
# Pack a single model by processing all of the Packer's declared fields.
|
217
307
|
def pack_model(model)
|
218
308
|
h = {}
|
219
309
|
|
@@ -236,15 +326,25 @@ module Sequel
|
|
236
326
|
h
|
237
327
|
end
|
238
328
|
|
329
|
+
# Pack an array of models by processing all of the Packer's declared fields.
|
239
330
|
def pack_models(models)
|
240
331
|
models.map {|m| pack_model(m)}
|
241
332
|
end
|
242
333
|
|
334
|
+
# Pack models from an association using the designated subpacker.
|
243
335
|
def pack_association(association, associated_models)
|
244
336
|
return nil if !associated_models
|
245
337
|
|
246
338
|
packer = @subpackers[association]
|
247
339
|
|
340
|
+
if !packer
|
341
|
+
raise(
|
342
|
+
NoAssociationSubpackerDefinedError,
|
343
|
+
"pack_association called for the #{class_model}.#{association} " +
|
344
|
+
'association, but no Packer has been set for that association.',
|
345
|
+
)
|
346
|
+
end
|
347
|
+
|
248
348
|
if associated_models.is_a?(Array)
|
249
349
|
packer.send(:pack_models, associated_models)
|
250
350
|
else
|
@@ -252,16 +352,19 @@ module Sequel
|
|
252
352
|
end
|
253
353
|
end
|
254
354
|
|
255
|
-
|
355
|
+
# See the definition of self.field. This method accepts the exact same
|
356
|
+
# arguments. When fields are declared within trait blocks, this method is
|
357
|
+
# called rather than the class method.
|
358
|
+
def field(field_name=nil, subpacker=nil, *traits, &block)
|
256
359
|
klass = self.class
|
257
360
|
|
258
361
|
Validation.check_field_arguments(
|
259
|
-
class_model, field_name,
|
362
|
+
class_model, field_name, subpacker, traits, &block)
|
260
363
|
field_type =
|
261
|
-
klass.send(:determine_field_type, field_name,
|
364
|
+
klass.send(:determine_field_type, field_name, subpacker, block)
|
262
365
|
|
263
366
|
if field_type == ASSOCIATION_FIELD
|
264
|
-
set_association_packer(field_name,
|
367
|
+
set_association_packer(field_name, subpacker, *traits)
|
265
368
|
end
|
266
369
|
|
267
370
|
@instance_fields << {
|
@@ -271,13 +374,19 @@ module Sequel
|
|
271
374
|
}
|
272
375
|
end
|
273
376
|
|
274
|
-
|
377
|
+
# See the definition of self.set_association_packer. This method accepts the
|
378
|
+
# exact same arguments. When used within a trait block, this method is
|
379
|
+
# called rather than the class method.
|
380
|
+
def set_association_packer(association, subpacker, *traits)
|
275
381
|
Validation.check_association_packer(
|
276
|
-
class_model, association,
|
382
|
+
class_model, association, subpacker, traits)
|
277
383
|
|
278
|
-
@instance_packers[association] = [
|
384
|
+
@instance_packers[association] = [subpacker, traits]
|
279
385
|
end
|
280
386
|
|
387
|
+
# See the definition of self.eager. This method accepts the exact same
|
388
|
+
# arguments. When used within a trait block, this method is called rather
|
389
|
+
# than the class method.
|
281
390
|
def eager(*associations)
|
282
391
|
@instance_eager_hash = EagerHash.merge!(
|
283
392
|
@instance_eager_hash,
|
@@ -285,6 +394,9 @@ module Sequel
|
|
285
394
|
)
|
286
395
|
end
|
287
396
|
|
397
|
+
# See the definition of self.precompute. This method accepts the exact same
|
398
|
+
# arguments. When used within a trait block, this method is called rather
|
399
|
+
# than the class method.
|
288
400
|
def precompute(&block)
|
289
401
|
if !block
|
290
402
|
raise ArgumentError, 'Sequel::Packer.precompute must be passed a block'
|
@@ -292,10 +404,25 @@ module Sequel
|
|
292
404
|
@instance_precomputations << block
|
293
405
|
end
|
294
406
|
|
407
|
+
# See the definition of self.with_context. This method accepts the exact
|
408
|
+
# same arguments. When used within a trait block, this method is called
|
409
|
+
# rather than the class method.
|
410
|
+
def with_context(&block)
|
411
|
+
raise(
|
412
|
+
UnnecessaryWithContextError,
|
413
|
+
'There is no need to call with_context from within a trait block; ' +
|
414
|
+
'@context can be accessed directly.',
|
415
|
+
)
|
416
|
+
end
|
417
|
+
|
418
|
+
# Access the internal eager hash.
|
295
419
|
def eager_hash
|
296
420
|
@instance_eager_hash
|
297
421
|
end
|
298
422
|
|
423
|
+
# The following methods expose the class instance variables containing the
|
424
|
+
# core definition of the Packer.
|
425
|
+
|
299
426
|
def class_model
|
300
427
|
self.class.instance_variable_get(:@model)
|
301
428
|
end
|
@@ -319,6 +446,10 @@ module Sequel
|
|
319
446
|
def class_precomputations
|
320
447
|
self.class.instance_variable_get(:@class_precomputations)
|
321
448
|
end
|
449
|
+
|
450
|
+
def class_with_contexts
|
451
|
+
self.class.instance_variable_get(:@class_with_contexts)
|
452
|
+
end
|
322
453
|
end
|
323
454
|
end
|
324
455
|
|
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.
|
4
|
+
version: 0.5.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-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|