alba 1.5.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/codeql-analysis.yml +70 -0
- data/.github/workflows/main.yml +3 -1
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +2 -2
- data/README.md +240 -92
- data/alba.gemspec +7 -3
- data/benchmark/collection.rb +60 -4
- data/benchmark/single_resource.rb +33 -2
- data/gemfiles/all.gemfile +1 -0
- data/lib/alba/association.rb +28 -8
- data/lib/alba/default_inflector.rb +19 -1
- data/lib/alba/errors.rb +10 -0
- data/lib/alba/resource.rb +117 -61
- data/lib/alba/version.rb +1 -1
- data/lib/alba.rb +44 -16
- metadata +9 -8
- data/lib/alba/key_transform_factory.rb +0 -33
- data/lib/alba/many.rb +0 -21
- data/lib/alba/one.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c7ab62feaab9747cd1441aea57b8d8f7f1d149261d86e4e64e9f859ae8f1091c
|
4
|
+
data.tar.gz: 4442a77416ee235ad6537580ec0600c4b1c9b0bea7b154b3a96010514316531d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 373f00c4b2bf57f18d65d86a9c8209f9d0c94038f44a98df8e7c7768158d23e03f427729006eb113f0e88fde17a829364cf561026858c83bbb861a7d7eefba13
|
7
|
+
data.tar.gz: ad75036a0f554d13637d32413906f17166347459f2e3bcd3e1fe55f4ddc5d805da59ece5c1e46babadea434d667927aab4e3b7f6b0d37887ee6f58cae10396c9
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# For most projects, this workflow file will not need changing; you simply need
|
2
|
+
# to commit it to your repository.
|
3
|
+
#
|
4
|
+
# You may wish to alter this file to override the set of languages analyzed,
|
5
|
+
# or to provide custom queries or build logic.
|
6
|
+
#
|
7
|
+
# ******** NOTE ********
|
8
|
+
# We have attempted to detect the languages in your repository. Please check
|
9
|
+
# the `language` matrix defined below to confirm you have the correct set of
|
10
|
+
# supported CodeQL languages.
|
11
|
+
#
|
12
|
+
name: "CodeQL"
|
13
|
+
|
14
|
+
on:
|
15
|
+
push:
|
16
|
+
branches: [ main ]
|
17
|
+
pull_request:
|
18
|
+
# The branches below must be a subset of the branches above
|
19
|
+
branches: [ main ]
|
20
|
+
schedule:
|
21
|
+
- cron: '21 4 * * 5'
|
22
|
+
|
23
|
+
jobs:
|
24
|
+
analyze:
|
25
|
+
name: Analyze
|
26
|
+
runs-on: ubuntu-latest
|
27
|
+
permissions:
|
28
|
+
actions: read
|
29
|
+
contents: read
|
30
|
+
security-events: write
|
31
|
+
|
32
|
+
strategy:
|
33
|
+
fail-fast: false
|
34
|
+
matrix:
|
35
|
+
language: [ 'ruby' ]
|
36
|
+
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
37
|
+
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
38
|
+
|
39
|
+
steps:
|
40
|
+
- name: Checkout repository
|
41
|
+
uses: actions/checkout@v2
|
42
|
+
|
43
|
+
# Initializes the CodeQL tools for scanning.
|
44
|
+
- name: Initialize CodeQL
|
45
|
+
uses: github/codeql-action/init@v1
|
46
|
+
with:
|
47
|
+
languages: ${{ matrix.language }}
|
48
|
+
# If you wish to specify custom queries, you can do so here or in a config file.
|
49
|
+
# By default, queries listed here will override any specified in a config file.
|
50
|
+
# Prefix the list here with "+" to use these queries and those in the config file.
|
51
|
+
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
52
|
+
|
53
|
+
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
54
|
+
# If this step fails, then you should remove it and run the build manually (see below)
|
55
|
+
- name: Autobuild
|
56
|
+
uses: github/codeql-action/autobuild@v1
|
57
|
+
|
58
|
+
# ℹ️ Command-line programs to run using the OS shell.
|
59
|
+
# 📚 https://git.io/JvXDl
|
60
|
+
|
61
|
+
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
62
|
+
# and modify them (or add more) to build your code if your project
|
63
|
+
# uses a compiled language
|
64
|
+
|
65
|
+
#- run: |
|
66
|
+
# make bootstrap
|
67
|
+
# make release
|
68
|
+
|
69
|
+
- name: Perform CodeQL Analysis
|
70
|
+
uses: github/codeql-action/analyze@v1
|
data/.github/workflows/main.yml
CHANGED
@@ -8,9 +8,11 @@ jobs:
|
|
8
8
|
fail-fast: false
|
9
9
|
matrix:
|
10
10
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
11
|
-
ruby: [2.5, 2.6, 2.7, 3.0, head, jruby, truffleruby]
|
11
|
+
ruby: [2.5, 2.6, 2.7, 3.0, 3.1, head, jruby, truffleruby]
|
12
12
|
gemfile: [all, without_active_support, without_oj]
|
13
13
|
exclude:
|
14
|
+
- os: windows-latest
|
15
|
+
ruby: 3.1
|
14
16
|
- os: windows-latest
|
15
17
|
ruby: jruby
|
16
18
|
- os: windows-latest
|
data/.rubocop.yml
CHANGED
@@ -46,9 +46,11 @@ Metrics:
|
|
46
46
|
- 'test/**/*.rb'
|
47
47
|
|
48
48
|
# `Resource` module is a core module and its length tends to be long...
|
49
|
+
# `Alba` main module is also long because it has all parts of configuration
|
49
50
|
Metrics/ModuleLength:
|
50
51
|
Exclude:
|
51
52
|
- 'lib/alba/resource.rb'
|
53
|
+
- 'lib/alba.rb'
|
52
54
|
|
53
55
|
# Resource class includes DSLs, which tend to accept long list of parameters
|
54
56
|
Metrics/ParameterLists:
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [1.6.0] 2022-03-16
|
10
|
+
|
11
|
+
- [Feat] Support instance method as an attribute
|
12
|
+
- [Fix] Explicitly raise error when inference is disabled
|
13
|
+
- [Improve] `enable_inference!` now takes inflector as argument
|
14
|
+
- [Improve] `transform_keys` now accepts `:snake` and `:none`
|
15
|
+
- [Deprecate] `to_hash` is special method and should not be used
|
16
|
+
- [Deprecate] `ignoring` in favor of `attributes` overriding
|
17
|
+
- [Deprecate] `Alba.on_nil`, `Alba.on_error` and `Alba.enable_root_key_transformation!`
|
18
|
+
|
9
19
|
## [1.5.0] 2021-11-28
|
10
20
|
|
11
21
|
- [Feat] Add nil handler
|
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.18.0', require: false # For lint
|
13
|
+
gem 'rubocop-performance', '~> 1.13.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
@@ -2,7 +2,6 @@
|
|
2
2
|
[![CI](https://github.com/okuramasafumi/alba/actions/workflows/main.yml/badge.svg)](https://github.com/okuramasafumi/alba/actions/workflows/main.yml)
|
3
3
|
[![codecov](https://codecov.io/gh/okuramasafumi/alba/branch/master/graph/badge.svg?token=3D3HEZ5OXT)](https://codecov.io/gh/okuramasafumi/alba)
|
4
4
|
[![Maintainability](https://api.codeclimate.com/v1/badges/fdab4cc0de0b9addcfe8/maintainability)](https://codeclimate.com/github/okuramasafumi/alba/maintainability)
|
5
|
-
[![Inline docs](http://inch-ci.org/github/okuramasafumi/alba.svg?branch=main)](http://inch-ci.org/github/okuramasafumi/alba)
|
6
5
|
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/okuramasafumi/alba)
|
7
6
|
![GitHub](https://img.shields.io/github/license/okuramasafumi/alba)
|
8
7
|
|
@@ -20,19 +19,19 @@ If you have feature requests or interesting ideas, join us with [Ideas](https://
|
|
20
19
|
|
21
20
|
## Why Alba?
|
22
21
|
|
23
|
-
Because it's fast,
|
22
|
+
Because it's fast, easy-to-use and extensible!
|
24
23
|
|
25
24
|
### Fast
|
26
25
|
|
27
26
|
Alba is faster than most of the alternatives. We have a [benchmark](https://github.com/okuramasafumi/alba/tree/master/benchmark).
|
28
27
|
|
29
|
-
###
|
28
|
+
### Easy to use
|
30
29
|
|
31
|
-
Alba provides
|
30
|
+
Alba provides four DSLs, `attributes` to fetch attribute with its name, `attribute` to execute block for the attribute, `one` to seriaize single attribute with another resource, and `many` to serialize collection attribute with another resource. When you want to do something complex, there are many examples in this README so you can mimic them to get started.
|
32
31
|
|
33
|
-
###
|
32
|
+
### Extensible
|
34
33
|
|
35
|
-
Alba
|
34
|
+
Alba embraces extensibility through common techniques such as class inheritance and module inclusion. Alba provides its capacity with one module so you can still have your own class hierarchy.
|
36
35
|
|
37
36
|
## Installation
|
38
37
|
|
@@ -72,15 +71,6 @@ You can find the documentation on [RubyDoc](https://rubydoc.info/github/okuramas
|
|
72
71
|
* Layout
|
73
72
|
* No runtime dependencies
|
74
73
|
|
75
|
-
## Anti features
|
76
|
-
|
77
|
-
* Sorting keys
|
78
|
-
* Class level support of parameters
|
79
|
-
* Supporting all existing JSON encoder/decoder
|
80
|
-
* Cache
|
81
|
-
* [JSON:API](https://jsonapi.org) support
|
82
|
-
* And many others
|
83
|
-
|
84
74
|
## Usage
|
85
75
|
|
86
76
|
### Configuration
|
@@ -116,23 +106,21 @@ You can consider setting a backend with Symbol as a shortcut to set encoder.
|
|
116
106
|
You can enable inference feature using `enable_inference!` method.
|
117
107
|
|
118
108
|
```ruby
|
119
|
-
Alba.enable_inference!
|
109
|
+
Alba.enable_inference!(with: :active_support)
|
120
110
|
```
|
121
111
|
|
122
|
-
You
|
123
|
-
|
124
|
-
#### Error handling configuration
|
112
|
+
You can choose which inflector Alba uses for inference. Possible value for `with` option are:
|
125
113
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
Alba.on_error :ignore
|
130
|
-
```
|
114
|
+
- `:active_support` for `ActiveSupport::Inflector`
|
115
|
+
- `:dry` for `Dry::Inflector`
|
116
|
+
- any object which responds to some methods (see [below](#custom-inflector))
|
131
117
|
|
132
118
|
For the details, see [Error handling section](#error-handling)
|
133
119
|
|
134
120
|
### Simple serialization with root key
|
135
121
|
|
122
|
+
You can define attributes with (yes) `attributes` macro with attribute names. If your attribute need some calculations, you can use `attribute` with block.
|
123
|
+
|
136
124
|
```ruby
|
137
125
|
class User
|
138
126
|
attr_accessor :id, :name, :email, :created_at, :updated_at
|
@@ -159,7 +147,34 @@ end
|
|
159
147
|
|
160
148
|
user = User.new(1, 'Masafumi OKURA', 'masafumi@example.com')
|
161
149
|
UserResource.new(user).serialize
|
162
|
-
# => "{\"id\":1,\"name\":\"Masafumi OKURA\",\"name_with_email\":\"Masafumi OKURA: masafumi@example.com\"}"
|
150
|
+
# => "{\"user\":{\"id\":1,\"name\":\"Masafumi OKURA\",\"name_with_email\":\"Masafumi OKURA: masafumi@example.com\"}}"
|
151
|
+
```
|
152
|
+
|
153
|
+
You can define instance methods on resources so that you can use it as attribute name in `attributes`.
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
# The serialization result is the same as above
|
157
|
+
class UserResource
|
158
|
+
include Alba::Resource
|
159
|
+
|
160
|
+
root_key :user, :users # Later is for plural
|
161
|
+
|
162
|
+
attributes :id, :name, :name_with_email
|
163
|
+
|
164
|
+
# Attribute methods must accept one argument for each serialized object
|
165
|
+
def name_with_email(user)
|
166
|
+
"#{user.name}: #{user.email}"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
```
|
170
|
+
|
171
|
+
This even works with users collection.
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
user1 = User.new(1, 'Masafumi OKURA', 'masafumi@example.com')
|
175
|
+
user2 = User.new(2, 'Test User', 'test@example.com')
|
176
|
+
UserResource.new([user1, user2]).serialize
|
177
|
+
# => "{\"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\"}]}"
|
163
178
|
```
|
164
179
|
|
165
180
|
### Serialization with associations
|
@@ -240,6 +255,63 @@ class AnotherUserResource
|
|
240
255
|
end
|
241
256
|
```
|
242
257
|
|
258
|
+
You can "filter" association using second proc argument. This proc takes association object and `params`.
|
259
|
+
|
260
|
+
This feature is useful when you want to modify association, such as adding `includes` or `order` to ActiveRecord relations.
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
class User
|
264
|
+
attr_reader :id
|
265
|
+
attr_accessor :articles
|
266
|
+
|
267
|
+
def initialize(id)
|
268
|
+
@id = id
|
269
|
+
@articles = []
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
class Article
|
274
|
+
attr_accessor :id, :title, :body
|
275
|
+
|
276
|
+
def initialize(id, title, body)
|
277
|
+
@id = id
|
278
|
+
@title = title
|
279
|
+
@body = body
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
class ArticleResource
|
284
|
+
include Alba::Resource
|
285
|
+
|
286
|
+
attributes :title
|
287
|
+
end
|
288
|
+
|
289
|
+
class UserResource
|
290
|
+
include Alba::Resource
|
291
|
+
|
292
|
+
attributes :id
|
293
|
+
|
294
|
+
# Second proc works as a filter
|
295
|
+
many :articles,
|
296
|
+
proc { |articles, params|
|
297
|
+
filter = params[:filter] || :odd?
|
298
|
+
articles.select {|a| a.id.send(filter) }
|
299
|
+
},
|
300
|
+
resource: ArticleResource
|
301
|
+
end
|
302
|
+
|
303
|
+
user = User.new(1)
|
304
|
+
article1 = Article.new(1, 'Hello World!', 'Hello World!!!')
|
305
|
+
user.articles << article1
|
306
|
+
article2 = Article.new(2, 'Super nice', 'Really nice!')
|
307
|
+
user.articles << article2
|
308
|
+
|
309
|
+
UserResource.new(user).serialize
|
310
|
+
# => '{"id":1,"articles":[{"title":"Hello World!"}]}'
|
311
|
+
UserResource.new(user, params: {filter: :even?}).serialize
|
312
|
+
# => '{"id":1,"articles":[{"title":"Super nice"}]}'
|
313
|
+
```
|
314
|
+
|
243
315
|
### Inline definition with `Alba.serialize`
|
244
316
|
|
245
317
|
`Alba.serialize` method is a shortcut to define everything inline.
|
@@ -263,9 +335,11 @@ Alba.serialize(something)
|
|
263
335
|
|
264
336
|
Although this might be useful sometimes, it's generally recommended to define a class for Resource.
|
265
337
|
|
266
|
-
### Inheritance and
|
338
|
+
### Inheritance and attributes filter
|
267
339
|
|
268
|
-
You can
|
340
|
+
You can filter out certain attributes by overriding `attributes` instance method. This is useful when you want to customize existing resource with inheritance.
|
341
|
+
|
342
|
+
You can access raw attributes via `super` call. It returns a Hash whose keys are the name of the attribute and whose values are the body. Usually you need only keys to filter out, like below.
|
269
343
|
|
270
344
|
```ruby
|
271
345
|
class Foo
|
@@ -285,7 +359,9 @@ class GenericFooResource
|
|
285
359
|
end
|
286
360
|
|
287
361
|
class RestrictedFooResource < GenericFooResource
|
288
|
-
|
362
|
+
def attributes
|
363
|
+
super.select { |key, _| key.to_sym == :name }
|
364
|
+
end
|
289
365
|
end
|
290
366
|
|
291
367
|
RestrictedFooResource.new(foo).serialize
|
@@ -324,14 +400,22 @@ UserResourceCamel.new(user).serialize
|
|
324
400
|
# => '{"id":1,"firstName":"Masafumi","lastName":"Okura"}'
|
325
401
|
```
|
326
402
|
|
403
|
+
Possible values for `transform_keys` argument are:
|
404
|
+
|
405
|
+
* `:camel` for CamelCase
|
406
|
+
* `:lower_camel` for lowerCamelCase
|
407
|
+
* `:dash` for dash-case
|
408
|
+
* `:snake` for snake_case
|
409
|
+
* `:none` for not transforming keys
|
410
|
+
|
327
411
|
You can also transform root key when:
|
328
412
|
|
329
413
|
* `Alba.enable_inference!` is called
|
330
414
|
* `root_key!` is called in Resource class
|
331
|
-
* `root` option of `transform_keys` is set to true
|
415
|
+
* `root` option of `transform_keys` is set to true
|
332
416
|
|
333
417
|
```ruby
|
334
|
-
Alba.enable_inference!
|
418
|
+
Alba.enable_inference!(with: :active_support) # with :dry also works
|
335
419
|
|
336
420
|
class BankAccount
|
337
421
|
attr_reader :account_number
|
@@ -361,30 +445,29 @@ Supported transformation types are :camel, :lower_camel and :dash.
|
|
361
445
|
|
362
446
|
#### Custom inflector
|
363
447
|
|
364
|
-
A custom inflector can be plugged in as follows
|
365
|
-
|
366
|
-
Alba.inflector = MyCustomInflector
|
367
|
-
```
|
368
|
-
...and has to implement following interface (the parameter `key` is of type `String`):
|
448
|
+
A custom inflector can be plugged in as follows.
|
449
|
+
|
369
450
|
```ruby
|
370
|
-
module
|
371
|
-
|
372
|
-
|
451
|
+
module CustomInflector
|
452
|
+
module_function
|
453
|
+
|
454
|
+
def camelize(string)
|
455
|
+
end
|
456
|
+
|
457
|
+
def camelize_lower(string)
|
373
458
|
end
|
374
459
|
|
375
|
-
def
|
376
|
-
raise "Not implemented"
|
460
|
+
def dasherize(string)
|
377
461
|
end
|
378
462
|
|
379
|
-
def
|
380
|
-
|
463
|
+
def underscore(string)
|
464
|
+
end
|
465
|
+
|
466
|
+
def classify(string)
|
381
467
|
end
|
382
468
|
end
|
383
469
|
|
384
|
-
|
385
|
-
For example you could use `Dry::Inflector`, which implements exactly the above interface. If you are developing a `Hanami`-Application `Dry::Inflector` is around. In this case the following would be sufficient:
|
386
|
-
```ruby
|
387
|
-
Alba.inflector = Dry::Inflector.new
|
470
|
+
Alba.enable_inference!(with: CustomInflector)
|
388
471
|
```
|
389
472
|
|
390
473
|
### Filtering attributes
|
@@ -469,7 +552,7 @@ We believe this is clearer than using some (not implemented yet) DSL such as `de
|
|
469
552
|
After `Alba.enable_inference!` called, Alba tries to infer root key and association resource name.
|
470
553
|
|
471
554
|
```ruby
|
472
|
-
Alba.enable_inference!
|
555
|
+
Alba.enable_inference!(with: :active_support) # with :dry also works
|
473
556
|
|
474
557
|
class User
|
475
558
|
attr_reader :id
|
@@ -517,8 +600,6 @@ This resource automatically sets its root key to either "users" or "user", depen
|
|
517
600
|
|
518
601
|
Also, you don't have to specify which resource class to use with `many`. Alba infers it from association name.
|
519
602
|
|
520
|
-
Note that to enable this feature you must install `ActiveSupport` gem.
|
521
|
-
|
522
603
|
### Error handling
|
523
604
|
|
524
605
|
You can set error handler globally or per resource using `on_error`.
|
@@ -562,12 +643,14 @@ There are four possible arguments `on_error` method accepts.
|
|
562
643
|
The block receives five arguments, `error`, `object`, `key`, `attribute` and `resource class` and must return a two-element array. Below is an example.
|
563
644
|
|
564
645
|
```ruby
|
565
|
-
|
566
|
-
Alba
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
646
|
+
class ExampleResource
|
647
|
+
include Alba::Resource
|
648
|
+
on_error do |error, object, key, attribute, resource_class|
|
649
|
+
if resource_class == MyResource
|
650
|
+
['error_fallback', object.error_fallback]
|
651
|
+
else
|
652
|
+
[key, error.message]
|
653
|
+
end
|
571
654
|
end
|
572
655
|
end
|
573
656
|
```
|
@@ -624,43 +707,6 @@ UserResource.new(User.new(1)).serialize
|
|
624
707
|
# => '{"user":{"id":1,"name":"User1","age":20}}'
|
625
708
|
```
|
626
709
|
|
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
|
-
|
664
710
|
### Metadata
|
665
711
|
|
666
712
|
You can set a metadata with `meta` DSL or `meta` option.
|
@@ -824,6 +870,108 @@ Also note that we use percentage notation here to use double quotes. Using singl
|
|
824
870
|
|
825
871
|
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).
|
826
872
|
|
873
|
+
### Extend Alba
|
874
|
+
|
875
|
+
Sometimes we have shared behaviors across resources. In such cases we can have a module for common logic.
|
876
|
+
|
877
|
+
In `attribute` block we can call instance method so we can improve the code below:
|
878
|
+
|
879
|
+
```ruby
|
880
|
+
class FooResource
|
881
|
+
include Alba::Resource
|
882
|
+
# other attributes
|
883
|
+
attribute :created_at do |foo|
|
884
|
+
foo.created_at.strftime('%m/%d/%Y')
|
885
|
+
end
|
886
|
+
|
887
|
+
attribute :updated_at do |foo|
|
888
|
+
foo.updated_at.strftime('%m/%d/%Y')
|
889
|
+
end
|
890
|
+
end
|
891
|
+
|
892
|
+
class BarResource
|
893
|
+
include Alba::Resource
|
894
|
+
# other attributes
|
895
|
+
attribute :created_at do |bar|
|
896
|
+
bar.created_at.strftime('%m/%d/%Y')
|
897
|
+
end
|
898
|
+
|
899
|
+
attribute :updated_at do |bar|
|
900
|
+
bar.updated_at.strftime('%m/%d/%Y')
|
901
|
+
end
|
902
|
+
end
|
903
|
+
```
|
904
|
+
|
905
|
+
to:
|
906
|
+
|
907
|
+
```ruby
|
908
|
+
module SharedLogic
|
909
|
+
def format_time(time)
|
910
|
+
time.strftime('%m/%d/%Y')
|
911
|
+
end
|
912
|
+
end
|
913
|
+
|
914
|
+
class FooResource
|
915
|
+
include Alba::Resource
|
916
|
+
include SharedLogic
|
917
|
+
# other attributes
|
918
|
+
attribute :created_at do |foo|
|
919
|
+
format_time(foo.created_at)
|
920
|
+
end
|
921
|
+
|
922
|
+
attribute :updated_at do |foo|
|
923
|
+
format_time(foo.updated_at)
|
924
|
+
end
|
925
|
+
end
|
926
|
+
|
927
|
+
class BarResource
|
928
|
+
include Alba::Resource
|
929
|
+
include SharedLogic
|
930
|
+
# other attributes
|
931
|
+
attribute :created_at do |bar|
|
932
|
+
format_time(bar.created_at)
|
933
|
+
end
|
934
|
+
|
935
|
+
attribute :updated_at do |bar|
|
936
|
+
format_time(bar.updated_at)
|
937
|
+
end
|
938
|
+
end
|
939
|
+
```
|
940
|
+
|
941
|
+
We can even add our own DSL to serialize attributes for readability and removing code duplications.
|
942
|
+
|
943
|
+
To do so, we need to `extend` our module. Let's see how we can achieve the same goal with this approach.
|
944
|
+
|
945
|
+
```ruby
|
946
|
+
module AlbaExtension
|
947
|
+
# Here attrs are an Array of Symbol
|
948
|
+
def formatted_time_attributes(*attrs)
|
949
|
+
attrs.each do |attr|
|
950
|
+
attribute attr do |object|
|
951
|
+
time = object.send(attr)
|
952
|
+
time.strftime('%m/%d/%Y')
|
953
|
+
end
|
954
|
+
end
|
955
|
+
end
|
956
|
+
end
|
957
|
+
|
958
|
+
class FooResource
|
959
|
+
include Alba::Resource
|
960
|
+
extend AlbaExtension
|
961
|
+
# other attributes
|
962
|
+
formatted_time_attributes :created_at, :updated_at
|
963
|
+
end
|
964
|
+
|
965
|
+
class BarResource
|
966
|
+
include Alba::Resource
|
967
|
+
extend AlbaExtension
|
968
|
+
# other attributes
|
969
|
+
formatted_time_attributes :created_at, :updated_at
|
970
|
+
end
|
971
|
+
```
|
972
|
+
|
973
|
+
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
|
+
|
827
975
|
## Rails
|
828
976
|
|
829
977
|
When you use Alba in Rails, you can create an initializer file with the line below for compatibility with Rails JSON encoder.
|
data/alba.gemspec
CHANGED
@@ -12,9 +12,13 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.license = 'MIT'
|
13
13
|
spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
|
14
14
|
|
15
|
-
spec.metadata
|
16
|
-
|
17
|
-
|
15
|
+
spec.metadata = {
|
16
|
+
'bug_tracker_uri' => 'https://github.com/okuramasafumi/issues',
|
17
|
+
'changelog_uri' => 'https://github.com/okuramasafumi/alba/blob/main/CHANGELOG.md',
|
18
|
+
'documentation_uri' => 'https://rubydoc.info/github/okuramasafumi/alba',
|
19
|
+
'source_code_uri' => 'https://github.com/okuramasafumi/alba',
|
20
|
+
'rubygems_mfa_required' => 'true'
|
21
|
+
}
|
18
22
|
|
19
23
|
# Specify which files should be added to the gem when it is released.
|
20
24
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|