sequel-packer 0.4.0 → 0.5.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 +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
|