alba 1.6.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +11 -0
- data/.github/dependabot.yml +4 -18
- data/.github/workflows/codeql-analysis.yml +4 -4
- data/.github/workflows/main.yml +4 -6
- data/.github/workflows/perf.yml +2 -2
- data/.rubocop.yml +3 -1
- data/CHANGELOG.md +24 -0
- data/CONTRIBUTING.md +30 -0
- data/Gemfile +6 -2
- data/HACKING.md +41 -0
- data/README.md +381 -58
- data/Rakefile +2 -2
- data/alba.gemspec +1 -1
- data/benchmark/README.md +81 -0
- data/benchmark/collection.rb +0 -70
- data/docs/migrate_from_jbuilder.md +18 -4
- data/docs/rails.md +44 -0
- data/lib/alba/association.rb +25 -5
- data/lib/alba/conditional_attribute.rb +54 -0
- data/lib/alba/default_inflector.rb +10 -39
- data/lib/alba/layout.rb +67 -0
- data/lib/alba/nested_attribute.rb +18 -0
- data/lib/alba/resource.rb +201 -173
- data/lib/alba/typed_attribute.rb +1 -1
- data/lib/alba/version.rb +1 -1
- data/lib/alba.rb +13 -56
- data/logo/alba-card.png +0 -0
- data/logo/alba-sign.png +0 -0
- data/logo/alba-typography.png +0 -0
- metadata +15 -6
- data/gemfiles/all.gemfile +0 -20
- data/sider.yml +0 -60
data/README.md
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
+
![alba card](https://raw.githubusercontent.com/okuramasafumi/alba/main/logo/alba-card.png)
|
2
|
+
----------
|
1
3
|
[![Gem Version](https://badge.fury.io/rb/alba.svg)](https://badge.fury.io/rb/alba)
|
2
4
|
[![CI](https://github.com/okuramasafumi/alba/actions/workflows/main.yml/badge.svg)](https://github.com/okuramasafumi/alba/actions/workflows/main.yml)
|
3
|
-
[![codecov](https://codecov.io/gh/okuramasafumi/alba/branch/
|
5
|
+
[![codecov](https://codecov.io/gh/okuramasafumi/alba/branch/main/graph/badge.svg?token=3D3HEZ5OXT)](https://codecov.io/gh/okuramasafumi/alba)
|
4
6
|
[![Maintainability](https://api.codeclimate.com/v1/badges/fdab4cc0de0b9addcfe8/maintainability)](https://codeclimate.com/github/okuramasafumi/alba/maintainability)
|
5
7
|
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/okuramasafumi/alba)
|
6
8
|
![GitHub](https://img.shields.io/github/license/okuramasafumi/alba)
|
7
9
|
|
8
10
|
# Alba
|
9
11
|
|
10
|
-
Alba is
|
12
|
+
Alba is a JSON serializer for Ruby, JRuby, and TruffleRuby.
|
11
13
|
|
12
14
|
## Discussions
|
13
15
|
|
@@ -17,21 +19,25 @@ If you've already used Alba, please consider posting your thoughts and feelings
|
|
17
19
|
|
18
20
|
If you have feature requests or interesting ideas, join us with [Ideas](https://github.com/okuramasafumi/alba/discussions/categories/ideas). Let's make Alba even better, together!
|
19
21
|
|
22
|
+
## Resources
|
23
|
+
|
24
|
+
If you want to know more about Alba, there's a [screencast](https://hanamimastery.com/episodes/21-serialization-with-alba) created by Sebastian from [Hanami Mastery](https://hanamimastery.com/). It covers basic features of Alba and how to use it in Hanami.
|
25
|
+
|
20
26
|
## Why Alba?
|
21
27
|
|
22
|
-
Because it's fast, easy
|
28
|
+
Because it's fast, easy and feature rich!
|
23
29
|
|
24
30
|
### Fast
|
25
31
|
|
26
|
-
Alba is faster than most of the alternatives. We have a [benchmark](https://github.com/okuramasafumi/alba/tree/
|
32
|
+
Alba is faster than most of the alternatives. We have a [benchmark](https://github.com/okuramasafumi/alba/tree/main/benchmark).
|
27
33
|
|
28
|
-
### Easy
|
34
|
+
### Easy
|
29
35
|
|
30
|
-
Alba
|
36
|
+
Alba is easy to use because there are only a few methods to remember. It's also easy to understand due to clean and short codebase. Finally it's easy to extend since it provides some methods for override to change default behavior of Alba.
|
31
37
|
|
32
|
-
###
|
38
|
+
### Feature rich
|
33
39
|
|
34
|
-
Alba
|
40
|
+
While Alba's core is simple, it provides additional features when you need them, For example, Alba provides [a way to control circular associations](#circular-associations-control), [inferring resource classes, root key and associations](#inference) and [supports layouts](#layout).
|
35
41
|
|
36
42
|
## Installation
|
37
43
|
|
@@ -81,9 +87,13 @@ Alba's configuration is fairly simple.
|
|
81
87
|
|
82
88
|
Backend is the actual part serializing an object into JSON. Alba supports these backends.
|
83
89
|
|
84
|
-
|
85
|
-
|
86
|
-
|
90
|
+
|name|description|requires_external_gem|
|
91
|
+
|--|--|--|
|
92
|
+
|`oj`, `oj_strict`|Using Oj in `strict` mode|Yes(C extension)|
|
93
|
+
|`oj_rails`|It's `oj` but in `rails` mode|Yes(C extension)|
|
94
|
+
|`oj_default`|It's `oj` but respects mode set by users|Yes(C extension)|
|
95
|
+
|`active_support`|For Rails compatibility|Yes|
|
96
|
+
|`default`, `json`|Using `json` gem|No|
|
87
97
|
|
88
98
|
You can set a backend like this:
|
89
99
|
|
@@ -177,8 +187,37 @@ UserResource.new([user1, user2]).serialize
|
|
177
187
|
# => "{\"users\":[{\"id\":1,\"name\":\"Masafumi OKURA\",\"name_with_email\":\"Masafumi OKURA: masafumi@example.com\"},{\"id\":2,\"name\":\"Test User\",\"name_with_email\":\"Test User: test@example.com\"}]}"
|
178
188
|
```
|
179
189
|
|
190
|
+
If you have a simple case where you want to change only the name, you can use the Symbol to Proc shortcut:
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
class UserResource
|
194
|
+
include Alba::Resource
|
195
|
+
|
196
|
+
attribute :some_other_name, &:name
|
197
|
+
end
|
198
|
+
```
|
199
|
+
|
200
|
+
#### Params
|
201
|
+
|
202
|
+
You can pass a Hash to the resource for internal use. It can be used as "flags" to control attribute content.
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
class UserResource
|
206
|
+
include Alba::Resource
|
207
|
+
attribute :name do |user|
|
208
|
+
params[:upcase] ? user.name.upcase : user.name
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
user = User.new(1, 'Masa', 'test@example.com')
|
213
|
+
UserResource.new(user).serialize # => "{\"name\":\"foo\"}"
|
214
|
+
UserResource.new(user, params: {upcase: true}).serialize # => "{\"name\":\"FOO\"}"
|
215
|
+
```
|
216
|
+
|
180
217
|
### Serialization with associations
|
181
218
|
|
219
|
+
Associations can be defined using the `association` macro, which is also aliased as `one`, `many`, `has_one`, and `has_many` for convenience.
|
220
|
+
|
182
221
|
```ruby
|
183
222
|
class User
|
184
223
|
attr_reader :id, :created_at, :updated_at
|
@@ -255,17 +294,18 @@ class AnotherUserResource
|
|
255
294
|
end
|
256
295
|
```
|
257
296
|
|
258
|
-
You can "filter" association using second proc argument. This proc takes association object and
|
297
|
+
You can "filter" association using second proc argument. This proc takes association object, `params` and initial object.
|
259
298
|
|
260
299
|
This feature is useful when you want to modify association, such as adding `includes` or `order` to ActiveRecord relations.
|
261
300
|
|
262
301
|
```ruby
|
263
302
|
class User
|
264
|
-
attr_reader :id
|
303
|
+
attr_reader :id, :banned
|
265
304
|
attr_accessor :articles
|
266
305
|
|
267
|
-
def initialize(id)
|
306
|
+
def initialize(id, banned = false)
|
268
307
|
@id = id
|
308
|
+
@banned = banned
|
269
309
|
@articles = []
|
270
310
|
end
|
271
311
|
end
|
@@ -293,9 +333,9 @@ class UserResource
|
|
293
333
|
|
294
334
|
# Second proc works as a filter
|
295
335
|
many :articles,
|
296
|
-
proc { |articles, params|
|
336
|
+
proc { |articles, params, user|
|
297
337
|
filter = params[:filter] || :odd?
|
298
|
-
articles.select {|a| a.id.send(filter) }
|
338
|
+
articles.select {|a| a.id.send(filter) && !user.banned }
|
299
339
|
},
|
300
340
|
resource: ArticleResource
|
301
341
|
end
|
@@ -312,6 +352,150 @@ UserResource.new(user, params: {filter: :even?}).serialize
|
|
312
352
|
# => '{"id":1,"articles":[{"title":"Super nice"}]}'
|
313
353
|
```
|
314
354
|
|
355
|
+
You can change a key for association with `key` option.
|
356
|
+
|
357
|
+
```ruby
|
358
|
+
class UserResource
|
359
|
+
include Alba::Resource
|
360
|
+
|
361
|
+
attributes :id
|
362
|
+
|
363
|
+
many :articles,
|
364
|
+
key: 'my_articles', # Set key here
|
365
|
+
resource: ArticleResource
|
366
|
+
end
|
367
|
+
UserResource.new(user).serialize
|
368
|
+
# => '{"id":1,"my_articles":[{"title":"Hello World!"}]}'
|
369
|
+
```
|
370
|
+
|
371
|
+
You can omit resource option if you enable Alba's inference feature.
|
372
|
+
|
373
|
+
```ruby
|
374
|
+
class UserResource
|
375
|
+
include Alba::Resource
|
376
|
+
|
377
|
+
attributes :id
|
378
|
+
|
379
|
+
many :articles # Using `ArticleResource`
|
380
|
+
end
|
381
|
+
UserResource.new(user).serialize
|
382
|
+
# => '{"id":1,"my_articles":[{"title":"Hello World!"}]}'
|
383
|
+
```
|
384
|
+
|
385
|
+
If you need complex logic to determine what resource to use for association, you can use a Proc for resource option.
|
386
|
+
|
387
|
+
```ruby
|
388
|
+
class UserResource
|
389
|
+
include Alba::Resource
|
390
|
+
|
391
|
+
attributes :id
|
392
|
+
|
393
|
+
many :articles, ->(article) { article.with_comment? ? ArticleWithCommentResource : ArticleResource }
|
394
|
+
end
|
395
|
+
```
|
396
|
+
|
397
|
+
Note that using a Proc slows down serialization if there are too `many` associated objects.
|
398
|
+
|
399
|
+
#### Params override
|
400
|
+
|
401
|
+
Associations can override params. This is useful when associations are deeply nested.
|
402
|
+
|
403
|
+
```ruby
|
404
|
+
class BazResource
|
405
|
+
include Alba::Resource
|
406
|
+
|
407
|
+
attributes :data
|
408
|
+
attributes :secret, if: proc { params[:expose_secret] }
|
409
|
+
end
|
410
|
+
|
411
|
+
class BarResource
|
412
|
+
include Alba::Resource
|
413
|
+
|
414
|
+
one :baz, resource: BazResource
|
415
|
+
end
|
416
|
+
|
417
|
+
class FooResource
|
418
|
+
include Alba::Resource
|
419
|
+
|
420
|
+
root_key :foo
|
421
|
+
|
422
|
+
one :bar, resource: BarResource
|
423
|
+
end
|
424
|
+
|
425
|
+
class FooResourceWithParamsOverride
|
426
|
+
include Alba::Resource
|
427
|
+
|
428
|
+
root_key :foo
|
429
|
+
|
430
|
+
one :bar, resource: BarResource, params: { expose_secret: false }
|
431
|
+
end
|
432
|
+
|
433
|
+
Baz = Struct.new(:data, :secret)
|
434
|
+
Bar = Struct.new(:baz)
|
435
|
+
Foo = Struct.new(:bar)
|
436
|
+
|
437
|
+
foo = Foo.new(Bar.new(Baz.new(1, 'secret')))
|
438
|
+
FooResource.new(foo, params: {expose_secret: true}).serialize # => '{"foo":{"bar":{"baz":{"data":1,"secret":"secret"}}}}'
|
439
|
+
FooResourceWithParamsOverride.new(foo, params: {expose_secret: true}).serialize # => '{"foo":{"bar":{"baz":{"data":1}}}}'
|
440
|
+
```
|
441
|
+
|
442
|
+
### Nested Attribute
|
443
|
+
|
444
|
+
Alba supports nested attributes that makes it easy to build complex data structure from single object.
|
445
|
+
|
446
|
+
In order to define nested attributes, you can use `nested` or `nested_attribute` (alias of `nested`).
|
447
|
+
|
448
|
+
```ruby
|
449
|
+
class User
|
450
|
+
attr_accessor :id, :name, :email, :city, :zipcode
|
451
|
+
|
452
|
+
def initialize(id, name, email, city, zipcode)
|
453
|
+
@id = id
|
454
|
+
@name = name
|
455
|
+
@email = email
|
456
|
+
@city = city
|
457
|
+
@zipcode = zipcode
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
class UserResource
|
462
|
+
include Alba::Resource
|
463
|
+
|
464
|
+
root_key :user
|
465
|
+
|
466
|
+
attributes :id
|
467
|
+
|
468
|
+
nested_attribute :address do
|
469
|
+
attributes :city, :zipcode
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
user = User.new(1, 'Masafumi OKURA', 'masafumi@example.com', 'Tokyo', '0000000')
|
474
|
+
UserResource.new(user).serialize
|
475
|
+
# => '{"user":{"id":1,"address":{"city":"Tokyo","zipcode":"0000000"}}}'
|
476
|
+
```
|
477
|
+
|
478
|
+
Nested attributes can be nested deeply.
|
479
|
+
|
480
|
+
```ruby
|
481
|
+
class FooResource
|
482
|
+
include Alba::Resource
|
483
|
+
|
484
|
+
root_key :foo
|
485
|
+
|
486
|
+
nested :bar do
|
487
|
+
nested :baz do
|
488
|
+
attribute :deep do
|
489
|
+
42
|
490
|
+
end
|
491
|
+
end
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
FooResource.new(nil).serialize
|
496
|
+
# => '{"foo":{"bar":{"baz":{"deep":42}}}}'
|
497
|
+
```
|
498
|
+
|
315
499
|
### Inline definition with `Alba.serialize`
|
316
500
|
|
317
501
|
`Alba.serialize` method is a shortcut to define everything inline.
|
@@ -335,7 +519,57 @@ Alba.serialize(something)
|
|
335
519
|
|
336
520
|
Although this might be useful sometimes, it's generally recommended to define a class for Resource.
|
337
521
|
|
338
|
-
###
|
522
|
+
### Serializable Hash
|
523
|
+
|
524
|
+
Instead of serializing to JSON, you can also output a Hash by calling `serializable_hash` or the `to_h` alias. Note also that the `serialize` method is aliased as `to_json`.
|
525
|
+
|
526
|
+
```ruby
|
527
|
+
# These are equivalent and will return serialized JSON
|
528
|
+
UserResource.new(user).serialize
|
529
|
+
UserResource.new(user).to_json
|
530
|
+
|
531
|
+
# These are equivalent and will return a Hash
|
532
|
+
UserResource.new(user).serializable_hash
|
533
|
+
UserResource.new(user).to_h
|
534
|
+
```
|
535
|
+
|
536
|
+
If you want a Hash that corresponds to a JSON String returned by `serialize` method, you can use `as_json`.
|
537
|
+
|
538
|
+
```ruby
|
539
|
+
# These are equivalent and will return the same result
|
540
|
+
UserResource.new(user).serialize
|
541
|
+
UserResource.new(user).to_json
|
542
|
+
JSON.generate(UserResource.new(user).as_json)
|
543
|
+
```
|
544
|
+
|
545
|
+
### Inheritance
|
546
|
+
|
547
|
+
When you include `Alba::Resource` in your class, it's just a class so you can define any class that inherits from it. You can add new attributes to inherited class like below:
|
548
|
+
|
549
|
+
```ruby
|
550
|
+
class FooResource
|
551
|
+
include Alba::Resource
|
552
|
+
|
553
|
+
root_key :foo
|
554
|
+
|
555
|
+
attributes :bar
|
556
|
+
end
|
557
|
+
|
558
|
+
class ExtendedFooResource < FooResource
|
559
|
+
root_key :foofoo
|
560
|
+
|
561
|
+
attributes :baz
|
562
|
+
end
|
563
|
+
|
564
|
+
Foo = Struct.new(:bar, :baz)
|
565
|
+
foo = Foo.new(1, 2)
|
566
|
+
FooResource.new(foo).serialize # => '{"foo":{"bar":1}}'
|
567
|
+
ExtendedFooResource.new(foo).serialize # => '{"foo":{"bar":1,"baz":2}}'
|
568
|
+
```
|
569
|
+
|
570
|
+
In this example we add `baz` attribute and change `root_key`. This way, you can extend existing resource classes just like normal OOP. Don't forget that when your inheritance structure is too deep it'll become difficult to modify existing classes.
|
571
|
+
|
572
|
+
### Filtering attributes
|
339
573
|
|
340
574
|
You can filter out certain attributes by overriding `attributes` instance method. This is useful when you want to customize existing resource with inheritance.
|
341
575
|
|
@@ -408,6 +642,8 @@ Possible values for `transform_keys` argument are:
|
|
408
642
|
* `:snake` for snake_case
|
409
643
|
* `:none` for not transforming keys
|
410
644
|
|
645
|
+
#### Root key transformation
|
646
|
+
|
411
647
|
You can also transform root key when:
|
412
648
|
|
413
649
|
* `Alba.enable_inference!` is called
|
@@ -441,74 +677,81 @@ BankAccountResource.new(bank_account).serialize
|
|
441
677
|
|
442
678
|
This behavior to transform root key will become default at version 2.
|
443
679
|
|
444
|
-
|
680
|
+
#### Key transformation cascading
|
445
681
|
|
446
|
-
|
682
|
+
When you use `transform_keys` with inline association, it automatically applies the same transformation type to those inline association.
|
447
683
|
|
448
|
-
|
684
|
+
This is the default behavior from version 2, but you can do the same thing with adding `transform_keys` to each association.
|
685
|
+
|
686
|
+
You can also turn it off by setting `cascade: false` option to `transform_keys`.
|
449
687
|
|
450
688
|
```ruby
|
451
|
-
|
452
|
-
|
689
|
+
class User
|
690
|
+
attr_reader :id, :first_name, :last_name
|
453
691
|
|
454
|
-
def
|
692
|
+
def initialize(id, first_name, last_name)
|
693
|
+
@id = id
|
694
|
+
@first_name = first_name
|
695
|
+
@last_name = last_name
|
696
|
+
@bank_account = BankAccount.new(1234)
|
455
697
|
end
|
698
|
+
end
|
456
699
|
|
457
|
-
|
458
|
-
|
700
|
+
class BankAccount
|
701
|
+
attr_reader :account_number
|
459
702
|
|
460
|
-
def
|
703
|
+
def initialize(account_number)
|
704
|
+
@account_number = account_number
|
461
705
|
end
|
706
|
+
end
|
462
707
|
|
463
|
-
|
464
|
-
|
708
|
+
class UserResource
|
709
|
+
include Alba::Resource
|
465
710
|
|
466
|
-
|
711
|
+
attributes :id, :first_name, :last_name
|
712
|
+
|
713
|
+
transform_keys :lower_camel # Default is cascade: true
|
714
|
+
|
715
|
+
one :bank_account do
|
716
|
+
attributes :account_number
|
467
717
|
end
|
468
718
|
end
|
469
719
|
|
470
|
-
|
720
|
+
user = User.new(1, 'Masafumi', 'Okura')
|
721
|
+
UserResource.new(user).serialize
|
722
|
+
# => '{"id":1,"firstName":"Masafumi","lastName":"Okura","bankAccount":{"accountNumber":1234}}'
|
471
723
|
```
|
472
724
|
|
473
|
-
|
725
|
+
#### Custom inflector
|
474
726
|
|
475
|
-
|
727
|
+
A custom inflector can be plugged in as follows.
|
476
728
|
|
477
729
|
```ruby
|
478
|
-
|
479
|
-
|
730
|
+
module CustomInflector
|
731
|
+
module_function
|
480
732
|
|
481
|
-
def
|
482
|
-
@id = id
|
483
|
-
@name = name
|
484
|
-
@email = email
|
733
|
+
def camelize(string)
|
485
734
|
end
|
486
|
-
end
|
487
735
|
|
488
|
-
|
489
|
-
|
736
|
+
def camelize_lower(string)
|
737
|
+
end
|
490
738
|
|
491
|
-
|
739
|
+
def dasherize(string)
|
740
|
+
end
|
492
741
|
|
493
|
-
|
742
|
+
def underscore(string)
|
743
|
+
end
|
494
744
|
|
495
|
-
|
496
|
-
def converter
|
497
|
-
super >> proc { |hash| hash.compact }
|
745
|
+
def classify(string)
|
498
746
|
end
|
499
747
|
end
|
500
748
|
|
501
|
-
|
502
|
-
UserResource.new(user).serialize # => '{"id":1}'
|
749
|
+
Alba.enable_inference!(with: CustomInflector)
|
503
750
|
```
|
504
751
|
|
505
|
-
The key part is the use of `Proc#>>` since `Alba::Resource#converter` returns a `Proc` which contains the basic logic and it's impossible to change its behavior by just overriding the method.
|
506
|
-
|
507
|
-
It's not recommended to swap the whole conversion logic. It's recommended to always call `super` when you override `converter`.
|
508
|
-
|
509
752
|
### Conditional attributes
|
510
753
|
|
511
|
-
Filtering attributes with overriding `
|
754
|
+
Filtering attributes with overriding `attributes` works well for simple cases. However, It's cumbersome when we want to filter various attributes based on different conditions for keys.
|
512
755
|
|
513
756
|
In these cases, conditional attributes works well. We can pass `if` option to `attributes`, `attribute`, `one` and `many`. Below is an example for the same effect as [filtering attributes section](#filtering-attributes).
|
514
757
|
|
@@ -761,7 +1004,7 @@ Note that setting root key is required when setting a metadata.
|
|
761
1004
|
|
762
1005
|
You can control circular associations with `within` option. `within` option is a nested Hash such as `{book: {authors: books}}`. In this example, Alba serializes a book's authors' books. This means you can reference `BookResource` from `AuthorResource` and vice versa. This is really powerful when you have a complex data structure and serialize certain parts of it.
|
763
1006
|
|
764
|
-
For more details, please refer to [test code](https://github.com/okuramasafumi/alba/blob/
|
1007
|
+
For more details, please refer to [test code](https://github.com/okuramasafumi/alba/blob/main/test/usecases/circular_association_test.rb)
|
765
1008
|
|
766
1009
|
### Experimental support of types
|
767
1010
|
|
@@ -804,6 +1047,37 @@ UserResource.new(user).serialize
|
|
804
1047
|
|
805
1048
|
Note that this feature is experimental and interfaces are subject to change.
|
806
1049
|
|
1050
|
+
### Collection serialization into Hash
|
1051
|
+
|
1052
|
+
Sometimes we want to serialize a collection into a Hash, not an Array. It's possible with Alba.
|
1053
|
+
|
1054
|
+
```ruby
|
1055
|
+
class User
|
1056
|
+
attr_reader :id, :name
|
1057
|
+
def initialize(id, name)
|
1058
|
+
@id, @name = id, name
|
1059
|
+
end
|
1060
|
+
end
|
1061
|
+
|
1062
|
+
class UserResource
|
1063
|
+
include Alba::Resource
|
1064
|
+
|
1065
|
+
collection_key :id # This line is important
|
1066
|
+
|
1067
|
+
attributes :id, :name
|
1068
|
+
end
|
1069
|
+
|
1070
|
+
user1 = User.new(1, 'John')
|
1071
|
+
user2 = User.new(2, 'Masafumi')
|
1072
|
+
|
1073
|
+
UserResource.new([user1, user2]).serialize
|
1074
|
+
# => '{"1":{"id":1,"name":"John"},"2":{"id":2,"name":"Masafumi"}}'
|
1075
|
+
```
|
1076
|
+
|
1077
|
+
In the snippet above, `collection_key :id` specifies the key used for the key of the collection hash. In this example it's `:id`.
|
1078
|
+
|
1079
|
+
Make sure that collection key is unique for the collection.
|
1080
|
+
|
807
1081
|
### Layout
|
808
1082
|
|
809
1083
|
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".
|
@@ -972,6 +1246,54 @@ end
|
|
972
1246
|
|
973
1247
|
In this way we have shorter and cleaner code. Note that we need to use `send` or `public_send` in `attribute` block to get attribute data.
|
974
1248
|
|
1249
|
+
### Debugging
|
1250
|
+
|
1251
|
+
Debugging is not easy. If you find Alba not working as you expect, there are a few things to do:
|
1252
|
+
|
1253
|
+
1. Inspect
|
1254
|
+
|
1255
|
+
The typical code looks like this:
|
1256
|
+
|
1257
|
+
```ruby
|
1258
|
+
class FooResource
|
1259
|
+
include Alba::Resource
|
1260
|
+
attributes :id
|
1261
|
+
end
|
1262
|
+
FooResource.new(foo).serialize
|
1263
|
+
```
|
1264
|
+
|
1265
|
+
Notice that we instantiate `FooResource` and then call `serialize` method. We can get various information by calling `inspect` method on it.
|
1266
|
+
|
1267
|
+
```ruby
|
1268
|
+
puts FooResource.new(foo).inspect # or: p class FooResource.new(foo)
|
1269
|
+
# => "#<FooResource:0x000000010e21f408 @object=[#<Foo:0x000000010e3470d8 @id=1>], @params={}, @within=#<Object:0x000000010df2eac8>, @method_existence={}, @_attributes={:id=>:id}, @_key=nil, @_key_for_collection=nil, @_meta=nil, @_transform_type=:none, @_transforming_root_key=false, @_on_error=nil, @_on_nil=nil, @_layout=nil, @_collection_key=nil>"
|
1270
|
+
```
|
1271
|
+
|
1272
|
+
The output might be different depending on the version of Alba or the object you give, but the concepts are the same. `@object` represents the object you gave as an argument to `new` method, `@_attributes` represents the attributes you defined in `FooResource` class using `attributes` DSL.
|
1273
|
+
|
1274
|
+
Other things are not so important, but you need to take care of corresponding part when you use additional features such as `root_key`, `transform_keys` and adding params.
|
1275
|
+
|
1276
|
+
2. Logging
|
1277
|
+
|
1278
|
+
Alba currently doesn't support logging directly, but you can add your own logging module to Alba easily.
|
1279
|
+
|
1280
|
+
```ruby
|
1281
|
+
module Logging
|
1282
|
+
def serialize(...) # `...` was added in Ruby 2.7
|
1283
|
+
puts serializable_hash
|
1284
|
+
super(...)
|
1285
|
+
end
|
1286
|
+
end
|
1287
|
+
|
1288
|
+
FooResource.prepend Logging
|
1289
|
+
FooResource.new(foo).serialize
|
1290
|
+
# => "{:id=>1}" is printed
|
1291
|
+
```
|
1292
|
+
|
1293
|
+
Here, we override `serialize` method with `prepend`. In overridden method we print the result of `serializable_hash` that gives the basic hash for serialization to `serialize` method. Using `...` allows us to override without knowing method signiture of `serialize`.
|
1294
|
+
|
1295
|
+
Don't forget calling `super` in this way.
|
1296
|
+
|
975
1297
|
## Rails
|
976
1298
|
|
977
1299
|
When you use Alba in Rails, you can create an initializer file with the line below for compatibility with Rails JSON encoder.
|
@@ -982,6 +1304,8 @@ Alba.backend = :active_support
|
|
982
1304
|
Alba.backend = :oj_rails
|
983
1305
|
```
|
984
1306
|
|
1307
|
+
To find out more details, please see https://github.com/okuramasafumi/alba/blob/main/docs/rails.md
|
1308
|
+
|
985
1309
|
## Why named "Alba"?
|
986
1310
|
|
987
1311
|
The name "Alba" comes from "albatross", a kind of birds. In Japanese, this bird is called "Aho-dori", which means "stupid bird". I find it funny because in fact albatrosses fly really fast. I hope Alba looks stupid but in fact it does its job quick.
|
@@ -995,14 +1319,13 @@ There are great pioneers in Ruby's ecosystem which does basically the same thing
|
|
995
1319
|
|
996
1320
|
## Development
|
997
1321
|
|
998
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
1322
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
999
1323
|
|
1000
1324
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
1001
1325
|
|
1002
1326
|
## Contributing
|
1003
1327
|
|
1004
|
-
|
1005
|
-
|
1328
|
+
Thank you for begin interested in contributing to Alba! Please see [contributors guide](https://github.com/okuramasafumi/alba/blob/main/CONTRIBUTING.md) before start contributing. If you have any questions, please feel free to ask in [Discussions](https://github.com/okuramasafumi/alba/discussions).
|
1006
1329
|
|
1007
1330
|
## License
|
1008
1331
|
|
@@ -1010,4 +1333,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
1010
1333
|
|
1011
1334
|
## Code of Conduct
|
1012
1335
|
|
1013
|
-
Everyone interacting in the Alba project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/okuramasafumi/alba/blob/
|
1336
|
+
Everyone interacting in the Alba project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/okuramasafumi/alba/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rake/testtask"
|
3
3
|
|
4
|
-
ENV["BUNDLE_GEMFILE"] = File.expand_path("
|
4
|
+
ENV["BUNDLE_GEMFILE"] = File.expand_path("Gemfile") if ENV["BUNDLE_GEMFILE"] == File.expand_path("Gemfile") || ENV["BUNDLE_GEMFILE"].empty? || ENV["BUNDLE_GEMFILE"].nil?
|
5
5
|
|
6
6
|
Rake::TestTask.new(:test) do |t|
|
7
7
|
t.libs << "test"
|
8
8
|
t.libs << "lib"
|
9
|
-
file_list = ENV["BUNDLE_GEMFILE"] == File.expand_path("
|
9
|
+
file_list = ENV["BUNDLE_GEMFILE"] == File.expand_path("Gemfile") ? FileList["test/**/*_test.rb"] : FileList["test/dependencies/test_dependencies.rb"]
|
10
10
|
t.test_files = file_list
|
11
11
|
end
|
12
12
|
|
data/alba.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.description = "Alba is the fastest JSON serializer for Ruby. It focuses on performance, flexibility and usability."
|
11
11
|
spec.homepage = 'https://github.com/okuramasafumi/alba'
|
12
12
|
spec.license = 'MIT'
|
13
|
-
spec.required_ruby_version = Gem::Requirement.new('>= 2.
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.6.0')
|
14
14
|
|
15
15
|
spec.metadata = {
|
16
16
|
'bug_tracker_uri' => 'https://github.com/okuramasafumi/issues',
|