sequel-packer 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +76 -8
- data/lib/sequel/packer/validation.rb +137 -0
- data/lib/sequel/packer/version.rb +1 -1
- data/lib/sequel/packer.rb +105 -180
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f7666c0db1cac9f34075e40a398a5963b33e6756d38507953dc14c59d5712d2
|
4
|
+
data.tar.gz: 10bf3ff431ba9fab4ff6bdfec84e51f1243cf3da1b5eff3ea66d6b7b45f94fbc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 67d08fbd8aef15f7005ae59b411f87a237d83b53aeb26e7e92efff2a628e6ae29bf14724cfedea03f34b5f7cf61e4c8d28be6cc4e01027535d04519fa681f7e1
|
7
|
+
data.tar.gz: c96e1f683319af1a20d6cb775f6b0ac8a479297947b6a26008082e4a8aa8e35a677de7d3a8f66a7a74fc57079fb8ecab1b678201a214f5768fc57eef61b4b150
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
### 0.3.0 (2020-05-14)
|
2
|
+
|
3
|
+
* Add `self.set_association_packer(association, packer_class, *traits)` and
|
4
|
+
`self.pack_association(association, models)` for more flexible packing of
|
5
|
+
associations.
|
6
|
+
* Improve internal code quality.
|
7
|
+
|
1
8
|
### 0.2.0 (2020-05-13)
|
2
9
|
|
3
10
|
* Add support for `Sequel::Packer.eager(*associations)`
|
data/README.md
CHANGED
@@ -12,8 +12,8 @@ ORM offering the following features:
|
|
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
|
-
* *Efficient:* When not using Sequel's
|
16
|
-
(https://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/TacticalEagerLoading.html)
|
15
|
+
* *Efficient:* When not using Sequel's
|
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.
|
@@ -58,8 +58,12 @@ DB.create_table(:comments) do
|
|
58
58
|
String :content
|
59
59
|
end
|
60
60
|
|
61
|
-
class User < Sequel::Model(:users)
|
62
|
-
|
61
|
+
class User < Sequel::Model(:users)
|
62
|
+
one_to_many :posts, key: :author_id, class: :Post
|
63
|
+
end
|
64
|
+
class Post < Sequel::Model(:posts)
|
65
|
+
one_to_many :comments, key: :post_id, class: :Comment
|
66
|
+
end
|
63
67
|
class Comment < Sequel::Model(:comments)
|
64
68
|
many_to_one :author, key: :author_id, class: :User
|
65
69
|
end
|
@@ -318,7 +322,8 @@ end
|
|
318
322
|
```
|
319
323
|
|
320
324
|
Though this version of course would result in many more queries to the database,
|
321
|
-
which are not required when using the shorthand form
|
325
|
+
which are not required when using the shorthand form, and also requires creating
|
326
|
+
a new instance of `packer_class` for every packed model.
|
322
327
|
|
323
328
|
### `self.field(&block)`
|
324
329
|
|
@@ -383,7 +388,7 @@ class UserPacker < Sequel::Packer
|
|
383
388
|
|
384
389
|
eager(:posts)
|
385
390
|
field(:num_posts) do |user|
|
386
|
-
user.posts.
|
391
|
+
user.posts.count
|
387
392
|
end
|
388
393
|
end
|
389
394
|
|
@@ -396,8 +401,7 @@ UserPacker.new.pack(User.dataset)
|
|
396
401
|
```
|
397
402
|
|
398
403
|
This helps prevent N+1 query problems when not using Sequel's
|
399
|
-
[`TacticalEagerLoading`]
|
400
|
-
(https://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/TacticalEagerLoading.html)
|
404
|
+
[`TacticalEagerLoading`](https://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/TacticalEagerLoading.html)
|
401
405
|
plugin.
|
402
406
|
|
403
407
|
Another use of `eager`, even when using `TacticalEagerLoading`, is to modify or
|
@@ -425,6 +429,70 @@ Also, it's important to note that if `eager` is called multiple times, with
|
|
425
429
|
multiple procs, each proc will get applied to the dataset, likely resulting in
|
426
430
|
overly restrictive filtering.
|
427
431
|
|
432
|
+
### `self.set_association_packer(association, packer_class, *traits)`
|
433
|
+
|
434
|
+
See `self.pack_association(association, models)` below.
|
435
|
+
|
436
|
+
### `self.pack_association(association, models)`
|
437
|
+
|
438
|
+
The simplest way to pack an association is to use
|
439
|
+
`self.field(association, packer_class, *traits)`, but sometimes this doesn't do
|
440
|
+
exactly what we want. We may want to pack the association under a different key
|
441
|
+
than the name of the association. Or we may only want to pack some of the
|
442
|
+
associated models (and it may be difficult or impossible to express which subset
|
443
|
+
we want to pack using `eager`). Or perhaps we have a `one_to_many` association
|
444
|
+
and instead of packing an array, we want to pack a single associated object
|
445
|
+
under a key. The two methods, `set_association_packer` and `pack_association`
|
446
|
+
are designed to handle these cases.
|
447
|
+
|
448
|
+
First, we'll note that following are exactly equivalent:
|
449
|
+
|
450
|
+
```ruby
|
451
|
+
field :my_assoc, MyAssocPacker, :trait1, :trait2
|
452
|
+
```
|
453
|
+
|
454
|
+
and
|
455
|
+
|
456
|
+
```ruby
|
457
|
+
set_association_packer :my_assoc, MyAssocPacker, :trait1, :trait2
|
458
|
+
field :my_assoc do |model|
|
459
|
+
pack_association(:my_assoc, model.my_assoc)
|
460
|
+
end
|
461
|
+
```
|
462
|
+
|
463
|
+
`set_association_packer` tells the Packer class that we will want to pack models
|
464
|
+
from a particular association using the designated Packer with the specified
|
465
|
+
traits. Declaring this ahead of time allows the Packer to ensure that the
|
466
|
+
association is eager loaded, as well as any nested associations used when using
|
467
|
+
the designated Packer with the specified traits.
|
468
|
+
|
469
|
+
`pack_association` can then be used in a `field` block to use that Packer after
|
470
|
+
the data has been fetched and we are actually packing the data. The key things
|
471
|
+
here are that we don't need to use the name of the association as the name of
|
472
|
+
the field, and that we can choose which models get serialized. If
|
473
|
+
`pack_association` is passed an array, it will return an array of packed models,
|
474
|
+
but if it is passed a single model, it will return just that packed model.
|
475
|
+
|
476
|
+
Examples:
|
477
|
+
|
478
|
+
#### Use a different field name than the name of the association
|
479
|
+
```ruby
|
480
|
+
set_association_packer :ugly_internal_names, InternalPacker
|
481
|
+
field :nice_external_names do |model|
|
482
|
+
pack_association(:ugly_internal_names, model.ugly_internal_names)
|
483
|
+
end
|
484
|
+
```
|
485
|
+
|
486
|
+
#### Pack a single instance of a `one_to_many` association
|
487
|
+
```ruby
|
488
|
+
class PostPacker < Sequel::Packer
|
489
|
+
set_association_packer :comments, CommentPacker
|
490
|
+
field :top_comment do |model|
|
491
|
+
pack_association(:comments, model.comments.max_by(&:num_likes))
|
492
|
+
end
|
493
|
+
end
|
494
|
+
```
|
495
|
+
|
428
496
|
|
429
497
|
### `initialize(*traits)`
|
430
498
|
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
class Packer
|
5
|
+
module Validation
|
6
|
+
# Cheks for common errors when using the field method. Additional
|
7
|
+
# checks around the packer class and traits occur in
|
8
|
+
# check_association_packer.
|
9
|
+
def self.check_field_arguments(
|
10
|
+
model,
|
11
|
+
field_name,
|
12
|
+
packer_class,
|
13
|
+
traits,
|
14
|
+
&block
|
15
|
+
)
|
16
|
+
fail ModelNotYetDeclaredError if !model
|
17
|
+
|
18
|
+
# This check applies to all invocations:
|
19
|
+
if field_name && !field_name.is_a?(Symbol) && !field_name.is_a?(String)
|
20
|
+
raise(
|
21
|
+
FieldArgumentError,
|
22
|
+
'Field name passed to Sequel::Packer::field must be a Symbol or ' +
|
23
|
+
'a String.',
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
if block
|
28
|
+
# If the user passed a block, we'll assume they either want:
|
29
|
+
# field :foo {|model| ...}
|
30
|
+
# or field {|model, hash| ...}
|
31
|
+
#
|
32
|
+
if packer_class || traits.any?
|
33
|
+
raise(
|
34
|
+
FieldArgumentError,
|
35
|
+
'When passing a block to Sequel::Packer::field, either pass the ' +
|
36
|
+
'name of field as a single argument (e.g., field(:foo) ' +
|
37
|
+
'{|model| ...}), or nothing at all to perform arbitrary ' +
|
38
|
+
'modifications of the final hash (e.g., field {|model, hash| ' +
|
39
|
+
'...}).',
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
arity = block.arity
|
44
|
+
|
45
|
+
# When using Symbol.to_proc (field(:foo, &:calculate_foo)), the block has arity -1.
|
46
|
+
if field_name && arity != 1 && arity != -1
|
47
|
+
raise(
|
48
|
+
FieldArgumentError,
|
49
|
+
"The block used to define :#{field_name} must accept exactly " +
|
50
|
+
'one argument.',
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
if !field_name && arity != 2
|
55
|
+
raise(
|
56
|
+
FieldArgumentError,
|
57
|
+
'When passing an arbitrary block to Sequel::Packer::field, the ' +
|
58
|
+
'block must accept exactly two arguments: the model and the ' +
|
59
|
+
'partially packed hash.',
|
60
|
+
)
|
61
|
+
end
|
62
|
+
else
|
63
|
+
# In this part of the if, block is not defined
|
64
|
+
|
65
|
+
if !field_name
|
66
|
+
# Note that this error isn't technically true, but usage of the
|
67
|
+
# field {|model, hash| ...} variant is likely pretty rare.
|
68
|
+
raise(
|
69
|
+
FieldArgumentError,
|
70
|
+
'Must pass a field name to Sequel::Packer::field.',
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
if model.associations.include?(field_name) && !packer_class
|
75
|
+
raise(
|
76
|
+
InvalidAssociationPackerError,
|
77
|
+
"#{field_name} is an association of #{model}. You must also " +
|
78
|
+
'pass a Sequel::Packer class to be used when serializing ' +
|
79
|
+
'this association.',
|
80
|
+
)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Performs various checks when using
|
86
|
+
# field(association, packer_class, *traits)
|
87
|
+
# or
|
88
|
+
# set_association_packer(association, packer_class, *traits)
|
89
|
+
def self.check_association_packer(
|
90
|
+
model,
|
91
|
+
association,
|
92
|
+
packer_class,
|
93
|
+
traits
|
94
|
+
)
|
95
|
+
if !model.associations.include?(association)
|
96
|
+
raise(
|
97
|
+
AssociationDoesNotExistError,
|
98
|
+
"The association :#{association} does not exist on #{model}.",
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
if !packer_class || !(packer_class < Sequel::Packer)
|
103
|
+
raise(
|
104
|
+
InvalidAssociationPackerError,
|
105
|
+
'You must pass a Sequel::Packer class to use when packing the ' +
|
106
|
+
":#{association} association. #{packer_class} is not a " +
|
107
|
+
"subclass of Sequel::Packer."
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
111
|
+
association_model =
|
112
|
+
model.association_reflections[association].associated_class
|
113
|
+
packer_class_model = packer_class.instance_variable_get(:@model)
|
114
|
+
|
115
|
+
if !(association_model <= packer_class_model)
|
116
|
+
raise(
|
117
|
+
InvalidAssociationPackerError,
|
118
|
+
"Model for association packer (#{packer_class_model}) " +
|
119
|
+
"doesn't match model for the #{association} association " +
|
120
|
+
"(#{association_model})",
|
121
|
+
)
|
122
|
+
end
|
123
|
+
|
124
|
+
packer_class_traits = packer_class.instance_variable_get(:@class_traits)
|
125
|
+
traits.each do |trait|
|
126
|
+
if !packer_class_traits.key?(trait)
|
127
|
+
raise(
|
128
|
+
UnknownTraitError,
|
129
|
+
"Trait :#{trait} isn't defined for #{packer_class} used to " +
|
130
|
+
"pack #{association} association.",
|
131
|
+
)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
data/lib/sequel/packer.rb
CHANGED
@@ -4,11 +4,15 @@ module Sequel
|
|
4
4
|
class FieldArgumentError < ArgumentError; end
|
5
5
|
# Must declare a model with `model MyModel` before calling field.
|
6
6
|
class ModelNotYetDeclaredError < StandardError; end
|
7
|
+
class AssociationDoesNotExistError < StandardError; end
|
8
|
+
class InvalidAssociationPackerError < StandardError; end
|
9
|
+
class UnknownTraitError < StandardError; end
|
7
10
|
|
8
11
|
def self.inherited(subclass)
|
9
|
-
subclass.instance_variable_set(:@
|
10
|
-
subclass.instance_variable_set(:@
|
11
|
-
subclass.instance_variable_set(:@
|
12
|
+
subclass.instance_variable_set(:@class_fields, [])
|
13
|
+
subclass.instance_variable_set(:@class_traits, {})
|
14
|
+
subclass.instance_variable_set(:@class_packers, {})
|
15
|
+
subclass.instance_variable_set(:@class_eager_hash, nil)
|
12
16
|
end
|
13
17
|
|
14
18
|
def self.model(klass)
|
@@ -34,22 +38,31 @@ module Sequel
|
|
34
38
|
ARBITRARY_MODIFICATION_FIELD = :arbitrary_modification_field
|
35
39
|
|
36
40
|
def self.field(field_name=nil, packer_class=nil, *traits, &block)
|
37
|
-
|
38
|
-
|
41
|
+
Validation.check_field_arguments(
|
42
|
+
@model, field_name, packer_class, traits, &block)
|
43
|
+
field_type = determine_field_type(field_name, packer_class, block)
|
39
44
|
|
40
|
-
|
45
|
+
if field_type == ASSOCIATION_FIELD
|
46
|
+
set_association_packer(field_name, packer_class, *traits)
|
47
|
+
end
|
48
|
+
|
49
|
+
@class_fields << {
|
41
50
|
type: field_type,
|
42
51
|
name: field_name,
|
43
|
-
packer: packer_class,
|
44
|
-
packer_traits: traits,
|
45
52
|
block: block,
|
46
53
|
}
|
47
54
|
end
|
48
55
|
|
56
|
+
def self.set_association_packer(association, packer_class, *traits)
|
57
|
+
Validation.check_association_packer(
|
58
|
+
@model, association, packer_class, traits)
|
59
|
+
@class_packers[association] = [packer_class, traits]
|
60
|
+
end
|
61
|
+
|
49
62
|
private_class_method def self.determine_field_type(
|
50
|
-
field_name
|
51
|
-
packer_class
|
52
|
-
|
63
|
+
field_name,
|
64
|
+
packer_class,
|
65
|
+
block
|
53
66
|
)
|
54
67
|
if block
|
55
68
|
if field_name
|
@@ -66,175 +79,65 @@ module Sequel
|
|
66
79
|
end
|
67
80
|
end
|
68
81
|
|
69
|
-
private_class_method def self.validate_field_args(
|
70
|
-
field_name=nil,
|
71
|
-
packer_class=nil,
|
72
|
-
*traits,
|
73
|
-
&block
|
74
|
-
)
|
75
|
-
fail ModelNotYetDeclaredError if !@model
|
76
|
-
|
77
|
-
# This check applies to all invocations:
|
78
|
-
if field_name && !field_name.is_a?(Symbol) && !field_name.is_a?(String)
|
79
|
-
raise(
|
80
|
-
FieldArgumentError,
|
81
|
-
'Field name passed to Sequel::Packer::field must be a Symbol or ' +
|
82
|
-
'a String.',
|
83
|
-
)
|
84
|
-
end
|
85
|
-
|
86
|
-
if block
|
87
|
-
# If the user passed a block, we'll assume they either want:
|
88
|
-
# field :foo {|model| ...}
|
89
|
-
# or field {|model, hash| ...}
|
90
|
-
#
|
91
|
-
if packer_class || traits.any?
|
92
|
-
raise(
|
93
|
-
FieldArgumentError,
|
94
|
-
'When passing a block to Sequel::Packer::field, either pass the ' +
|
95
|
-
'name of field as a single argument (e.g., field(:foo) ' +
|
96
|
-
'{|model| ...}), or nothing at all to perform arbitrary ' +
|
97
|
-
'modifications of the final hash (e.g., field {|model, hash| ' +
|
98
|
-
'...}).',
|
99
|
-
)
|
100
|
-
end
|
101
|
-
|
102
|
-
arity = block.arity
|
103
|
-
|
104
|
-
# When using Symbol.to_proc (field(:foo, &:calculate_foo)), the block has arity -1.
|
105
|
-
if field_name && arity != 1 && arity != -1
|
106
|
-
raise(
|
107
|
-
FieldArgumentError,
|
108
|
-
"The block used to define :#{field_name} must accept exactly " +
|
109
|
-
'one argument.',
|
110
|
-
)
|
111
|
-
end
|
112
|
-
|
113
|
-
if !field_name && arity != 2
|
114
|
-
raise(
|
115
|
-
FieldArgumentError,
|
116
|
-
'When passing an arbitrary block to Sequel::Packer::field, the ' +
|
117
|
-
'block must accept exactly two arguments: the model and the ' +
|
118
|
-
'partially packed hash.',
|
119
|
-
)
|
120
|
-
end
|
121
|
-
else
|
122
|
-
# In this part of the if, block is not defined
|
123
|
-
|
124
|
-
if !field_name
|
125
|
-
# Note that this error isn't technically true, but usage of the
|
126
|
-
# field {|model, hash| ...} variant is likely pretty rare.
|
127
|
-
raise(
|
128
|
-
FieldArgumentError,
|
129
|
-
'Must pass a field name to Sequel::Packer::field.',
|
130
|
-
)
|
131
|
-
end
|
132
|
-
|
133
|
-
if packer_class || traits.any?
|
134
|
-
if !@model.associations.include?(field_name)
|
135
|
-
raise(
|
136
|
-
FieldArgumentError,
|
137
|
-
'Passing multiple arguments to Sequel::Packer::field ' +
|
138
|
-
'is used to serialize associations with designated ' +
|
139
|
-
"packers, but the association #{field_name} does not " +
|
140
|
-
"exist on #{@model}.",
|
141
|
-
)
|
142
|
-
end
|
143
|
-
|
144
|
-
if !(packer_class < Sequel::Packer)
|
145
|
-
raise(
|
146
|
-
FieldArgumentError,
|
147
|
-
'When declaring the serialization behavior for an ' +
|
148
|
-
'association, the second argument must be a Sequel::Packer ' +
|
149
|
-
"subclass. #{packer_class} is not a subclass of " +
|
150
|
-
'Sequel::Packer.',
|
151
|
-
)
|
152
|
-
end
|
153
|
-
|
154
|
-
association_model =
|
155
|
-
@model.association_reflections[field_name].associated_class
|
156
|
-
packer_class_model = packer_class.instance_variable_get(:@model)
|
157
|
-
|
158
|
-
if !(association_model <= packer_class_model)
|
159
|
-
raise(
|
160
|
-
FieldArgumentError,
|
161
|
-
"Model for association packer (#{packer_class_model}) " +
|
162
|
-
"doesn't match model for the #{field_name} association " +
|
163
|
-
"(#{association_model})",
|
164
|
-
)
|
165
|
-
end
|
166
|
-
|
167
|
-
packer_class_traits = packer_class.instance_variable_get(:@traits)
|
168
|
-
traits.each do |trait|
|
169
|
-
if !packer_class_traits.key?(trait)
|
170
|
-
raise(
|
171
|
-
FieldArgumentError,
|
172
|
-
"Trait :#{trait} isn't defined for #{packer_class} used to " +
|
173
|
-
"pack #{field_name} association.",
|
174
|
-
)
|
175
|
-
end
|
176
|
-
end
|
177
|
-
else
|
178
|
-
if @model.associations.include?(field_name)
|
179
|
-
raise(
|
180
|
-
FieldArgumentError,
|
181
|
-
'When declaring a field for a model association, you must ' +
|
182
|
-
'also pass a Sequel::Packer class to use as the second ' +
|
183
|
-
'argument to field.',
|
184
|
-
)
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
82
|
def self.trait(name, &block)
|
191
|
-
|
83
|
+
if @class_traits.key?(name)
|
84
|
+
raise ArgumentError, "Trait :#{name} already defined"
|
85
|
+
end
|
192
86
|
if !block_given?
|
193
87
|
raise ArgumentError, 'Must give a block when defining a trait'
|
194
88
|
end
|
195
|
-
@
|
89
|
+
@class_traits[name] = block
|
196
90
|
end
|
197
91
|
|
198
92
|
def self.eager(*associations)
|
199
|
-
@
|
200
|
-
@
|
93
|
+
@class_eager_hash = EagerHash.merge!(
|
94
|
+
@class_eager_hash,
|
201
95
|
EagerHash.normalize_eager_args(*associations),
|
202
96
|
)
|
203
97
|
end
|
204
98
|
|
205
99
|
def initialize(*traits)
|
206
|
-
@
|
207
|
-
|
208
|
-
|
100
|
+
@subpackers = nil
|
101
|
+
|
102
|
+
# If there aren't any traits, we can just re-use the class variables.
|
103
|
+
if traits.empty?
|
104
|
+
@instance_fields = class_fields
|
105
|
+
@instance_packers = class_packers
|
106
|
+
@instance_eager_hash = class_eager_hash
|
107
|
+
else
|
108
|
+
@instance_fields = class_fields.dup
|
109
|
+
@instance_packers = class_packers.dup
|
110
|
+
@instance_eager_hash = EagerHash.deep_dup(class_eager_hash)
|
111
|
+
end
|
209
112
|
|
113
|
+
# Evaluate trait blocks, which might add new fields to @instance_fields,
|
114
|
+
# new packers to @instance_packers, and/or new associations to
|
115
|
+
# @instance_eager_hash.
|
210
116
|
traits.each do |trait|
|
211
|
-
trait_block =
|
117
|
+
trait_block = class_traits[trait]
|
212
118
|
if !trait_block
|
213
|
-
raise
|
119
|
+
raise UnknownTraitError, "Unknown trait for #{self.class}: :#{trait}"
|
214
120
|
end
|
215
121
|
|
216
122
|
self.instance_exec(&trait_block)
|
217
123
|
end
|
218
124
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
association_packer =
|
223
|
-
field_options[:packer].new(*field_options[:packer_traits])
|
125
|
+
# Create all the subpackers, and merge in their eager hashes.
|
126
|
+
@instance_packers.each do |association, (packer_class, traits)|
|
127
|
+
association_packer = packer_class.new(*traits)
|
224
128
|
|
225
|
-
|
226
|
-
|
129
|
+
@subpackers ||= {}
|
130
|
+
@subpackers[association] = association_packer
|
227
131
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
end
|
132
|
+
@instance_eager_hash = EagerHash.merge!(
|
133
|
+
@instance_eager_hash,
|
134
|
+
{association => association_packer.send(:eager_hash)},
|
135
|
+
)
|
233
136
|
end
|
234
137
|
end
|
235
138
|
|
236
139
|
def pack(dataset)
|
237
|
-
dataset = dataset.eager(@
|
140
|
+
dataset = dataset.eager(@instance_eager_hash) if @instance_eager_hash
|
238
141
|
models = dataset.all
|
239
142
|
pack_models(models)
|
240
143
|
end
|
@@ -242,26 +145,19 @@ module Sequel
|
|
242
145
|
def pack_model(model)
|
243
146
|
h = {}
|
244
147
|
|
245
|
-
@
|
148
|
+
@instance_fields.each do |field_options|
|
246
149
|
field_name = field_options[:name]
|
247
150
|
|
248
151
|
case field_options[:type]
|
249
152
|
when METHOD_FIELD
|
250
153
|
h[field_name] = model.send(field_name)
|
251
154
|
when BLOCK_FIELD
|
252
|
-
h[field_name] = field_options[:block]
|
155
|
+
h[field_name] = instance_exec(model, &field_options[:block])
|
253
156
|
when ASSOCIATION_FIELD
|
254
157
|
associated_objects = model.send(field_name)
|
255
|
-
|
256
|
-
if !associated_objects
|
257
|
-
h[field_name] = nil
|
258
|
-
elsif associated_objects.is_a?(Array)
|
259
|
-
h[field_name] = @packers[field_name].pack_models(associated_objects)
|
260
|
-
else
|
261
|
-
h[field_name] = @packers[field_name].pack_model(associated_objects)
|
262
|
-
end
|
158
|
+
h[field_name] = pack_association(field_name, associated_objects)
|
263
159
|
when ARBITRARY_MODIFICATION_FIELD
|
264
|
-
field_options[:block]
|
160
|
+
instance_exec(model, h, &field_options[:block])
|
265
161
|
end
|
266
162
|
end
|
267
163
|
|
@@ -272,48 +168,77 @@ module Sequel
|
|
272
168
|
models.map {|m| pack_model(m)}
|
273
169
|
end
|
274
170
|
|
171
|
+
def pack_association(association, associated_models)
|
172
|
+
return nil if !associated_models
|
173
|
+
|
174
|
+
packer = @subpackers[association]
|
175
|
+
|
176
|
+
if associated_models.is_a?(Array)
|
177
|
+
packer.pack_models(associated_models)
|
178
|
+
else
|
179
|
+
packer.pack_model(associated_models)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
275
183
|
private
|
276
184
|
|
277
185
|
def field(field_name=nil, packer_class=nil, *traits, &block)
|
278
186
|
klass = self.class
|
279
|
-
klass.
|
280
|
-
|
187
|
+
model = klass.instance_variable_get(:@model)
|
188
|
+
|
189
|
+
Validation.check_field_arguments(
|
190
|
+
model, field_name, packer_class, traits, &block)
|
281
191
|
field_type =
|
282
|
-
klass.send(:determine_field_type, field_name, packer_class,
|
192
|
+
klass.send(:determine_field_type, field_name, packer_class, block)
|
193
|
+
|
194
|
+
if field_type == ASSOCIATION_FIELD
|
195
|
+
set_association_packer(field_name, packer_class, *traits)
|
196
|
+
end
|
283
197
|
|
284
|
-
@
|
198
|
+
@instance_fields << {
|
285
199
|
type: field_type,
|
286
200
|
name: field_name,
|
287
|
-
packer: packer_class,
|
288
|
-
packer_traits: traits,
|
289
201
|
block: block,
|
290
202
|
}
|
291
203
|
end
|
292
204
|
|
205
|
+
def set_association_packer(association, packer_class, *traits)
|
206
|
+
model = self.class.instance_variable_get(:@model)
|
207
|
+
Validation.check_association_packer(
|
208
|
+
model, association, packer_class, traits)
|
209
|
+
|
210
|
+
@instance_packers[association] = [packer_class, traits]
|
211
|
+
end
|
212
|
+
|
293
213
|
def eager_hash
|
294
|
-
@
|
214
|
+
@instance_eager_hash
|
295
215
|
end
|
296
216
|
|
297
217
|
def eager(*associations)
|
298
|
-
@
|
299
|
-
@
|
218
|
+
@instance_eager_hash = EagerHash.merge!(
|
219
|
+
@instance_eager_hash,
|
300
220
|
EagerHash.normalize_eager_args(*associations),
|
301
221
|
)
|
302
222
|
end
|
303
223
|
|
304
|
-
def
|
305
|
-
self.class.instance_variable_get(:@
|
224
|
+
def class_fields
|
225
|
+
self.class.instance_variable_get(:@class_fields)
|
226
|
+
end
|
227
|
+
|
228
|
+
def class_eager_hash
|
229
|
+
self.class.instance_variable_get(:@class_eager_hash)
|
306
230
|
end
|
307
231
|
|
308
|
-
def
|
309
|
-
self.class.instance_variable_get(:@
|
232
|
+
def class_packers
|
233
|
+
self.class.instance_variable_get(:@class_packers)
|
310
234
|
end
|
311
235
|
|
312
|
-
def
|
313
|
-
self.class.instance_variable_get(:@
|
236
|
+
def class_traits
|
237
|
+
self.class.instance_variable_get(:@class_traits)
|
314
238
|
end
|
315
239
|
end
|
316
240
|
end
|
317
241
|
|
318
242
|
require 'sequel/packer/eager_hash'
|
243
|
+
require 'sequel/packer/validation'
|
319
244
|
require "sequel/packer/version"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel-packer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Julius Martinez
|
@@ -128,6 +128,7 @@ files:
|
|
128
128
|
- bin/setup
|
129
129
|
- lib/sequel/packer.rb
|
130
130
|
- lib/sequel/packer/eager_hash.rb
|
131
|
+
- lib/sequel/packer/validation.rb
|
131
132
|
- lib/sequel/packer/version.rb
|
132
133
|
- sequel-packer.gemspec
|
133
134
|
homepage: https://github.com/PaulJuliusMartinez/sequel-packer
|