alba 1.4.0 → 1.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 +9 -0
- data/Gemfile +2 -2
- data/README.md +158 -5
- data/benchmark/collection.rb +49 -0
- data/benchmark/single_resource.rb +50 -0
- data/docs/migrate_from_active_model_serializers.md +359 -0
- data/docs/migrate_from_jbuilder.md +223 -0
- data/lib/alba/deprecation.rb +14 -0
- data/lib/alba/resource.rb +73 -17
- data/lib/alba/version.rb +1 -1
- data/lib/alba.rb +21 -5
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 34c441cbc87f959f73c3e7a0175309f780b23ce840add4d53cf5083488390f1f
|
4
|
+
data.tar.gz: f78fb2eea40f502c6f7b3c905317616054bd397d30aa9ce979285003205c81af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 326ac8e731f2b4e0fe4afbb8f690059ecd578ffcb9270b9779bdcb6bb50d57d063ec0a98fc488398915c39ca2978b41e79d53bb1f5a809f9272ad885c383a549
|
7
|
+
data.tar.gz: 2b36eb5d07749505b4ab377b56cc7e564bcd6e86d872e7cb2c45f62ae2cf91a41b2c6e0b70cdcbb8d2a5d802528e4d9bfb113b723a2be7b1bdbc207c0277df45
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [1.5.0] 2021-11-28
|
10
|
+
|
11
|
+
- [Feat] Add nil handler
|
12
|
+
- [Feat] Implement layout feature
|
13
|
+
- [Improve] if option now works with Symbol
|
14
|
+
- [Improve] Add an alias for serialize
|
15
|
+
- [Improve] Deprecation warning now printed with caller
|
16
|
+
|
9
17
|
## [1.4.0] 2021-06-30
|
10
18
|
|
11
19
|
- [Feat] Add a config method to set encoder directly
|
@@ -14,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
14
22
|
- [Feat] Enable setting key for collection with `root_key`
|
15
23
|
- [Feat] Add `Resource.root_key` and `Resource.root_key!`
|
16
24
|
- [Feat] `Alba.serialize` now infers resource class
|
25
|
+
- [Deprecated] `Resource.key` and `Resource.key!` are deprecated
|
17
26
|
|
18
27
|
## [1.3.0] 2021-05-31
|
19
28
|
|
data/Gemfile
CHANGED
@@ -9,8 +9,8 @@ gem 'inch', require: false # For inline documents
|
|
9
9
|
gem 'minitest', '~> 5.14' # For test
|
10
10
|
gem 'rake', '~> 13.0' # For test and automation
|
11
11
|
gem 'rubocop', '>= 0.79.0', require: false # For lint
|
12
|
-
gem 'rubocop-minitest', '~> 0.
|
13
|
-
gem 'rubocop-performance', '~> 1.
|
12
|
+
gem 'rubocop-minitest', '~> 0.17.0', require: false # For lint
|
13
|
+
gem 'rubocop-performance', '~> 1.12.0', require: false # For lint
|
14
14
|
gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
|
15
15
|
gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
|
16
16
|
gem 'simplecov', '~> 0.21.0', require: false # For test coverage
|
data/README.md
CHANGED
@@ -65,9 +65,11 @@ You can find the documentation on [RubyDoc](https://rubydoc.info/github/okuramas
|
|
65
65
|
* Key transformation
|
66
66
|
* Root key inference
|
67
67
|
* Error handling
|
68
|
+
* Nil handling
|
68
69
|
* Resource name inflection based on association name
|
69
70
|
* Circular associations control
|
70
71
|
* [Experimental] Types for validation and conversion
|
72
|
+
* Layout
|
71
73
|
* No runtime dependencies
|
72
74
|
|
73
75
|
## Anti features
|
@@ -129,7 +131,7 @@ Alba.on_error :ignore
|
|
129
131
|
|
130
132
|
For the details, see [Error handling section](#error-handling)
|
131
133
|
|
132
|
-
### Simple serialization with key
|
134
|
+
### Simple serialization with root key
|
133
135
|
|
134
136
|
```ruby
|
135
137
|
class User
|
@@ -146,7 +148,7 @@ end
|
|
146
148
|
class UserResource
|
147
149
|
include Alba::Resource
|
148
150
|
|
149
|
-
|
151
|
+
root_key :user
|
150
152
|
|
151
153
|
attributes :id, :name
|
152
154
|
|
@@ -243,7 +245,7 @@ end
|
|
243
245
|
`Alba.serialize` method is a shortcut to define everything inline.
|
244
246
|
|
245
247
|
```ruby
|
246
|
-
Alba.serialize(user,
|
248
|
+
Alba.serialize(user, root_key: :foo) do
|
247
249
|
attributes :id
|
248
250
|
many :articles do
|
249
251
|
attributes :title, :body
|
@@ -325,7 +327,7 @@ UserResourceCamel.new(user).serialize
|
|
325
327
|
You can also transform root key when:
|
326
328
|
|
327
329
|
* `Alba.enable_inference!` is called
|
328
|
-
* `
|
330
|
+
* `root_key!` is called in Resource class
|
329
331
|
* `root` option of `transform_keys` is set to true or `Alba.enable_root_key_transformation!` is called.
|
330
332
|
|
331
333
|
```ruby
|
@@ -342,7 +344,7 @@ end
|
|
342
344
|
class BankAccountResource
|
343
345
|
include Alba::Resource
|
344
346
|
|
345
|
-
|
347
|
+
root_key!
|
346
348
|
|
347
349
|
attributes :account_number
|
348
350
|
transform_keys :dash, root: true
|
@@ -570,6 +572,95 @@ Alba.on_error do |error, object, key, attribute, resource_class|
|
|
570
572
|
end
|
571
573
|
```
|
572
574
|
|
575
|
+
### Nil handling
|
576
|
+
|
577
|
+
Sometimes we want to convert `nil` to different values such as empty string. Alba provides a flexible way to handle `nil`.
|
578
|
+
|
579
|
+
```ruby
|
580
|
+
class User
|
581
|
+
attr_reader :id, :name, :age
|
582
|
+
|
583
|
+
def initialize(id, name = nil, age = nil)
|
584
|
+
@id = id
|
585
|
+
@name = name
|
586
|
+
@age = age
|
587
|
+
end
|
588
|
+
end
|
589
|
+
|
590
|
+
class UserResource
|
591
|
+
include Alba::Resource
|
592
|
+
|
593
|
+
on_nil { '' }
|
594
|
+
|
595
|
+
root_key :user, :users
|
596
|
+
|
597
|
+
attributes :id, :name, :age
|
598
|
+
end
|
599
|
+
|
600
|
+
UserResource.new(User.new(1)).serialize
|
601
|
+
# => '{"user":{"id":1,"name":"","age":""}}'
|
602
|
+
```
|
603
|
+
|
604
|
+
You can get various information via block parameters.
|
605
|
+
|
606
|
+
```ruby
|
607
|
+
class UserResource
|
608
|
+
include Alba::Resource
|
609
|
+
|
610
|
+
on_nil do |object, key|
|
611
|
+
if key == age
|
612
|
+
20
|
613
|
+
else
|
614
|
+
"User#{object.id}"
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
root_key :user, :users
|
619
|
+
|
620
|
+
attributes :id, :name, :age
|
621
|
+
end
|
622
|
+
|
623
|
+
UserResource.new(User.new(1)).serialize
|
624
|
+
# => '{"user":{"id":1,"name":"User1","age":20}}'
|
625
|
+
```
|
626
|
+
|
627
|
+
You can also set global nil handler.
|
628
|
+
|
629
|
+
```ruby
|
630
|
+
Alba.on_nil { 'default name' }
|
631
|
+
|
632
|
+
class Foo
|
633
|
+
attr_reader :name
|
634
|
+
def initialize(name)
|
635
|
+
@name = name
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
class FooResource
|
640
|
+
include Alba::Resource
|
641
|
+
|
642
|
+
key :foo
|
643
|
+
|
644
|
+
attributes :name
|
645
|
+
end
|
646
|
+
|
647
|
+
FooResource.new(Foo.new).serialize
|
648
|
+
# => '{"foo":{"name":"default name"}}'
|
649
|
+
|
650
|
+
class FooResource2
|
651
|
+
include Alba::Resource
|
652
|
+
|
653
|
+
key :foo
|
654
|
+
|
655
|
+
on_nil { '' } # This is applied instead of global handler
|
656
|
+
|
657
|
+
attributes :name
|
658
|
+
end
|
659
|
+
|
660
|
+
FooResource2.new(Foo.new).serialize
|
661
|
+
# => '{"foo":{"name":""}}'
|
662
|
+
```
|
663
|
+
|
573
664
|
### Metadata
|
574
665
|
|
575
666
|
You can set a metadata with `meta` DSL or `meta` option.
|
@@ -667,6 +758,68 @@ UserResource.new(user).serialize
|
|
667
758
|
|
668
759
|
Note that this feature is experimental and interfaces are subject to change.
|
669
760
|
|
761
|
+
### Layout
|
762
|
+
|
763
|
+
Sometimes we'd like to serialize JSON into a template. In other words, we need some structure OUTSIDE OF serialized JSON. IN HTML world, we call it a "layout".
|
764
|
+
|
765
|
+
Alba supports serializing JSON in a layout. You need a file for layout and then to specify file with `layout` method.
|
766
|
+
|
767
|
+
```erb
|
768
|
+
{
|
769
|
+
"header": "my_header",
|
770
|
+
"body": <%= serialized_json %>
|
771
|
+
}
|
772
|
+
```
|
773
|
+
|
774
|
+
```ruby
|
775
|
+
class FooResource
|
776
|
+
include Alba::Resource
|
777
|
+
layout file: 'my_layout.json.erb'
|
778
|
+
end
|
779
|
+
```
|
780
|
+
|
781
|
+
Note that layout files are treated as `json` and `erb` and evaluated in a context of the resource, meaning
|
782
|
+
|
783
|
+
* A layout file must be a valid JSON
|
784
|
+
* You must write `<%= serialized_json %>` in a layout to put serialized JSON string into a layout
|
785
|
+
* You can access `params` in a layout so that you can add virtually any objects to a layout
|
786
|
+
* When you access `params`, it's usually a Hash. You can use `encode` method in a layout to convert `params` Hash into a JSON with the backend you use
|
787
|
+
* You can also access `object`, the underlying object for the resource
|
788
|
+
|
789
|
+
In case you don't want to have a file for layout, Alba lets you define and apply layouts inline:
|
790
|
+
|
791
|
+
```ruby
|
792
|
+
class FooResource
|
793
|
+
include Alba::Resource
|
794
|
+
layout inline: proc do
|
795
|
+
{
|
796
|
+
header: 'my header',
|
797
|
+
body: serializable_hash
|
798
|
+
}
|
799
|
+
end
|
800
|
+
end
|
801
|
+
```
|
802
|
+
|
803
|
+
In the example above, we specify a Proc which returns a Hash as an inline layout. In the Proc we can use `serializable_hash` method to access a Hash right before serialization.
|
804
|
+
|
805
|
+
You can also use a Proc which returns String, not a Hash, for an inline layout.
|
806
|
+
|
807
|
+
```ruby
|
808
|
+
class FooResource
|
809
|
+
include Alba::Resource
|
810
|
+
layout inline: proc do
|
811
|
+
%({
|
812
|
+
"header": "my header",
|
813
|
+
"body": #{serialized_json}
|
814
|
+
})
|
815
|
+
end
|
816
|
+
end
|
817
|
+
```
|
818
|
+
|
819
|
+
It looks similar to file layout but you must use string interpolation for method calls since it's not an ERB.
|
820
|
+
|
821
|
+
Also note that we use percentage notation here to use double quotes. Using single quotes in inline string layout causes the error which might be resolved in other ways.
|
822
|
+
|
670
823
|
### Caching
|
671
824
|
|
672
825
|
Currently, Alba doesn't support caching, primarily due to the behavior of `ActiveRecord::Relation`'s cache. See [the issue](https://github.com/rails/rails/issues/41784).
|
data/benchmark/collection.rb
CHANGED
@@ -16,8 +16,11 @@ gemfile(true) do
|
|
16
16
|
gem "benchmark-memory"
|
17
17
|
gem "blueprinter"
|
18
18
|
gem "jbuilder"
|
19
|
+
gem "jserializer"
|
19
20
|
gem "jsonapi-serializer" # successor of fast_jsonapi
|
20
21
|
gem "multi_json"
|
22
|
+
gem "panko_serializer"
|
23
|
+
gem "pg"
|
21
24
|
gem "primalize"
|
22
25
|
gem "oj"
|
23
26
|
gem "representable"
|
@@ -27,7 +30,9 @@ end
|
|
27
30
|
|
28
31
|
# --- Test data model setup ---
|
29
32
|
|
33
|
+
require "pg"
|
30
34
|
require "active_record"
|
35
|
+
require "active_record/connection_adapters/postgresql_adapter"
|
31
36
|
require "logger"
|
32
37
|
require "oj"
|
33
38
|
require "sqlite3"
|
@@ -157,6 +162,22 @@ class Comment
|
|
157
162
|
end
|
158
163
|
end
|
159
164
|
|
165
|
+
# --- Jserializer serializers ---
|
166
|
+
|
167
|
+
require 'jserializer'
|
168
|
+
|
169
|
+
class JserializerCommentSerializer < Jserializer::Base
|
170
|
+
attributes :id, :body
|
171
|
+
end
|
172
|
+
|
173
|
+
class JserializerPostSerializer < Jserializer::Base
|
174
|
+
attributes :id, :body, :commenter_names
|
175
|
+
has_many :comments, serializer: JserializerCommentSerializer
|
176
|
+
def commenter_names
|
177
|
+
object.commenters.pluck(:name)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
160
181
|
# --- JSONAPI:Serializer serializers / (successor of fast_jsonapi) ---
|
161
182
|
|
162
183
|
class JsonApiStandardCommentSerializer
|
@@ -218,6 +239,26 @@ class JsonApiSameFormatPostSerializer < JsonApiSameFormatSerializer
|
|
218
239
|
end
|
219
240
|
end
|
220
241
|
|
242
|
+
# --- Panko serializers ---
|
243
|
+
#
|
244
|
+
|
245
|
+
require "panko_serializer"
|
246
|
+
|
247
|
+
class PankoCommentSerializer < Panko::Serializer
|
248
|
+
attributes :id, :body
|
249
|
+
end
|
250
|
+
|
251
|
+
|
252
|
+
class PankoPostSerializer < Panko::Serializer
|
253
|
+
attributes :id, :body, :commenter_names
|
254
|
+
|
255
|
+
has_many :comments, serializer: PankoCommentSerializer
|
256
|
+
|
257
|
+
def commenter_names
|
258
|
+
object.comments.pluck(:name)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
221
262
|
# --- Primalize serializers ---
|
222
263
|
#
|
223
264
|
class PrimalizeCommentResource < Primalize::Single
|
@@ -328,8 +369,10 @@ jbuilder = Proc.new do
|
|
328
369
|
end
|
329
370
|
end.target!
|
330
371
|
end
|
372
|
+
jserializer = Proc.new { JserializerPostSerializer.new(posts, is_collection: true).to_json }
|
331
373
|
jsonapi = proc { JsonApiStandardPostSerializer.new(posts).to_json }
|
332
374
|
jsonapi_same_format = proc { JsonApiSameFormatPostSerializer.new(posts).to_json }
|
375
|
+
panko = proc { Panko::ArraySerializer.new(posts, each_serializer: PankoPostSerializer).to_json }
|
333
376
|
primalize = proc { PrimalizePostsResource.new(posts: posts).to_json }
|
334
377
|
rails = Proc.new do
|
335
378
|
ActiveSupport::JSON.encode(posts.map{ |post| post.serializable_hash(include: :comments) })
|
@@ -346,8 +389,10 @@ puts "Serializer outputs ----------------------------------"
|
|
346
389
|
ams: ams,
|
347
390
|
blueprinter: blueprinter,
|
348
391
|
jbuilder: jbuilder, # different order
|
392
|
+
jserializer: jserializer,
|
349
393
|
jsonapi: jsonapi, # nested JSON:API format
|
350
394
|
jsonapi_same_format: jsonapi_same_format,
|
395
|
+
panko: panko,
|
351
396
|
primalize: primalize,
|
352
397
|
rails: rails,
|
353
398
|
representable: representable,
|
@@ -363,8 +408,10 @@ Benchmark.ips do |x|
|
|
363
408
|
x.report(:ams, &ams)
|
364
409
|
x.report(:blueprinter, &blueprinter)
|
365
410
|
x.report(:jbuilder, &jbuilder)
|
411
|
+
x.report(:jserializer, &jserializer)
|
366
412
|
x.report(:jsonapi, &jsonapi)
|
367
413
|
x.report(:jsonapi_same_format, &jsonapi_same_format)
|
414
|
+
x.report(:panko, &panko)
|
368
415
|
x.report(:primalize, &primalize)
|
369
416
|
x.report(:rails, &rails)
|
370
417
|
x.report(:representable, &representable)
|
@@ -381,8 +428,10 @@ Benchmark.memory do |x|
|
|
381
428
|
x.report(:ams, &ams)
|
382
429
|
x.report(:blueprinter, &blueprinter)
|
383
430
|
x.report(:jbuilder, &jbuilder)
|
431
|
+
x.report(:jserializer, &jserializer)
|
384
432
|
x.report(:jsonapi, &jsonapi)
|
385
433
|
x.report(:jsonapi_same_format, &jsonapi_same_format)
|
434
|
+
x.report(:panko, &panko)
|
386
435
|
x.report(:primalize, &primalize)
|
387
436
|
x.report(:rails, &rails)
|
388
437
|
x.report(:representable, &representable)
|
@@ -13,10 +13,14 @@ gemfile(true) do
|
|
13
13
|
gem "activerecord", "6.1.3"
|
14
14
|
gem "alba", path: '../'
|
15
15
|
gem "benchmark-ips"
|
16
|
+
gem "benchmark-memory"
|
16
17
|
gem "blueprinter"
|
17
18
|
gem "jbuilder"
|
19
|
+
gem "jserializer"
|
18
20
|
gem "jsonapi-serializer" # successor of fast_jsonapi
|
19
21
|
gem "multi_json"
|
22
|
+
gem "panko_serializer"
|
23
|
+
gem "pg"
|
20
24
|
gem "primalize"
|
21
25
|
gem "oj"
|
22
26
|
gem "representable"
|
@@ -26,7 +30,9 @@ end
|
|
26
30
|
|
27
31
|
# --- Test data model setup ---
|
28
32
|
|
33
|
+
require "pg"
|
29
34
|
require "active_record"
|
35
|
+
require "active_record/connection_adapters/postgresql_adapter"
|
30
36
|
require "logger"
|
31
37
|
require "oj"
|
32
38
|
require "sqlite3"
|
@@ -154,6 +160,23 @@ class Comment
|
|
154
160
|
end
|
155
161
|
end
|
156
162
|
|
163
|
+
# --- Jserializer serializers ---
|
164
|
+
|
165
|
+
require 'jserializer'
|
166
|
+
|
167
|
+
class JserializerCommentSerializer < Jserializer::Base
|
168
|
+
attributes :id, :body
|
169
|
+
end
|
170
|
+
|
171
|
+
class JserializerPostSerializer < Jserializer::Base
|
172
|
+
attributes :id, :body, :commenter_names
|
173
|
+
has_many :comments, serializer: JserializerCommentSerializer
|
174
|
+
def commenter_names
|
175
|
+
object.commenters.pluck(:name)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
|
157
180
|
# --- JSONAPI:Serializer serializers / (successor of fast_jsonapi) ---
|
158
181
|
|
159
182
|
class JsonApiStandardCommentSerializer
|
@@ -215,6 +238,25 @@ class JsonApiSameFormatPostSerializer < JsonApiSameFormatSerializer
|
|
215
238
|
end
|
216
239
|
end
|
217
240
|
|
241
|
+
# --- Panko serializers ---
|
242
|
+
#
|
243
|
+
require "panko_serializer"
|
244
|
+
|
245
|
+
class PankoCommentSerializer < Panko::Serializer
|
246
|
+
attributes :id, :body
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
class PankoPostSerializer < Panko::Serializer
|
251
|
+
attributes :id, :body, :commenter_names
|
252
|
+
|
253
|
+
has_many :comments, serializer: PankoCommentSerializer
|
254
|
+
|
255
|
+
def commenter_names
|
256
|
+
object.comments.pluck(:name)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
218
260
|
# --- Primalize serializers ---
|
219
261
|
#
|
220
262
|
class PrimalizeCommentResource < Primalize::Single
|
@@ -308,8 +350,10 @@ end
|
|
308
350
|
ams = Proc.new { AMSPostSerializer.new(post, {}).to_json }
|
309
351
|
blueprinter = Proc.new { PostBlueprint.render(post) }
|
310
352
|
jbuilder = Proc.new { post.to_builder.target! }
|
353
|
+
jserializer = Proc.new { JserializerPostSerializer.new(post).to_json }
|
311
354
|
jsonapi = proc { JsonApiStandardPostSerializer.new(post).to_json }
|
312
355
|
jsonapi_same_format = proc { JsonApiSameFormatPostSerializer.new(post).to_json }
|
356
|
+
panko = proc { PankoPostSerializer.new.serialize_to_json(post) }
|
313
357
|
primalize = proc { PrimalizePostResource.new(post).to_json }
|
314
358
|
rails = Proc.new { ActiveSupport::JSON.encode(post.serializable_hash(include: :comments)) }
|
315
359
|
representable = Proc.new { PostRepresenter.new(post).to_json }
|
@@ -324,8 +368,10 @@ puts "Serializer outputs ----------------------------------"
|
|
324
368
|
ams: ams,
|
325
369
|
blueprinter: blueprinter,
|
326
370
|
jbuilder: jbuilder, # different order
|
371
|
+
jserializer: jserializer,
|
327
372
|
jsonapi: jsonapi, # nested JSON:API format
|
328
373
|
jsonapi_same_format: jsonapi_same_format,
|
374
|
+
panko: panko,
|
329
375
|
primalize: primalize,
|
330
376
|
rails: rails,
|
331
377
|
representable: representable,
|
@@ -341,8 +387,10 @@ Benchmark.ips do |x|
|
|
341
387
|
x.report(:ams, &ams)
|
342
388
|
x.report(:blueprinter, &blueprinter)
|
343
389
|
x.report(:jbuilder, &jbuilder)
|
390
|
+
x.report(:jserializer, &jserializer)
|
344
391
|
x.report(:jsonapi, &jsonapi)
|
345
392
|
x.report(:jsonapi_same_format, &jsonapi_same_format)
|
393
|
+
x.report(:panko, &panko)
|
346
394
|
x.report(:primalize, &primalize)
|
347
395
|
x.report(:rails, &rails)
|
348
396
|
x.report(:representable, &representable)
|
@@ -359,8 +407,10 @@ Benchmark.memory do |x|
|
|
359
407
|
x.report(:ams, &ams)
|
360
408
|
x.report(:blueprinter, &blueprinter)
|
361
409
|
x.report(:jbuilder, &jbuilder)
|
410
|
+
x.report(:jserializer, &jserializer)
|
362
411
|
x.report(:jsonapi, &jsonapi)
|
363
412
|
x.report(:jsonapi_same_format, &jsonapi_same_format)
|
413
|
+
x.report(:panko, &panko)
|
364
414
|
x.report(:primalize, &primalize)
|
365
415
|
x.report(:rails, &rails)
|
366
416
|
x.report(:representable, &representable)
|
@@ -0,0 +1,359 @@
|
|
1
|
+
---
|
2
|
+
title: Upgrading from ActiveModelSerializers
|
3
|
+
---
|
4
|
+
|
5
|
+
<!-- @format -->
|
6
|
+
|
7
|
+
This guide is aimed at helping ActiveModelSerializers users transition to Alba, and it consists of three parts:
|
8
|
+
|
9
|
+
1. Basic serialization
|
10
|
+
2. Complex serialization
|
11
|
+
3. Unsupported features
|
12
|
+
|
13
|
+
## Example class
|
14
|
+
|
15
|
+
Example clsss is inherited `ActiveRecord::Base`, because [serializing PORO with AMS is pretty hard](https://github.com/rails-api/active_model_serializers/blob/0-10-stable/docs/howto/serialize_poro.md).
|
16
|
+
|
17
|
+
```rb
|
18
|
+
class User < ActiveRecord::Base
|
19
|
+
# columns: id, created_at, updated_at
|
20
|
+
has_one :profile
|
21
|
+
has_many :articles
|
22
|
+
end
|
23
|
+
|
24
|
+
class Profile < ActiveRecord::Base
|
25
|
+
# columns: id, user_id, email, created_at, updated_at
|
26
|
+
belongs_to :user
|
27
|
+
end
|
28
|
+
|
29
|
+
class Article < ActiveRecord::Base
|
30
|
+
# columns: id, user_id, title, body, created_at, updated_at
|
31
|
+
belongs_to :user
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
## 1. Basic serialization
|
36
|
+
|
37
|
+
### #serializable_hash
|
38
|
+
|
39
|
+
- or #as_json, #to_json
|
40
|
+
|
41
|
+
#### ActiveModelSerializer
|
42
|
+
|
43
|
+
```rb
|
44
|
+
# Infer and use by "#{MODEL_NAME}Serializer" in app/serializers/user_serializer.rb
|
45
|
+
class UserSerializer < ActiveModel::Serializer
|
46
|
+
type :user
|
47
|
+
attributes :id, :created_at, :updated_at
|
48
|
+
end
|
49
|
+
|
50
|
+
# serialze
|
51
|
+
user = User.create!
|
52
|
+
ActiveModelSerializers::SerializableResource.new(
|
53
|
+
user
|
54
|
+
).serializable_hash
|
55
|
+
# => {
|
56
|
+
# user: {
|
57
|
+
# id: id,
|
58
|
+
# created_at: created_at,
|
59
|
+
# updated_at: updated_at
|
60
|
+
# }
|
61
|
+
# }
|
62
|
+
```
|
63
|
+
|
64
|
+
#### Alba
|
65
|
+
|
66
|
+
```rb
|
67
|
+
# Infer and use by "#{MODEL_NAME}Resource"
|
68
|
+
# In app/resources/user_resource.rb
|
69
|
+
class UserResource
|
70
|
+
include Alba::Resource
|
71
|
+
attributes :id, :created_at, :updated_at
|
72
|
+
end
|
73
|
+
|
74
|
+
# serialze
|
75
|
+
user = User.create!
|
76
|
+
UserResource.new(user).serializable_hash
|
77
|
+
# => {
|
78
|
+
# id: id,
|
79
|
+
# created_at: created_at,
|
80
|
+
# updated_at: updated_at,
|
81
|
+
# }
|
82
|
+
|
83
|
+
# If want `user key`
|
84
|
+
class UserResource
|
85
|
+
include Alba::Resource
|
86
|
+
root_key :user # Call root_key method like ActiveModel::Serializer#type
|
87
|
+
attributes :id, :created_at, :updated_at
|
88
|
+
end
|
89
|
+
|
90
|
+
# serialze
|
91
|
+
user = User.create!
|
92
|
+
JSON.parse UserResource.new(user).serialize # !!!!serializable_hash does not support root key!!! Must use JSON.parse and serialize
|
93
|
+
# => {
|
94
|
+
# "user"=>{
|
95
|
+
# "id"=>id,
|
96
|
+
# "created_at"=>created_at,
|
97
|
+
# "updated_at"=>updated_at
|
98
|
+
# }
|
99
|
+
# }
|
100
|
+
# If want symbolize keys with #deep_symbolize_keys in Rails
|
101
|
+
user = User.create!
|
102
|
+
JSON.parse(UserResource.new(user).serialize).deep_symbolize_keys
|
103
|
+
# => {
|
104
|
+
# user: {
|
105
|
+
# id: id,
|
106
|
+
# created_at: created_at,
|
107
|
+
# updated_at: updated_at
|
108
|
+
# }
|
109
|
+
# }
|
110
|
+
```
|
111
|
+
|
112
|
+
## 2. Complex serialization
|
113
|
+
|
114
|
+
### Serialize collections
|
115
|
+
|
116
|
+
#### ActiveModelSerializer
|
117
|
+
|
118
|
+
```rb
|
119
|
+
class UserSerializer < ActiveModel::Serializer
|
120
|
+
type :user
|
121
|
+
attributes :id, :created_at, :updated_at
|
122
|
+
end
|
123
|
+
3.times { User.create! }
|
124
|
+
users = User.limit 3
|
125
|
+
ActiveModelSerializers::SerializableResource.new(
|
126
|
+
users,
|
127
|
+
adapter: :attributes # Comment out this line if you want users key
|
128
|
+
# Want to specified key to call with root: args
|
129
|
+
).serializable_hash
|
130
|
+
# => [{:id=>1, :created_at=>created_at, :updated_at=>updated_at},
|
131
|
+
# {:id=>2, :created_at=>created_at, :updated_at=>updated_at},
|
132
|
+
# {:id=>3, :created_at=>created_at, :updated_at=>updated_at}]
|
133
|
+
```
|
134
|
+
|
135
|
+
#### Alba
|
136
|
+
|
137
|
+
```rb
|
138
|
+
class UserResource
|
139
|
+
include Alba::Resource
|
140
|
+
attributes :id, :created_at, :updated_at
|
141
|
+
end
|
142
|
+
3.times { User.create! }
|
143
|
+
users = User.limit 3
|
144
|
+
UserResource.new(users).serializable_hash
|
145
|
+
# =>[{:id=>1, :created_at=>created_at, :updated_at=>updated_at},
|
146
|
+
# {:id=>2, :created_at=>created_at, :updated_at=>updated_at},
|
147
|
+
# {:id=>3, :created_at=>created_at, :updated_at=>updated_at}]
|
148
|
+
# or
|
149
|
+
JSON.parse UserResource.new(users).serialize(root_key: :users)
|
150
|
+
# => {"users"=>
|
151
|
+
# [{"id"=>1, "created_at"=>created_at, "updated_at"=>updated_at},
|
152
|
+
# {"id"=>2, "created_at"=>created_at, "updated_at"=>updated_at},
|
153
|
+
# {"id"=>3, "created_at"=>created_at, "updated_at"=>updated_at}]}
|
154
|
+
```
|
155
|
+
|
156
|
+
### Nested serialization
|
157
|
+
|
158
|
+
#### ActiveModelSerializer
|
159
|
+
|
160
|
+
```rb
|
161
|
+
class ProfileSerializer < ActiveModel::Serializer
|
162
|
+
type :profile
|
163
|
+
attributes :email
|
164
|
+
end
|
165
|
+
|
166
|
+
class ArticleSerializer < ActiveModel::Serializer
|
167
|
+
type :article
|
168
|
+
attributes :title, :body
|
169
|
+
end
|
170
|
+
|
171
|
+
class UserSerializer < ActiveModel::Serializer
|
172
|
+
type :user
|
173
|
+
attributes :id, :created_at, :updated_at
|
174
|
+
has_one :profile, serializer: ProfileSerializer # For has_one relation
|
175
|
+
has_many :articles, serializer: ArticleSerializer # For has_many relation
|
176
|
+
end
|
177
|
+
user = User.create!
|
178
|
+
user.craete_profile! email: email
|
179
|
+
user.articles.create! title: title, body: body
|
180
|
+
ActiveModelSerializers::SerializableResource.new(
|
181
|
+
user
|
182
|
+
).serializable_hash
|
183
|
+
# => {
|
184
|
+
# :user=> {
|
185
|
+
# :id=>1,
|
186
|
+
# :created_at=>created_at,
|
187
|
+
# :updated_at=>updated_at,
|
188
|
+
# :profile=> {
|
189
|
+
# :email=>email
|
190
|
+
# },
|
191
|
+
# :articles => [
|
192
|
+
# {
|
193
|
+
# :title=>title,
|
194
|
+
# :body=>body
|
195
|
+
# }
|
196
|
+
# ]
|
197
|
+
# }
|
198
|
+
# }
|
199
|
+
```
|
200
|
+
|
201
|
+
#### Alba
|
202
|
+
|
203
|
+
```rb
|
204
|
+
class ProfileResource
|
205
|
+
include Alba::Resource
|
206
|
+
root_key :profile
|
207
|
+
attributes :email
|
208
|
+
end
|
209
|
+
|
210
|
+
class ArticleResource
|
211
|
+
include Alba::Resource
|
212
|
+
root_key :article
|
213
|
+
attributes :title, :body
|
214
|
+
end
|
215
|
+
|
216
|
+
class UserResource
|
217
|
+
include Alba::Resource
|
218
|
+
root_key :user
|
219
|
+
attributes :id, :created_at, :updated_at
|
220
|
+
one :profile, resource: ProfileResource # For has_one relation
|
221
|
+
many :articles, resource: ArticleResource # For has_many relation
|
222
|
+
end
|
223
|
+
|
224
|
+
user = User.create!
|
225
|
+
user.craete_profile! email: email
|
226
|
+
user.articles.create! title: title, body: body
|
227
|
+
UserResource.new(user).serializable_hash
|
228
|
+
# => {
|
229
|
+
# :id=>1,
|
230
|
+
# :created_at=>created_at,
|
231
|
+
# :updated_at=>updated_at,
|
232
|
+
# :profile=> {
|
233
|
+
# :email=>email
|
234
|
+
# },
|
235
|
+
# :articles => [
|
236
|
+
# {
|
237
|
+
# :title=>title,
|
238
|
+
# :body=>body
|
239
|
+
# }
|
240
|
+
# ]
|
241
|
+
# }
|
242
|
+
```
|
243
|
+
|
244
|
+
### Serialize with custom serializer
|
245
|
+
|
246
|
+
#### ActiveModelSerializer
|
247
|
+
|
248
|
+
```rb
|
249
|
+
class CustomUserSerializer < ActiveModel::Serializer
|
250
|
+
type :user
|
251
|
+
attribute :email do
|
252
|
+
object.profile.email
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# serialze
|
257
|
+
user = User.create!
|
258
|
+
user.craete_profile! email: email
|
259
|
+
ActiveModelSerializers::SerializableResource.new(
|
260
|
+
user,
|
261
|
+
serializer: ::CustomUserSerializer # Call with serializer arg
|
262
|
+
).serializable_hash
|
263
|
+
# => {
|
264
|
+
# user: {
|
265
|
+
# email: email
|
266
|
+
# }
|
267
|
+
# }
|
268
|
+
```
|
269
|
+
|
270
|
+
#### Alba
|
271
|
+
|
272
|
+
```rb
|
273
|
+
class CustomUserResource
|
274
|
+
include Alba::Resource
|
275
|
+
root_key :user
|
276
|
+
attribute :email do
|
277
|
+
object.profile.email
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# serialze
|
282
|
+
user = User.create!
|
283
|
+
user.craete_profile! email: email
|
284
|
+
CustomUserResource.new(user).serializable_hash
|
285
|
+
# => {
|
286
|
+
# email: email
|
287
|
+
# }
|
288
|
+
```
|
289
|
+
|
290
|
+
### Passing arbitrary options to a serializer
|
291
|
+
|
292
|
+
#### ActiveModelSerializer
|
293
|
+
|
294
|
+
```rb
|
295
|
+
class UserSerializer < ApplicationSerializer
|
296
|
+
type :user
|
297
|
+
attributes :id, :created_at, :updated_at
|
298
|
+
attribute :custom_params do
|
299
|
+
pp instance_options
|
300
|
+
# => given_params: { a: :b }
|
301
|
+
instance_options # Access by instance_options method
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
# serialze
|
306
|
+
user = User.create!
|
307
|
+
ActiveModelSerializers::SerializableResource.new(
|
308
|
+
user,
|
309
|
+
given_params: { a: :b } # Give with your favorite keyword argument
|
310
|
+
).serializable_hash
|
311
|
+
# => {
|
312
|
+
# :id=>1,
|
313
|
+
# :created_at=>created_at,
|
314
|
+
# :updated_at=>updated_at,
|
315
|
+
# :custom_params=>{
|
316
|
+
# :given_params=>{
|
317
|
+
# :a=>:b
|
318
|
+
# }
|
319
|
+
# }
|
320
|
+
# }
|
321
|
+
```
|
322
|
+
|
323
|
+
#### Alba
|
324
|
+
|
325
|
+
```rb
|
326
|
+
class UserResource
|
327
|
+
include Alba::Resource
|
328
|
+
root_key :user
|
329
|
+
attributes :id, :created_at, :updated_at
|
330
|
+
attribute :custom_params do
|
331
|
+
pp params
|
332
|
+
# => { :a=>:b }
|
333
|
+
params
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
# serialze
|
338
|
+
user = User.create!
|
339
|
+
UserResource.new(
|
340
|
+
user,
|
341
|
+
params: { a: :b } # Give with :params keyword argument
|
342
|
+
).serializable_hash
|
343
|
+
# => {
|
344
|
+
# :id=>1,
|
345
|
+
# :created_at=>created_at,
|
346
|
+
# :updated_at=>updated_at,
|
347
|
+
# :custom_params=>{
|
348
|
+
# :a=>:b
|
349
|
+
# }
|
350
|
+
# }
|
351
|
+
```
|
352
|
+
|
353
|
+
## 3. Unsupported features
|
354
|
+
|
355
|
+
- [RelationshipLinks](https://github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/howto/add_relationship_links.md)
|
356
|
+
- [PaginationLinks](https://github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/howto/add_pagination_links.md)
|
357
|
+
- [Logging](https://github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/general/logging.md)
|
358
|
+
- [Caching](https://github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/general/caching.md)
|
359
|
+
- [Rendering](https://github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/general/rendering.md)
|
@@ -0,0 +1,223 @@
|
|
1
|
+
---
|
2
|
+
title: Upgrading from Jbuilder
|
3
|
+
---
|
4
|
+
|
5
|
+
<!-- @format -->
|
6
|
+
|
7
|
+
This guide is aimed at helping Jbuilder users transition to Alba, and it consists of three parts:
|
8
|
+
|
9
|
+
1. Basic serialization
|
10
|
+
2. Complex serialization
|
11
|
+
3. Unsupported features
|
12
|
+
|
13
|
+
## Example class
|
14
|
+
|
15
|
+
This example will also be replaced by ActiveReord.
|
16
|
+
|
17
|
+
```rb
|
18
|
+
class User
|
19
|
+
attr_reader :id, :created_at, :updated_at
|
20
|
+
attr_accessor :profile, :articles
|
21
|
+
|
22
|
+
def initialize(id)
|
23
|
+
@id = id
|
24
|
+
@created_at = Time.now
|
25
|
+
@updated_at = Time.now
|
26
|
+
@articles = []
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Profile
|
31
|
+
attr_reader :email
|
32
|
+
|
33
|
+
def initialize(email)
|
34
|
+
@email = email
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Article
|
39
|
+
attr_accessor :title, :body
|
40
|
+
|
41
|
+
def initialize(title, body)
|
42
|
+
@title = title
|
43
|
+
@body = body
|
44
|
+
end
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
## 1. Basic serialization
|
49
|
+
|
50
|
+
#### Jbuilder
|
51
|
+
|
52
|
+
```rb
|
53
|
+
# show.json.jbuilder
|
54
|
+
# With block
|
55
|
+
@user = User.new(id)
|
56
|
+
json.user do |user|
|
57
|
+
user.id @user.id
|
58
|
+
user.created_at @user.created_at
|
59
|
+
user.updated_at @user.updated_at
|
60
|
+
end
|
61
|
+
# => '{"user":{"id":id, "created_at": created_at, "updated_at": updated_at}'
|
62
|
+
# or #extract!
|
63
|
+
json.extract! @user, :id, :created_at, :updated_at
|
64
|
+
# => '{"id":id, "created_at": created_at, "updated_at": updated_at}'
|
65
|
+
```
|
66
|
+
|
67
|
+
#### Alba
|
68
|
+
|
69
|
+
```rb
|
70
|
+
# With block
|
71
|
+
user = User.new(id)
|
72
|
+
Alba.serialize(user, root_key: :user) do
|
73
|
+
attributes :id, :created_at, :updated_at
|
74
|
+
end
|
75
|
+
# => '{"user":{"id":id, "created_at": created_at, "updated_at": updated_at}'
|
76
|
+
# or with resourceClass.
|
77
|
+
# Infer and use by "#{MODEL_NAME}Resource"
|
78
|
+
class UserResource
|
79
|
+
include Alba::Resource
|
80
|
+
root_key :user
|
81
|
+
attributes :id, :created_at, :updated_at
|
82
|
+
end
|
83
|
+
UserResource.new(user).serialize
|
84
|
+
# => '{"user":{"id":id, "created_at": created_at, "updated_at": updated_at}'
|
85
|
+
```
|
86
|
+
|
87
|
+
## 2. Complex serialization
|
88
|
+
|
89
|
+
### Serialize collections
|
90
|
+
|
91
|
+
#### Jbuilder
|
92
|
+
|
93
|
+
```rb
|
94
|
+
@users = ids.map { |id| User.new(id) }
|
95
|
+
# index.json.jbuilder
|
96
|
+
json.array! @users, :id, :created_at, :updated_at
|
97
|
+
# => '[{"id":id, "created_at": created_at, "updated_at": updated_at}, {"id":id, "created_at": created_at, "updated_at": updated_at}, {"id":id, "created_at": created_at, "updated_at": updated_at}]'
|
98
|
+
```
|
99
|
+
|
100
|
+
#### Alba
|
101
|
+
|
102
|
+
```rb
|
103
|
+
class UserResource
|
104
|
+
include Alba::Resource
|
105
|
+
root_key :user
|
106
|
+
attributes :id, :created_at, :updated_at
|
107
|
+
end
|
108
|
+
users = ids.map { |id| User.new(id) }
|
109
|
+
UserResource.new(users).serialize
|
110
|
+
# => '[{"id":id, "created_at": created_at, "updated_at": updated_at}, {"id":id, "created_at": created_at, "updated_at": updated_at}, {"id":id, "created_at": created_at, "updated_at": updated_at}]'
|
111
|
+
|
112
|
+
```
|
113
|
+
|
114
|
+
### Nested serialization
|
115
|
+
|
116
|
+
#### Jbuilder
|
117
|
+
|
118
|
+
```rb
|
119
|
+
# show.json.jbuilder
|
120
|
+
@user = User.new(id)
|
121
|
+
@user.profile = Profile.new(email)
|
122
|
+
@user.articles = [Article.new(title, body)]
|
123
|
+
json.user do |user|
|
124
|
+
user.id @user.id
|
125
|
+
user.created_at @user.created_at
|
126
|
+
user.updated_at @user.updated_at
|
127
|
+
json.profile do
|
128
|
+
json.email @user.profile.email
|
129
|
+
end
|
130
|
+
json.articles do
|
131
|
+
json.array! @user.articles, :title, :body
|
132
|
+
end
|
133
|
+
end
|
134
|
+
# => '{"user":{"id":id, "created_at": created_at, "updated_at": updated_at, "profile": {"email": email}, articles: [{"title": title, "body": body}]}'
|
135
|
+
# or #merge!
|
136
|
+
profile_hash = { profile: { email: @user.profile.email } }
|
137
|
+
articles_hash = { articles: @user.articles.map { |article| { title: article.title, body: article.body } } }
|
138
|
+
json.user do |user|
|
139
|
+
user.id @user.id
|
140
|
+
user.created_at @user.created_at
|
141
|
+
user.updated_at @user.updated_at
|
142
|
+
json.merge! profile_hash
|
143
|
+
json.merge! articles_hash
|
144
|
+
end
|
145
|
+
# => '{"user":{"id":id, "created_at": created_at, "updated_at": updated_at, "profile": {"email": email}, articles: [{"title": title, "body": body}]}'
|
146
|
+
# or #partial!
|
147
|
+
# profiles/_profile.json.jbuilder
|
148
|
+
json.profile do
|
149
|
+
json.email @profile.email
|
150
|
+
end
|
151
|
+
# articles/_article.json.jbuilder
|
152
|
+
json.extract! article, :title, :body
|
153
|
+
# user/show.json.jbuilder
|
154
|
+
json.user do |user|
|
155
|
+
user.id @user.id
|
156
|
+
user.created_at @user.created_at
|
157
|
+
user.updated_at @user.updated_at
|
158
|
+
json.partial! @user.profile, as: :profile
|
159
|
+
json.articles @user.articles do |article|
|
160
|
+
json.partial! article, partial: 'articles/article'
|
161
|
+
end
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
165
|
+
#### Alba
|
166
|
+
|
167
|
+
```rb
|
168
|
+
# With ResourceClass by each resources
|
169
|
+
class ProfileResource
|
170
|
+
include Alba::Resource
|
171
|
+
root_key :profile
|
172
|
+
attributes :email
|
173
|
+
end
|
174
|
+
class ArticleResource
|
175
|
+
include Alba::Resource
|
176
|
+
root_key :article
|
177
|
+
attributes :title, :body
|
178
|
+
end
|
179
|
+
class UserResource
|
180
|
+
include Alba::Resource
|
181
|
+
root_key :user
|
182
|
+
attributes :id, :created_at, :updated_at
|
183
|
+
one :profile, resource: ProfileResource
|
184
|
+
many :articles, resource: ArticleResource
|
185
|
+
end
|
186
|
+
user = User.new(id)
|
187
|
+
user.profile = Profile.new(email)
|
188
|
+
user.articles = [Article.new(title, body)]
|
189
|
+
UserResource.new(user).serialize
|
190
|
+
# => '{"user":{"id":id, "created_at": created_at, "updated_at": updated_at, "profile": {"email": email}, articles: [{"title": title, "body": body}]}'
|
191
|
+
|
192
|
+
# or #attribute
|
193
|
+
class UserResource
|
194
|
+
include Alba::Resource
|
195
|
+
root_key :user
|
196
|
+
attributes :id, :created_at, :updated_at
|
197
|
+
|
198
|
+
attribute :profile do
|
199
|
+
{
|
200
|
+
email: object.profile.email # Can access to received resource by #object method
|
201
|
+
}
|
202
|
+
end
|
203
|
+
|
204
|
+
attribute :articles do
|
205
|
+
object.articles.map do |article|
|
206
|
+
{
|
207
|
+
title: article.title,
|
208
|
+
body: article.body,
|
209
|
+
}
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
user = User.new(id)
|
214
|
+
user.profile = Profile.new(email)
|
215
|
+
UserResource.new(user).serialize
|
216
|
+
# => '{"user":{"id":id, "created_at": created_at, "updated_at": updated_at, "profile": {"email": email}, articles: [{"title": title, "body": body}]}'
|
217
|
+
```
|
218
|
+
|
219
|
+
## 3. Unsupported features
|
220
|
+
|
221
|
+
- Jbuilder#ignore_nil!
|
222
|
+
- Jbuilder#cache!
|
223
|
+
- Jbuilder.key_format! and Jbuilder.deep_format_keys!
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Alba
|
2
|
+
# Module for printing deprecation warning
|
3
|
+
module Deprecation
|
4
|
+
# Similar to {Kernel.warn} but prints caller as well
|
5
|
+
#
|
6
|
+
# @param message [String] main message to print
|
7
|
+
# @return void
|
8
|
+
def warn(message)
|
9
|
+
Kernel.warn(message)
|
10
|
+
Kernel.warn(caller_locations(2..2).first) # For performance reason we use (2..2).first
|
11
|
+
end
|
12
|
+
module_function :warn
|
13
|
+
end
|
14
|
+
end
|
data/lib/alba/resource.rb
CHANGED
@@ -2,13 +2,14 @@ require_relative 'one'
|
|
2
2
|
require_relative 'many'
|
3
3
|
require_relative 'key_transform_factory'
|
4
4
|
require_relative 'typed_attribute'
|
5
|
+
require_relative 'deprecation'
|
5
6
|
|
6
7
|
module Alba
|
7
8
|
# This module represents what should be serialized
|
8
9
|
module Resource
|
9
10
|
# @!parse include InstanceMethods
|
10
11
|
# @!parse extend ClassMethods
|
11
|
-
DSLS = {_attributes: {}, _key: nil, _key_for_collection: nil, _meta: nil, _transform_key_function: nil, _transforming_root_key: false, _on_error: nil}.freeze # rubocop:disable Layout/LineLength
|
12
|
+
DSLS = {_attributes: {}, _key: nil, _key_for_collection: nil, _meta: nil, _transform_key_function: nil, _transforming_root_key: false, _on_error: nil, _on_nil: nil, _layout: nil}.freeze # rubocop:disable Layout/LineLength
|
12
13
|
private_constant :DSLS
|
13
14
|
|
14
15
|
WITHIN_DEFAULT = Object.new.freeze
|
@@ -48,7 +49,7 @@ module Alba
|
|
48
49
|
# @param meta [Hash] metadata for this seialization
|
49
50
|
# @return [String] serialized JSON string
|
50
51
|
def serialize(key: nil, root_key: nil, meta: {})
|
51
|
-
warn '`key` option to `serialize` method is deprecated, use `root_key` instead.' if key
|
52
|
+
Alba::Deprecation.warn '`key` option to `serialize` method is deprecated, use `root_key` instead.' if key
|
52
53
|
key = key.nil? && root_key.nil? ? fetch_key : root_key || key
|
53
54
|
hash = if key && key != ''
|
54
55
|
h = {key.to_s => serializable_hash}
|
@@ -56,8 +57,9 @@ module Alba
|
|
56
57
|
else
|
57
58
|
serializable_hash
|
58
59
|
end
|
59
|
-
|
60
|
+
serialize_with(hash)
|
60
61
|
end
|
62
|
+
alias to_json serialize
|
61
63
|
|
62
64
|
# A Hash for serialization
|
63
65
|
#
|
@@ -69,6 +71,25 @@ module Alba
|
|
69
71
|
|
70
72
|
private
|
71
73
|
|
74
|
+
attr_reader :serialized_json # Mainly for layout
|
75
|
+
|
76
|
+
def encode(hash)
|
77
|
+
Alba.encoder.call(hash)
|
78
|
+
end
|
79
|
+
|
80
|
+
def serialize_with(hash)
|
81
|
+
@serialized_json = encode(hash)
|
82
|
+
case @_layout
|
83
|
+
when String # file
|
84
|
+
ERB.new(File.read(@_layout)).result(binding)
|
85
|
+
when Proc # inline
|
86
|
+
inline = instance_eval(&@_layout)
|
87
|
+
inline.is_a?(Hash) ? encode(inline) : inline
|
88
|
+
else # no layout
|
89
|
+
@serialized_json
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
72
93
|
def hash_with_metadata(hash, meta)
|
73
94
|
base = @_meta ? instance_eval(&@_meta) : {}
|
74
95
|
metadata = base.merge(meta)
|
@@ -120,23 +141,38 @@ module Alba
|
|
120
141
|
if attribute.is_a?(Array) # Conditional
|
121
142
|
conditional_attribute(object, key, attribute)
|
122
143
|
else
|
123
|
-
|
144
|
+
fetched_attribute = fetch_attribute(object, key, attribute)
|
145
|
+
[key, fetched_attribute]
|
124
146
|
end
|
125
147
|
end
|
126
148
|
|
127
149
|
def conditional_attribute(object, key, attribute)
|
128
150
|
condition = attribute.last
|
151
|
+
if condition.is_a?(Proc)
|
152
|
+
conditional_attribute_with_proc(object, key, attribute.first, condition)
|
153
|
+
else
|
154
|
+
conditional_attribute_with_symbol(object, key, attribute.first, condition)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def conditional_attribute_with_proc(object, key, attribute, condition)
|
129
159
|
arity = condition.arity
|
130
160
|
# We can return early to skip fetch_attribute
|
131
161
|
return [] if arity <= 1 && !instance_exec(object, &condition)
|
132
162
|
|
133
|
-
fetched_attribute = fetch_attribute(object, attribute
|
134
|
-
attr = attribute.
|
163
|
+
fetched_attribute = fetch_attribute(object, key, attribute)
|
164
|
+
attr = attribute.is_a?(Alba::Association) ? attribute.object : fetched_attribute
|
135
165
|
return [] if arity >= 2 && !instance_exec(object, attr, &condition)
|
136
166
|
|
137
167
|
[key, fetched_attribute]
|
138
168
|
end
|
139
169
|
|
170
|
+
def conditional_attribute_with_symbol(object, key, attribute, condition)
|
171
|
+
return [] unless __send__(condition)
|
172
|
+
|
173
|
+
[key, fetch_attribute(object, key, attribute)]
|
174
|
+
end
|
175
|
+
|
140
176
|
def handle_error(error, object, key, attribute)
|
141
177
|
on_error = @_on_error || Alba._on_error
|
142
178
|
case on_error
|
@@ -156,15 +192,20 @@ module Alba
|
|
156
192
|
@_transform_key_function.call(key.to_s)
|
157
193
|
end
|
158
194
|
|
159
|
-
def fetch_attribute(object, attribute)
|
160
|
-
case attribute
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
195
|
+
def fetch_attribute(object, key, attribute)
|
196
|
+
value = case attribute
|
197
|
+
when Symbol then object.public_send attribute
|
198
|
+
when Proc then instance_exec(object, &attribute)
|
199
|
+
when Alba::One, Alba::Many then yield_if_within(attribute.name.to_sym) { |within| attribute.to_hash(object, params: params, within: within) }
|
200
|
+
when TypedAttribute then attribute.value(object)
|
201
|
+
else
|
202
|
+
raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
|
203
|
+
end
|
204
|
+
value.nil? && nil_handler ? instance_exec(object, key, attribute, &nil_handler) : value
|
205
|
+
end
|
206
|
+
|
207
|
+
def nil_handler
|
208
|
+
@nil_handler ||= (@_on_nil || Alba._on_nil)
|
168
209
|
end
|
169
210
|
|
170
211
|
def yield_if_within(association_name)
|
@@ -289,7 +330,7 @@ module Alba
|
|
289
330
|
# @param key [String, Symbol]
|
290
331
|
# @deprecated Use {#root_key} instead
|
291
332
|
def key(key)
|
292
|
-
warn '[DEPRECATION] `key` is deprecated, use `root_key` instead.'
|
333
|
+
Alba::Deprecation.warn '[DEPRECATION] `key` is deprecated, use `root_key` instead.'
|
293
334
|
@_key = key.respond_to?(:to_sym) ? key.to_sym : key
|
294
335
|
end
|
295
336
|
|
@@ -307,7 +348,7 @@ module Alba
|
|
307
348
|
#
|
308
349
|
# @deprecated Use {#root_key!} instead
|
309
350
|
def key!
|
310
|
-
warn '[DEPRECATION] `key!` is deprecated, use `root_key!` instead.'
|
351
|
+
Alba::Deprecation.warn '[DEPRECATION] `key!` is deprecated, use `root_key!` instead.'
|
311
352
|
@_key = true
|
312
353
|
@_key_for_collection = true
|
313
354
|
end
|
@@ -323,6 +364,14 @@ module Alba
|
|
323
364
|
@_meta = block
|
324
365
|
end
|
325
366
|
|
367
|
+
# Set layout
|
368
|
+
#
|
369
|
+
# @params file [String] name of the layout file
|
370
|
+
# @params inline [Proc] a proc returning JSON string or a Hash representing JSON
|
371
|
+
def layout(file: nil, inline: nil)
|
372
|
+
@_layout = file || inline
|
373
|
+
end
|
374
|
+
|
326
375
|
# Delete attributes
|
327
376
|
# Use this DSL in child class to ignore certain attributes
|
328
377
|
#
|
@@ -354,6 +403,13 @@ module Alba
|
|
354
403
|
@_on_error = handler || block
|
355
404
|
end
|
356
405
|
|
406
|
+
# Set nil handler
|
407
|
+
#
|
408
|
+
# @param block [Block]
|
409
|
+
def on_nil(&block)
|
410
|
+
@_on_nil = block
|
411
|
+
end
|
412
|
+
|
357
413
|
# rubocop:enable Metrics/ParameterLists
|
358
414
|
end
|
359
415
|
end
|
data/lib/alba/version.rb
CHANGED
data/lib/alba.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'json'
|
2
2
|
require_relative 'alba/version'
|
3
3
|
require_relative 'alba/resource'
|
4
|
+
require_relative 'alba/deprecation'
|
4
5
|
|
5
6
|
# Core module
|
6
7
|
module Alba
|
@@ -14,7 +15,7 @@ module Alba
|
|
14
15
|
class UnsupportedType < Error; end
|
15
16
|
|
16
17
|
class << self
|
17
|
-
attr_reader :backend, :encoder, :inferring, :_on_error, :transforming_root_key
|
18
|
+
attr_reader :backend, :encoder, :inferring, :_on_error, :_on_nil, :transforming_root_key
|
18
19
|
|
19
20
|
# Accessor for inflector, a module responsible for incflecting strings
|
20
21
|
attr_accessor :inflector
|
@@ -51,7 +52,7 @@ module Alba
|
|
51
52
|
# @return [String] serialized JSON string
|
52
53
|
# @raise [ArgumentError] if block is absent or `with` argument's type is wrong
|
53
54
|
def serialize(object, key: nil, root_key: nil, &block)
|
54
|
-
warn '`key` option to `serialize` method is deprecated, use `root_key` instead.' if key
|
55
|
+
Alba::Deprecation.warn '`key` option to `serialize` method is deprecated, use `root_key` instead.' if key
|
55
56
|
klass = block ? resource_class(&block) : infer_resource_class(object.class.name)
|
56
57
|
|
57
58
|
resource = klass.new(object)
|
@@ -87,6 +88,14 @@ module Alba
|
|
87
88
|
@_on_error = handler || block
|
88
89
|
end
|
89
90
|
|
91
|
+
# Set nil handler
|
92
|
+
#
|
93
|
+
# @param block [Block]
|
94
|
+
# @return [void]
|
95
|
+
def on_nil(&block)
|
96
|
+
@_on_nil = block
|
97
|
+
end
|
98
|
+
|
90
99
|
# Enable root key transformation
|
91
100
|
def enable_root_key_transformation!
|
92
101
|
@transforming_root_key = true
|
@@ -115,6 +124,15 @@ module Alba
|
|
115
124
|
const_parent.const_get("#{ActiveSupport::Inflector.classify(name)}Resource")
|
116
125
|
end
|
117
126
|
|
127
|
+
# Reset config variables
|
128
|
+
# Useful for test cleanup
|
129
|
+
def reset!
|
130
|
+
@encoder = default_encoder
|
131
|
+
@_on_error = :raise
|
132
|
+
@_on_nil = nil
|
133
|
+
@transforming_root_key = false # TODO: This will be true since 2.0
|
134
|
+
end
|
135
|
+
|
118
136
|
private
|
119
137
|
|
120
138
|
def set_encoder_from_backend
|
@@ -151,7 +169,5 @@ module Alba
|
|
151
169
|
end
|
152
170
|
end
|
153
171
|
|
154
|
-
|
155
|
-
@_on_error = :raise
|
156
|
-
@transforming_root_key = false # TODO: This will be true since 2.0
|
172
|
+
reset!
|
157
173
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: alba
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- OKURA Masafumi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-11-28 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Alba is the fastest JSON serializer for Ruby. It focuses on performance,
|
14
14
|
flexibility and usability.
|
@@ -39,12 +39,15 @@ files:
|
|
39
39
|
- bin/console
|
40
40
|
- bin/setup
|
41
41
|
- codecov.yml
|
42
|
+
- docs/migrate_from_active_model_serializers.md
|
43
|
+
- docs/migrate_from_jbuilder.md
|
42
44
|
- gemfiles/all.gemfile
|
43
45
|
- gemfiles/without_active_support.gemfile
|
44
46
|
- gemfiles/without_oj.gemfile
|
45
47
|
- lib/alba.rb
|
46
48
|
- lib/alba/association.rb
|
47
49
|
- lib/alba/default_inflector.rb
|
50
|
+
- lib/alba/deprecation.rb
|
48
51
|
- lib/alba/key_transform_factory.rb
|
49
52
|
- lib/alba/many.rb
|
50
53
|
- lib/alba/one.rb
|
@@ -75,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
78
|
- !ruby/object:Gem::Version
|
76
79
|
version: '0'
|
77
80
|
requirements: []
|
78
|
-
rubygems_version: 3.2.
|
81
|
+
rubygems_version: 3.2.22
|
79
82
|
signing_key:
|
80
83
|
specification_version: 4
|
81
84
|
summary: Alba is the fastest JSON serializer for Ruby.
|