deserializer 0.3.0 → 0.4.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/Gemfile.lock +13 -1
- data/README.md +273 -105
- data/deserializer.gemspec +2 -0
- data/lib/deserializer.rb +3 -0
- data/lib/deserializer/associatable.rb +38 -0
- data/lib/deserializer/base.rb +27 -22
- data/lib/deserializer/version.rb +1 -1
- data/test/lib/deserializers.rb +12 -0
- data/test/unit/deserializer_test.rb +13 -0
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e444e500d391065ce2aed5b2b41cb605fa443df
|
4
|
+
data.tar.gz: ba9db864ddb2d65dce89ddcb494939eda0d05428
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c84a34731a8b4e26b93b8ac96116bf39116a9617676330b32465e1ff8ab743491a33c54d8eb321cad436d5ab8a1fc6954be981405066e9b48d62459cb6d47911
|
7
|
+
data.tar.gz: 302c940e2185dfc3eaf5471cf9ea34ca276a5b8a5327f7d94a216d55dc27e4a527ec39a2b35a832885ea6c7d826d286d7725d4e06f0957ebe1bae08e20c5932c
|
data/Gemfile.lock
CHANGED
@@ -1,12 +1,21 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
deserializer (0.
|
4
|
+
deserializer (0.3.0)
|
5
|
+
activesupport
|
5
6
|
|
6
7
|
GEM
|
7
8
|
remote: https://rubygems.org/
|
8
9
|
specs:
|
10
|
+
activesupport (4.2.5)
|
11
|
+
i18n (~> 0.7)
|
12
|
+
json (~> 1.7, >= 1.7.7)
|
13
|
+
minitest (~> 5.1)
|
14
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
15
|
+
tzinfo (~> 1.1)
|
9
16
|
docile (1.1.5)
|
17
|
+
i18n (0.7.0)
|
18
|
+
json (1.8.3)
|
10
19
|
minitest (5.7.0)
|
11
20
|
multi_json (1.11.1)
|
12
21
|
rake (10.4.2)
|
@@ -15,6 +24,9 @@ GEM
|
|
15
24
|
multi_json (~> 1.0)
|
16
25
|
simplecov-html (~> 0.9.0)
|
17
26
|
simplecov-html (0.9.0)
|
27
|
+
thread_safe (0.3.5)
|
28
|
+
tzinfo (1.2.2)
|
29
|
+
thread_safe (~> 0.1)
|
18
30
|
|
19
31
|
PLATFORMS
|
20
32
|
ruby
|
data/README.md
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# Deserializer
|
2
|
-
|
3
|
-
Hash transformation and sanitization
|
4
|
-
|
5
|
-
|
2
|
+
## Features
|
3
|
+
- Hash transformation and sanitization
|
4
|
+
- Deserialization of complex parameters into a hash that an AR model can take
|
5
|
+
- Avoid having multiple definitions in fragile arrays when using strong params
|
6
|
+
- Easy create and update from JSON without writing heavy controllers
|
7
|
+
- [ActiveModel::Serializer](https://github.com/rails-api/active_model_serializers)-like interface and conventions
|
6
8
|
|
7
9
|
## Problem
|
8
|
-
|
9
|
-
Let's say we have a API create endpoint that takes json that looks something like
|
10
|
+
Let's say we have an API with an endpoint that takes this JSON:
|
10
11
|
|
11
12
|
```json
|
12
13
|
{
|
@@ -23,7 +24,7 @@ Let's say we have a API create endpoint that takes json that looks something lik
|
|
23
24
|
}
|
24
25
|
```
|
25
26
|
|
26
|
-
|
27
|
+
But this goes into a flat DishReview model:
|
27
28
|
|
28
29
|
```ruby
|
29
30
|
t.belongs_to :restaurant
|
@@ -37,11 +38,10 @@ t.string :texture
|
|
37
38
|
t.string :smell
|
38
39
|
```
|
39
40
|
|
40
|
-
|
41
|
+
### Solution (No `Deserializer`)
|
42
|
+
Permit some params, do some parsing and feed that into `DishReview.new`:
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
``` ruby
|
44
|
+
```ruby
|
45
45
|
class DishReviewController < BaseController
|
46
46
|
|
47
47
|
def create
|
@@ -93,30 +93,78 @@ class DishReviewController < BaseController
|
|
93
93
|
end
|
94
94
|
```
|
95
95
|
|
96
|
-
|
96
|
+
#### What's up with that?
|
97
|
+
- You have to do this for every action
|
98
|
+
- Controllers are obese, hard to parse and fragile
|
99
|
+
- Controllers are doing non-controller-y things
|
100
|
+
|
101
|
+
### Solution (With `Deserializer`)
|
102
|
+
`DishReviewDeserializer`:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
module MyApi
|
106
|
+
module V1
|
107
|
+
class DishReviewDeserializer < Deserializer::Base
|
108
|
+
attributes :restaurant_id
|
109
|
+
:user_id
|
110
|
+
:description
|
111
|
+
|
112
|
+
attribute :name, key: :dish_name
|
113
|
+
|
114
|
+
has_one :ratings, :deserializer => RatingsDeserializer
|
115
|
+
|
116
|
+
def ratings
|
117
|
+
object
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
```
|
97
124
|
|
98
|
-
|
125
|
+
`RatingsDeserializer`:
|
99
126
|
|
100
|
-
|
127
|
+
```ruby
|
128
|
+
module MyApi
|
129
|
+
module V1
|
130
|
+
class RatingsDeserializer < Deserializer::Base
|
101
131
|
|
102
|
-
|
132
|
+
attributes :taste,
|
133
|
+
:color,
|
134
|
+
:texture,
|
135
|
+
:smell
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
```
|
103
140
|
|
104
|
-
|
141
|
+
All of this allows your controller to be so very small:
|
105
142
|
|
106
|
-
#### from_params
|
107
|
-
`MyDeserializer.from_params(params)` created the json that your AR model will then consume.
|
108
143
|
```ruby
|
109
|
-
|
144
|
+
class DishReviewsController < YourApiController::Base
|
145
|
+
def create
|
146
|
+
@review = DishReview.new( MyApi::V1::DishReviewDeserailzer.from_params(params) )
|
147
|
+
|
148
|
+
if @review.save
|
149
|
+
# return review
|
150
|
+
else
|
151
|
+
# return sad errors splody
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# RUD
|
156
|
+
end
|
110
157
|
```
|
111
158
|
|
112
|
-
####
|
113
|
-
|
159
|
+
#### What's up with that?
|
160
|
+
- Un-pollutes controllers from all the parsing
|
161
|
+
- Builds deserializers that look like our serializers
|
114
162
|
|
115
|
-
|
116
|
-
|
163
|
+
## Definition
|
164
|
+
Inherit from `Deserializer::Base` and define it in much the same way you would an [ActiveModel::Serializer](https://github.com/rails-api/active_model_serializers).
|
117
165
|
|
118
|
-
|
119
|
-
|
166
|
+
### attributes
|
167
|
+
Use `attributes` for straight mapping from params to the model:
|
120
168
|
|
121
169
|
```ruby
|
122
170
|
class PostDeserializer < Deserializer::Base
|
@@ -124,31 +172,70 @@ class PostDeserializer < Deserializer::Base
|
|
124
172
|
:body
|
125
173
|
end
|
126
174
|
```
|
127
|
-
with params `{"title" => "lorem", "body" => "ipsum"}`, will give you a hash of `{title: "lorem", body: "ipsum"}`.
|
128
175
|
|
129
|
-
|
130
|
-
|
176
|
+
```ruby
|
177
|
+
# Example params
|
178
|
+
{
|
179
|
+
"title" => "lorem",
|
180
|
+
"body" => "ipsum"
|
181
|
+
}
|
182
|
+
# Resulting hash
|
183
|
+
{
|
184
|
+
title: "lorem",
|
185
|
+
body: "ipsum"
|
186
|
+
}
|
187
|
+
```
|
188
|
+
|
189
|
+
### attribute
|
190
|
+
Allows the following customizations for each `attribute`
|
191
|
+
#### :key
|
192
|
+
|
131
193
|
```ruby
|
132
194
|
class PostDeserializer < Deserializer::Base
|
133
195
|
attribute :title, ignore_empty: true
|
134
|
-
attribute :body, key: :
|
196
|
+
attribute :body, key: :content
|
135
197
|
end
|
136
198
|
```
|
137
|
-
It is symmetric with `ActiveModel::Serializer`, so that :text is what it will get in params, but :body is what it will insert into the result.
|
138
199
|
|
139
|
-
|
200
|
+
`:content` here is what it will get in params while `:body` is what it will be inserted into the result.
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
# Example params
|
204
|
+
{
|
205
|
+
"title" => "lorem",
|
206
|
+
"content" => "ipsum"
|
207
|
+
}
|
208
|
+
# Resulting hash
|
209
|
+
{
|
210
|
+
title: "lorem",
|
211
|
+
body: "ipsum"
|
212
|
+
}
|
213
|
+
```
|
214
|
+
|
215
|
+
#### :ignore_empty
|
216
|
+
While `Deserializer`'s default is to pass all values through, this option will drop any key with `false`/`nil`/`""`/`[]`/`{}` values from the result.
|
140
217
|
|
141
|
-
|
218
|
+
```ruby
|
219
|
+
# Example params
|
220
|
+
{
|
221
|
+
"title" => "",
|
222
|
+
"text" => nil
|
223
|
+
}
|
224
|
+
# Resulting hash
|
225
|
+
{}
|
226
|
+
```
|
142
227
|
|
143
|
-
|
228
|
+
#### :convert_with
|
229
|
+
Allows deserializing and converting a value at the same time. For example:
|
144
230
|
|
145
|
-
```ruby
|
231
|
+
```ruby
|
146
232
|
class Post < ActiveRecord::Base
|
147
233
|
belongs_to :post_type # this is a domain table
|
148
234
|
end
|
149
235
|
```
|
150
236
|
|
151
|
-
|
237
|
+
If we serialize with
|
238
|
+
|
152
239
|
```ruby
|
153
240
|
class PostSerializer < ActiveModel::Serializer
|
154
241
|
attribute :type
|
@@ -159,13 +246,13 @@ class PostSerializer < ActiveModel::Serializer
|
|
159
246
|
end
|
160
247
|
```
|
161
248
|
|
162
|
-
Then, when we
|
249
|
+
Then, when we get a symbolic name from the controller but want to work with an id in the backend, we can:
|
163
250
|
|
164
251
|
```ruby
|
165
252
|
class PostDeserializer < Deserializer::Base
|
166
253
|
attribute :title, ignore_empty: true
|
167
254
|
attribute :body
|
168
|
-
attribute :post_type_id, key: type, convert_with: to_type_id
|
255
|
+
attribute :post_type_id, key: :type, convert_with: to_type_id
|
169
256
|
|
170
257
|
def to_type_id(value)
|
171
258
|
Type.find_by_symbolic_name.id
|
@@ -173,13 +260,25 @@ class PostDeserializer < Deserializer::Base
|
|
173
260
|
end
|
174
261
|
```
|
175
262
|
|
176
|
-
|
263
|
+
```ruby
|
264
|
+
# Example params
|
265
|
+
{
|
266
|
+
"title" => "lorem",
|
267
|
+
"body" => "ipsum",
|
268
|
+
"type" => "BLAGABLAG"
|
269
|
+
}
|
270
|
+
# Resulting hash
|
271
|
+
{
|
272
|
+
title: "lorem",
|
273
|
+
body: "ipsum",
|
274
|
+
post_type_id: 1
|
275
|
+
}
|
276
|
+
```
|
177
277
|
|
278
|
+
### has_one
|
279
|
+
`has_one` association expects a param and its deserializer:
|
178
280
|
|
179
|
-
|
180
|
-
NOTE: This is the only association currently supported by `Deserializer`.
|
181
|
-
`has_one` expects the param and its deserializer.
|
182
|
-
```ruby
|
281
|
+
```ruby
|
183
282
|
class DishDeserializer < Deserializer::Base
|
184
283
|
# probably other stuff
|
185
284
|
has_one :ratings, deserializer: RatingsDeserializer
|
@@ -190,10 +289,27 @@ class RatingsDeserializer < Deserializer::Base
|
|
190
289
|
:smell
|
191
290
|
end
|
192
291
|
```
|
193
|
-
So for params `{"ratings" => {"taste" => "bad", "smell" => "good"}}` you would get `{ratings: {taste: "bad", smell: "good"}}`
|
194
292
|
|
195
|
-
|
196
|
-
|
293
|
+
```ruby
|
294
|
+
# Example params
|
295
|
+
{
|
296
|
+
"ratings" => {
|
297
|
+
"taste" => "bad",
|
298
|
+
"smell" => "good"
|
299
|
+
}
|
300
|
+
}
|
301
|
+
# Resulting hash
|
302
|
+
{
|
303
|
+
ratings: {
|
304
|
+
taste: "bad",
|
305
|
+
smell: "good"
|
306
|
+
}
|
307
|
+
}
|
308
|
+
```
|
309
|
+
|
310
|
+
#### Deserialize into a Different Name
|
311
|
+
In the example above, if `ratings` inside `Dish` is called `scores` in your ActiveRecord, you can:
|
312
|
+
|
197
313
|
```ruby
|
198
314
|
class DishDeserializer < Deserializer::Base
|
199
315
|
has_one :ratings, deserializer: RatingsDeserializer
|
@@ -203,9 +319,26 @@ class DishDeserializer < Deserializer::Base
|
|
203
319
|
end
|
204
320
|
end
|
205
321
|
```
|
206
|
-
which will give you `{scores: {taste: "bad", smell: "good"}}` for params `{"ratings" => {"taste" => "bad", "smell" => "good"}}`
|
207
322
|
|
208
|
-
|
323
|
+
```ruby
|
324
|
+
# Example params
|
325
|
+
{
|
326
|
+
"ratings" => {
|
327
|
+
"taste" => "bad",
|
328
|
+
"smell" => "good"
|
329
|
+
}
|
330
|
+
}
|
331
|
+
# Resulting hash
|
332
|
+
{
|
333
|
+
scores: {
|
334
|
+
taste: "bad",
|
335
|
+
smell: "good"
|
336
|
+
}
|
337
|
+
}
|
338
|
+
```
|
339
|
+
|
340
|
+
#### Deserialize into Parent Object
|
341
|
+
To deserialize `ratings` into the `dish` object, you can use `object`:
|
209
342
|
|
210
343
|
```ruby
|
211
344
|
class DishDeserializer < Deserializer::Base
|
@@ -216,9 +349,17 @@ class DishDeserializer < Deserializer::Base
|
|
216
349
|
end
|
217
350
|
end
|
218
351
|
```
|
219
|
-
which will give you `{taste: "bad", smell: "good"}` for params `{"ratings" => {"taste" => "bad", "smell" => "good"}}`
|
220
352
|
|
221
|
-
|
353
|
+
```ruby
|
354
|
+
# Resulting hash
|
355
|
+
{
|
356
|
+
taste: "bad",
|
357
|
+
smell: "good"
|
358
|
+
}
|
359
|
+
```
|
360
|
+
|
361
|
+
#### Deserialize into a Different Sub-object
|
362
|
+
|
222
363
|
```ruby
|
223
364
|
class DishDeserializer < Deserializer::Base
|
224
365
|
has_one :colors, deserializer: ColorsDeserializer
|
@@ -229,99 +370,126 @@ class DishDeserializer < Deserializer::Base
|
|
229
370
|
end
|
230
371
|
end
|
231
372
|
```
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
373
|
+
|
374
|
+
Given params:
|
375
|
+
|
376
|
+
```ruby
|
377
|
+
# Example params
|
378
|
+
{
|
379
|
+
"ratings" =>
|
380
|
+
{
|
237
381
|
"taste" => "bad",
|
238
382
|
"smell" => "good"
|
239
|
-
},
|
240
|
-
"colors" =>
|
241
|
-
{
|
383
|
+
},
|
384
|
+
"colors" =>
|
385
|
+
{
|
242
386
|
"color" => "red"
|
243
387
|
}
|
244
388
|
}
|
389
|
+
# Resulting hash
|
390
|
+
{
|
391
|
+
ratings: {
|
392
|
+
taste: "bad",
|
393
|
+
smell: "good",
|
394
|
+
color: "red"
|
395
|
+
}
|
396
|
+
}
|
245
397
|
```
|
246
|
-
, will give you `{ratings: {taste: "bad", smell: "good", color: "red"}}`
|
247
398
|
|
248
|
-
###
|
399
|
+
### has_many
|
400
|
+
Not supported as it's an odd thing for a write endpoint to support, but can easily be added.
|
401
|
+
|
402
|
+
### nests
|
403
|
+
Sometimes you get a flat param list, but want it to be nested for `updated_nested_attributes`
|
249
404
|
|
250
|
-
|
405
|
+
If you have 2 models that look like
|
251
406
|
|
252
407
|
```ruby
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
:user_id
|
258
|
-
:description
|
408
|
+
class RestaurantLocation
|
409
|
+
belongs_to :address
|
410
|
+
# t.string :name
|
411
|
+
end
|
259
412
|
|
260
|
-
|
413
|
+
# where Address is something like
|
414
|
+
t.string :line_1
|
415
|
+
t.string :line_2
|
416
|
+
t.string :city
|
417
|
+
t.string :state
|
418
|
+
```
|
261
419
|
|
262
|
-
|
420
|
+
And you want to update them at the same time, as they're closely tied, `nests` lets you define
|
263
421
|
|
264
|
-
|
265
|
-
|
266
|
-
|
422
|
+
```ruby
|
423
|
+
class ResaturantLocationDeserializer < Deserializer::Base
|
424
|
+
attribute :name
|
267
425
|
|
268
|
-
|
269
|
-
end
|
426
|
+
nests :address, deserializer: AddressDesrializer
|
270
427
|
end
|
271
|
-
```
|
272
428
|
|
273
|
-
|
429
|
+
class AddressDeserializer
|
430
|
+
attributes :line_1,
|
431
|
+
:line_2,
|
432
|
+
:city,
|
433
|
+
:state
|
434
|
+
end
|
435
|
+
```
|
436
|
+
And now you can take a single block of json
|
274
437
|
|
275
438
|
```ruby
|
276
|
-
|
277
|
-
|
278
|
-
|
439
|
+
# Example params into restaurant_location endpoint
|
440
|
+
{
|
441
|
+
"name" => "Little Caesars: Et Two Brute",
|
442
|
+
"line_1" => "2 Brute St.",
|
443
|
+
"city" => "Seattle",
|
444
|
+
"state" => "WA"
|
445
|
+
}
|
446
|
+
|
447
|
+
# Resulting hash
|
448
|
+
{
|
449
|
+
name: "Little Caesars: Et Two Brute",
|
450
|
+
address: {
|
451
|
+
line_1: "2 Brute St",
|
452
|
+
city: "Seattle",
|
453
|
+
state: "WA"
|
454
|
+
}
|
455
|
+
}
|
279
456
|
|
280
|
-
attributes :taste,
|
281
|
-
:color,
|
282
|
-
:texture,
|
283
|
-
:smell
|
284
|
-
end
|
285
|
-
end
|
286
|
-
end
|
287
457
|
```
|
288
458
|
|
289
|
-
All of this allows your controller to be so very small, like
|
290
459
|
|
291
|
-
```ruby
|
292
|
-
class DishReviewsController < YourApiController::Base
|
293
|
-
def create
|
294
|
-
@review = DishReview.new( MyApi::V1::DishReviewDeserailzer.from_params(params) )
|
295
460
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
# return sad errors splody
|
300
|
-
end
|
301
|
-
end
|
461
|
+
## Functions
|
462
|
+
### from_params
|
463
|
+
`MyDeserializer.from_params(params)` creates the JSON that your AR model will then consume.
|
302
464
|
|
303
|
-
|
304
|
-
|
465
|
+
```ruby
|
466
|
+
@review = DishReview.new( MyApi::V1::DishReviewDeserailzer.from_params(params) )
|
305
467
|
```
|
306
468
|
|
307
|
-
|
469
|
+
### permitted_params
|
470
|
+
Just call `MyDeserailzer.permitted_params` and you'll have the full array of keys you expect params to have.
|
308
471
|
|
472
|
+
## Installation
|
309
473
|
Add this line to your application's Gemfile:
|
310
474
|
|
311
|
-
|
475
|
+
```
|
476
|
+
gem 'deserializer'
|
477
|
+
```
|
312
478
|
|
313
479
|
And then execute:
|
314
480
|
|
315
|
-
|
481
|
+
```
|
482
|
+
$ bundle
|
483
|
+
```
|
316
484
|
|
317
485
|
Or install it yourself as:
|
318
486
|
|
319
|
-
|
320
|
-
|
487
|
+
```
|
488
|
+
$ gem install deserializer
|
489
|
+
```
|
321
490
|
|
322
491
|
## Contributing
|
323
|
-
|
324
|
-
1. Fork it ( https://github.com/[my-github-username]/deserializer/fork )
|
492
|
+
1. Fork it ( [https://github.com/[my-github-username]/deserializer/fork](https://github.com/[my-github-username]/deserializer/fork) )
|
325
493
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
326
494
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
327
495
|
4. Push to the branch (`git push origin my-new-feature`)
|
data/deserializer.gemspec
CHANGED
data/lib/deserializer.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
require "deserializer/version"
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/concern'
|
2
4
|
|
3
5
|
module Deserializer
|
6
|
+
autoload :Associatable, 'deserializer/associatable'
|
4
7
|
autoload :Base, 'deserializer/base'
|
5
8
|
autoload :DeserializerError, 'deserializer/deserializer_error'
|
6
9
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Deserializer
|
2
|
+
module Associatable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class << self
|
7
|
+
def has_one( target, opts = {})
|
8
|
+
deserializer = opts[:deserializer]
|
9
|
+
|
10
|
+
unless deserializer
|
11
|
+
raise DeserializerError, class: self, message: "has_one associations need a deserilaizer"
|
12
|
+
end
|
13
|
+
|
14
|
+
self.attrs[target] = { attr: nil, deserializer: deserializer }
|
15
|
+
end
|
16
|
+
|
17
|
+
def has_many(*args)
|
18
|
+
raise DeserializerError, class: self, message: "has_many is intentionally unsupported."
|
19
|
+
end
|
20
|
+
|
21
|
+
def belongs_to(*args)
|
22
|
+
raise DeserializerError, class: self, message: "belongs_to is unsupported."
|
23
|
+
end
|
24
|
+
|
25
|
+
def nests(target, opts = {})
|
26
|
+
deserializer = opts[:deserializer]
|
27
|
+
|
28
|
+
unless deserializer
|
29
|
+
raise DeserializerError, class: self, message: "nested associations need a deserilaizer"
|
30
|
+
end
|
31
|
+
|
32
|
+
self.nested_attrs ||= {}
|
33
|
+
self.nested_attrs[target] = { deserializer: deserializer }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/deserializer/base.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
module Deserializer
|
2
2
|
class Base
|
3
|
+
|
4
|
+
## has_one, nested, etc associations
|
5
|
+
include Deserializer::Associatable
|
6
|
+
|
3
7
|
class << self
|
4
|
-
attr_accessor :attrs
|
8
|
+
attr_accessor :attrs, :nested_attrs
|
5
9
|
|
6
10
|
# deserializer interface functions
|
7
11
|
|
@@ -18,24 +22,6 @@ module Deserializer
|
|
18
22
|
self.attrs[key] = {attr: attr, options: options}
|
19
23
|
end
|
20
24
|
|
21
|
-
def has_one( target, opts = {})
|
22
|
-
deserializer = opts[:deserializer]
|
23
|
-
|
24
|
-
unless deserializer
|
25
|
-
raise DeserializerError, class: self, message: "has_one associations need a deserilaizer"
|
26
|
-
end
|
27
|
-
|
28
|
-
self.attrs[target] = {attr: nil, deserializer: deserializer}
|
29
|
-
end
|
30
|
-
|
31
|
-
def has_many(*args)
|
32
|
-
raise DeserializerError, class: self, message: "has_many is currently unsupported."
|
33
|
-
end
|
34
|
-
|
35
|
-
def belongs_to(*args)
|
36
|
-
raise DeserializerError, class: self, message: "belongs_to is currently unsupported."
|
37
|
-
end
|
38
|
-
|
39
25
|
# deserializer usage functions
|
40
26
|
|
41
27
|
def from_params( params = {} )
|
@@ -57,7 +43,7 @@ module Deserializer
|
|
57
43
|
|
58
44
|
# this checks if the object_key is a class that inherits from Deserializer
|
59
45
|
if object_key[:deserializer]
|
60
|
-
|
46
|
+
deseralize_association(param_key, object_key[:deserializer])
|
61
47
|
else
|
62
48
|
attribute = object_key[:attr]
|
63
49
|
options = object_key[:options]
|
@@ -65,6 +51,11 @@ module Deserializer
|
|
65
51
|
assign_value attribute, params[param_key], options
|
66
52
|
end
|
67
53
|
end
|
54
|
+
|
55
|
+
self.class.nested_attrs ||= {}
|
56
|
+
self.class.nested_attrs.each do |target, options|
|
57
|
+
deserialize_nested target, options[:deserializer]
|
58
|
+
end
|
68
59
|
object
|
69
60
|
end
|
70
61
|
|
@@ -73,7 +64,6 @@ module Deserializer
|
|
73
64
|
attr_accessor :params
|
74
65
|
attr_writer :object
|
75
66
|
|
76
|
-
|
77
67
|
def initialize( object = {}, params = {})
|
78
68
|
unless params
|
79
69
|
raise DeserializerError, class: self.class, message: "params cannot be nil"
|
@@ -83,7 +73,17 @@ module Deserializer
|
|
83
73
|
self.object = object
|
84
74
|
end
|
85
75
|
|
86
|
-
def
|
76
|
+
def deseralize_association(association, deserializer)
|
77
|
+
# check for method defining the target object (something, in the example below)
|
78
|
+
#
|
79
|
+
# class ExampleDeserializer < Deserializer::Base
|
80
|
+
# has_one :something, deserializer: SomethingDeserializer
|
81
|
+
#
|
82
|
+
# def something
|
83
|
+
# object
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
|
87
87
|
if self.respond_to? association
|
88
88
|
|
89
89
|
target = self.send( association )
|
@@ -98,6 +98,11 @@ module Deserializer
|
|
98
98
|
deserializer.new( target, params[association] ).deserialize
|
99
99
|
end
|
100
100
|
|
101
|
+
def deserialize_nested( target, deserializer )
|
102
|
+
target = object[target] ||= {}
|
103
|
+
deserializer.new( target, params ).deserialize
|
104
|
+
end
|
105
|
+
|
101
106
|
def assign_value( attribute, value, options = {} )
|
102
107
|
if options[:ignore_empty] && empty?(value)
|
103
108
|
return
|
data/lib/deserializer/version.rb
CHANGED
data/test/lib/deserializers.rb
CHANGED
@@ -110,4 +110,16 @@ class NillableConversionDeserializer < Deserializer::Base
|
|
110
110
|
range_array = Array(value)
|
111
111
|
(range_array[0]..range_array[-1])
|
112
112
|
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class NestedDeserializer < Deserializer::Base
|
116
|
+
attribute :name, key: :attr_1
|
117
|
+
attribute :attr_2
|
118
|
+
end
|
119
|
+
|
120
|
+
class NestableDeserializer < Deserializer::Base
|
121
|
+
attributes :id,
|
122
|
+
:attr_1
|
123
|
+
|
124
|
+
nests :nested_object, deserializer: ::NestedDeserializer
|
113
125
|
end
|
@@ -142,4 +142,17 @@ class DeserializerTest < Minitest::Test
|
|
142
142
|
|
143
143
|
assert_equal expected, NillableConversionDeserializer.from_params( params )
|
144
144
|
end
|
145
|
+
|
146
|
+
def test_using_requires_deserializer
|
147
|
+
assert_raises Deserializer::DeserializerError do
|
148
|
+
BasicDeserializer.nests :splosion
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_supports_using
|
153
|
+
params = { id: 1, attr_1: "blah", attr_2: "something" }
|
154
|
+
expected = { id: 1, attr_1: "blah", nested_object: { name: "blah", attr_2: "something" } }
|
155
|
+
|
156
|
+
assert_equal expected, NestableDeserializer.from_params( params )
|
157
|
+
end
|
145
158
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: deserializer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Greg Orlov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-12-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -54,6 +68,7 @@ files:
|
|
54
68
|
- Rakefile
|
55
69
|
- deserializer.gemspec
|
56
70
|
- lib/deserializer.rb
|
71
|
+
- lib/deserializer/associatable.rb
|
57
72
|
- lib/deserializer/base.rb
|
58
73
|
- lib/deserializer/deserializer_error.rb
|
59
74
|
- lib/deserializer/version.rb
|
@@ -90,4 +105,3 @@ test_files:
|
|
90
105
|
- test/minitest_helper.rb
|
91
106
|
- test/unit/deserializer_error_test.rb
|
92
107
|
- test/unit/deserializer_test.rb
|
93
|
-
has_rdoc:
|