alba 3.0.3 → 3.2.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/.github/workflows/codeql-analysis.yml +3 -3
- data/.github/workflows/main.yml +8 -3
- data/.github/workflows/perf.yml +1 -6
- data/.rubocop.yml +10 -1
- data/CHANGELOG.md +14 -1
- data/Gemfile +4 -5
- data/README.md +107 -4
- data/Rakefile +9 -7
- data/benchmark/prep.rb +2 -2
- data/docs/rails.md +10 -0
- data/lib/alba/association.rb +15 -6
- data/lib/alba/conditional_attribute.rb +0 -4
- data/lib/alba/layout.rb +9 -9
- data/lib/alba/nested_attribute.rb +3 -0
- data/lib/alba/railtie.rb +11 -0
- data/lib/alba/resource.rb +59 -29
- data/lib/alba/version.rb +1 -1
- data/lib/alba.rb +9 -10
- data/script/perf_check.rb +1 -68
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3b0683f5d43a9e7ce7eaea591c2a67267f56d7327a045ab0e2da081256a4543
|
4
|
+
data.tar.gz: 0dd80fd3c618fe35bc7b727f01c6ce8c9357fe1b0d303e2a756daf245f6d7e50
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec6b56b091d9fe52bf05dd8ac7f9d67bc33db86e480e2281e2914cc075939a3c21b3b2cc1e6680a435c0a69069b3e73db0983b3a3d6433bb1c12b65f83ee307a
|
7
|
+
data.tar.gz: afb843e8dbaa8e45181526128704a4fbd0ed427306bb294d73dc6889477e037d86091ec2fc94bf9db6d973f5da07551ce74977d9469184c67fbc3354c904f906
|
@@ -42,7 +42,7 @@ jobs:
|
|
42
42
|
|
43
43
|
# Initializes the CodeQL tools for scanning.
|
44
44
|
- name: Initialize CodeQL
|
45
|
-
uses: github/codeql-action/init@
|
45
|
+
uses: github/codeql-action/init@v3
|
46
46
|
with:
|
47
47
|
languages: ${{ matrix.language }}
|
48
48
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
@@ -53,7 +53,7 @@ jobs:
|
|
53
53
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
54
54
|
# If this step fails, then you should remove it and run the build manually (see below)
|
55
55
|
- name: Autobuild
|
56
|
-
uses: github/codeql-action/autobuild@
|
56
|
+
uses: github/codeql-action/autobuild@v3
|
57
57
|
|
58
58
|
# ℹ️ Command-line programs to run using the OS shell.
|
59
59
|
# 📚 https://git.io/JvXDl
|
@@ -67,4 +67,4 @@ jobs:
|
|
67
67
|
# make release
|
68
68
|
|
69
69
|
- name: Perform CodeQL Analysis
|
70
|
-
uses: github/codeql-action/analyze@
|
70
|
+
uses: github/codeql-action/analyze@v3
|
data/.github/workflows/main.yml
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
name: CI
|
2
2
|
|
3
|
-
on:
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- main
|
7
|
+
pull_request:
|
4
8
|
|
5
9
|
jobs:
|
6
10
|
build:
|
@@ -8,7 +12,7 @@ jobs:
|
|
8
12
|
fail-fast: false
|
9
13
|
matrix:
|
10
14
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
11
|
-
ruby: ['3.0', 3.1, 3.2, head, truffleruby]
|
15
|
+
ruby: ['3.0', 3.1, 3.2, 3.3, head, jruby, truffleruby]
|
12
16
|
gemfile: [all, without_active_support, without_oj]
|
13
17
|
exclude:
|
14
18
|
- os: windows-latest
|
@@ -29,6 +33,7 @@ jobs:
|
|
29
33
|
run: |
|
30
34
|
bundle exec rake
|
31
35
|
- name: CodeCov
|
32
|
-
uses: codecov/codecov-action@
|
36
|
+
uses: codecov/codecov-action@v4
|
33
37
|
with:
|
34
38
|
files: ./coverage/coverage.xml
|
39
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
data/.github/workflows/perf.yml
CHANGED
@@ -4,18 +4,13 @@ on: [pull_request]
|
|
4
4
|
|
5
5
|
jobs:
|
6
6
|
build:
|
7
|
-
strategy:
|
8
|
-
fail-fast: false
|
9
|
-
matrix:
|
10
|
-
ruby: ['3.0', 3.1, 3.2]
|
11
7
|
runs-on: ubuntu-latest
|
12
8
|
steps:
|
13
9
|
- uses: actions/checkout@v4
|
14
10
|
- name: Set up Ruby
|
15
11
|
uses: ruby/setup-ruby@v1
|
16
12
|
with:
|
17
|
-
ruby-version:
|
18
|
-
bundler-cache: true
|
13
|
+
ruby-version: 3.3
|
19
14
|
- name: Run benchmark
|
20
15
|
run: |
|
21
16
|
ruby script/perf_check.rb
|
data/.rubocop.yml
CHANGED
@@ -15,7 +15,6 @@ require:
|
|
15
15
|
|
16
16
|
AllCops:
|
17
17
|
Exclude:
|
18
|
-
- 'Rakefile'
|
19
18
|
- 'alba.gemspec'
|
20
19
|
- 'benchmark/**/*.rb'
|
21
20
|
- 'docs/**/*'
|
@@ -89,6 +88,11 @@ Minitest/NoTestCases:
|
|
89
88
|
Exclude:
|
90
89
|
- 'test/dependencies/test_dependencies.rb'
|
91
90
|
|
91
|
+
# We need to use `OpenStruct` to wrap object in ConfitionalAttribute
|
92
|
+
Performance/OpenStruct:
|
93
|
+
Exclude:
|
94
|
+
- 'lib/alba/conditional_attribute.rb'
|
95
|
+
|
92
96
|
# We need to eval resource code to test errors on resource classes
|
93
97
|
Security/Eval:
|
94
98
|
Exclude:
|
@@ -141,6 +145,11 @@ Style/MethodCallWithArgsParentheses:
|
|
141
145
|
Style/MissingElse:
|
142
146
|
EnforcedStyle: case
|
143
147
|
|
148
|
+
# We need to use `OpenStruct` to wrap object in ConfitionalAttribute
|
149
|
+
Style/OpenStructUse:
|
150
|
+
Exclude:
|
151
|
+
- 'lib/alba/conditional_attribute.rb'
|
152
|
+
|
144
153
|
# It's example code, please forgive us
|
145
154
|
Style/OptionalBooleanParameter:
|
146
155
|
Exclude:
|
data/CHANGELOG.md
CHANGED
@@ -1,11 +1,24 @@
|
|
1
1
|
# Changelog
|
2
2
|
All notable changes to this project will be documented in this file.
|
3
3
|
|
4
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [3.2.0] 2024-06-21
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- Rails controller integration [#370](https://github.com/okuramasafumi/alba/pull/370)
|
14
|
+
- Modification API: `transform_keys!` [#372](https://github.com/okuramasafumi/alba/pull/372)
|
15
|
+
|
16
|
+
## [3.1.0] 2024-03-23
|
17
|
+
|
18
|
+
### Added
|
19
|
+
|
20
|
+
- Add the ability to change key for metadata [#362](https://github.com/okuramasafumi/alba/pull/362)
|
21
|
+
|
9
22
|
## [3.0.3] 2023-12-25
|
10
23
|
|
11
24
|
### Fixed
|
data/Gemfile
CHANGED
@@ -9,13 +9,12 @@ gem 'ffaker', require: false # For testing
|
|
9
9
|
gem 'minitest', '~> 5.14' # For test
|
10
10
|
gem 'railties', require: false # For Rails integration testing
|
11
11
|
gem 'rake', '~> 13.0' # For test and automation
|
12
|
-
gem 'rubocop', '
|
12
|
+
gem 'rubocop', '~> 1.64.0', require: false # For lint
|
13
13
|
gem 'rubocop-gem_dev', '>= 0.3.0', require: false # For lint
|
14
14
|
gem 'rubocop-md', '~> 1.0', require: false # For lint
|
15
|
-
gem 'rubocop-minitest', '
|
16
|
-
gem 'rubocop-performance', '
|
17
|
-
gem 'rubocop-rake', '
|
18
|
-
gem 'ruby-lsp', require: false # For language server
|
15
|
+
gem 'rubocop-minitest', '~> 0.35.0', require: false # For lint
|
16
|
+
gem 'rubocop-performance', '~> 1.21.0', require: false # For lint
|
17
|
+
gem 'rubocop-rake', '~> 0.6.0', require: false # For lint
|
19
18
|
gem 'simplecov', '~> 0.22.0', require: false # For test coverage
|
20
19
|
gem 'simplecov-cobertura', require: false # For test coverage
|
21
20
|
# gem 'steep', require: false # For language server and typing
|
data/README.md
CHANGED
@@ -92,7 +92,7 @@ Alba is easy to use because there are only a few methods to remember. It's also
|
|
92
92
|
|
93
93
|
### Feature rich
|
94
94
|
|
95
|
-
While Alba's core is simple, it provides additional features when you need them
|
95
|
+
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), [root key and association resource name inference](#root-key-and-association-resource-name-inference) and [supports layouts](#layout).
|
96
96
|
|
97
97
|
### Other reasons
|
98
98
|
|
@@ -130,6 +130,7 @@ You can find the documentation on [RubyDoc](https://rubydoc.info/github/okuramas
|
|
130
130
|
* Selectable backend
|
131
131
|
* Key transformation
|
132
132
|
* Root key and association resource name inference
|
133
|
+
* Inline definition without explicit classes
|
133
134
|
* Error handling
|
134
135
|
* Nil handling
|
135
136
|
* Circular associations control
|
@@ -362,7 +363,7 @@ end
|
|
362
363
|
FooResource.new(Foo.new).serialize
|
363
364
|
# => '{"bar":"This is Foo"}'
|
364
365
|
```
|
365
|
-
|
366
|
+
|
366
367
|
#### Params
|
367
368
|
|
368
369
|
You can pass a Hash to the resource for internal use. It can be used as "flags" to control attribute content.
|
@@ -1232,6 +1233,65 @@ UserResource.new([user]).serialize
|
|
1232
1233
|
|
1233
1234
|
UserResource.new([user]).serialize(meta: {foo: :bar})
|
1234
1235
|
# => '{"users":[{"id":1,"name":"Masafumi OKURA"}],"meta":{"size":1,"foo":"bar"}}'
|
1236
|
+
```
|
1237
|
+
|
1238
|
+
You can change the key for metadata. If you change the key, it also affects the key when you pass `meta` option.
|
1239
|
+
|
1240
|
+
```ruby
|
1241
|
+
# You can change meta key
|
1242
|
+
class UserResourceWithDifferentMetaKey
|
1243
|
+
include Alba::Resource
|
1244
|
+
|
1245
|
+
root_key :user, :users
|
1246
|
+
|
1247
|
+
attributes :id, :name
|
1248
|
+
|
1249
|
+
meta :my_meta do
|
1250
|
+
{foo: :bar}
|
1251
|
+
end
|
1252
|
+
end
|
1253
|
+
|
1254
|
+
UserResourceWithDifferentMetaKey.new([user]).serialize
|
1255
|
+
# => '{"users":[{"id":1,"name":"Masafumi OKURA"}],"my_meta":{"foo":"bar"}}'
|
1256
|
+
|
1257
|
+
UserResourceWithDifferentMetaKey.new([user]).serialize(meta: {extra: 42})
|
1258
|
+
# => '{"users":[{"id":1,"name":"Masafumi OKURA"}],"meta":{"size":1,"extra":42}}'
|
1259
|
+
|
1260
|
+
class UserResourceChangingMetaKeyOnly
|
1261
|
+
include Alba::Resource
|
1262
|
+
|
1263
|
+
root_key :user, :users
|
1264
|
+
|
1265
|
+
attributes :id, :name
|
1266
|
+
|
1267
|
+
meta :my_meta
|
1268
|
+
end
|
1269
|
+
|
1270
|
+
UserResourceChangingMetaKeyOnly.new([user]).serialize
|
1271
|
+
# => '{"users":[{"id":1,"name":"Masafumi OKURA"}]}'
|
1272
|
+
|
1273
|
+
UserResourceChangingMetaKeyOnly.new([user]).serialize(meta: {extra: 42})
|
1274
|
+
# => '{"users":[{"id":1,"name":"Masafumi OKURA"}],"my_meta":{"extra":42}}'
|
1275
|
+
```
|
1276
|
+
|
1277
|
+
It's also possible to remove the key for metadata, resulting a flat structure.
|
1278
|
+
|
1279
|
+
```ruby
|
1280
|
+
class UserResourceRemovingMetaKey
|
1281
|
+
include Alba::Resource
|
1282
|
+
|
1283
|
+
root_key :user, :users
|
1284
|
+
|
1285
|
+
attributes :id, :name
|
1286
|
+
|
1287
|
+
meta nil
|
1288
|
+
end
|
1289
|
+
|
1290
|
+
UserResourceRemovingMetaKey.new([user]).serialize
|
1291
|
+
# => '{"users":[{"id":1,"name":"Masafumi OKURA"}]}'
|
1292
|
+
|
1293
|
+
UserResourceRemovingMetaKey.new([user]).serialize(meta: {extra: 42})
|
1294
|
+
# => '{"users":[{"id":1,"name":"Masafumi OKURA"}],"extra":42}'
|
1235
1295
|
|
1236
1296
|
# You can set metadata with `meta` option alone
|
1237
1297
|
|
@@ -1457,7 +1517,36 @@ end
|
|
1457
1517
|
|
1458
1518
|
Within `helper` block, all methods should be defined without `self.`.
|
1459
1519
|
|
1460
|
-
|
1520
|
+
### Experimental: modification API
|
1521
|
+
|
1522
|
+
Alba now provides an experimental API to modify existing resource class without adding new classes. Currently only `transform_keys!` is implemented.
|
1523
|
+
|
1524
|
+
Modification API returns a new class with given modifications. It's useful when you want lots of resource classes with small changes. See it in action:
|
1525
|
+
|
1526
|
+
```ruby
|
1527
|
+
class FooResource
|
1528
|
+
include Alba::Resource
|
1529
|
+
|
1530
|
+
transform_keys :camel
|
1531
|
+
|
1532
|
+
attributes :id
|
1533
|
+
end
|
1534
|
+
|
1535
|
+
# Rails app
|
1536
|
+
class FoosController < ApplicationController
|
1537
|
+
def index
|
1538
|
+
foos = Foo.where(some: :condition)
|
1539
|
+
key_transformation_type = params[:key_transformation_type] # Say it's "lower_camel"
|
1540
|
+
# When params is absent, do not use modification API since it's slower
|
1541
|
+
resource_class = key_transformation_type ? FooResource.transform_keys!(key_transformation_type) : FooResource
|
1542
|
+
render json: resource_class.new(foos).serialize # The keys are lower_camel
|
1543
|
+
end
|
1544
|
+
end
|
1545
|
+
```
|
1546
|
+
|
1547
|
+
The point is that there's no need to define classes for each key transformation type (dash, camel, lower_camel and snake). This gives even more flexibility.
|
1548
|
+
|
1549
|
+
There are some drawbacks with this approach. For example, it creates an internal, anonymous class when it's called, so there is a performance penalty and debugging difficulty. It's recommended to define classes manually when you don't need high flexibility.
|
1461
1550
|
|
1462
1551
|
### Caching
|
1463
1552
|
|
@@ -1599,6 +1688,20 @@ class BarResource
|
|
1599
1688
|
end
|
1600
1689
|
```
|
1601
1690
|
|
1691
|
+
You can also pass options to your helpers.
|
1692
|
+
|
1693
|
+
```ruby
|
1694
|
+
module AlbaExtension
|
1695
|
+
def time_attributes(*attrs, **options)
|
1696
|
+
attrs.each do |attr|
|
1697
|
+
attribute(attr, **options) do |object|
|
1698
|
+
object.__send__(attr).iso8601
|
1699
|
+
end
|
1700
|
+
end
|
1701
|
+
end
|
1702
|
+
end
|
1703
|
+
```
|
1704
|
+
|
1602
1705
|
### Debugging
|
1603
1706
|
|
1604
1707
|
Debugging is not easy. If you find Alba not working as you expect, there are a few things to do:
|
@@ -1635,7 +1738,7 @@ module Logging
|
|
1635
1738
|
# `...` was added in Ruby 2.7
|
1636
1739
|
def serialize(...)
|
1637
1740
|
puts serializable_hash
|
1638
|
-
super
|
1741
|
+
super
|
1639
1742
|
end
|
1640
1743
|
end
|
1641
1744
|
|
data/Rakefile
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
3
|
|
4
|
-
|
4
|
+
if ENV['BUNDLE_GEMFILE'] == File.expand_path('Gemfile') || ENV['BUNDLE_GEMFILE'].empty? || ENV['BUNDLE_GEMFILE'].nil?
|
5
|
+
ENV['BUNDLE_GEMFILE'] = File.expand_path('Gemfile')
|
6
|
+
end
|
5
7
|
|
6
8
|
Rake::TestTask.new(:test) do |t|
|
7
|
-
t.libs <<
|
8
|
-
t.libs <<
|
9
|
-
file_list = ENV[
|
9
|
+
t.libs << 'test'
|
10
|
+
t.libs << 'lib'
|
11
|
+
file_list = ENV['BUNDLE_GEMFILE'] == File.expand_path('Gemfile') ? FileList['test/**/*_test.rb'] : FileList['test/dependencies/test_dependencies.rb']
|
10
12
|
t.test_files = file_list
|
11
13
|
end
|
12
14
|
|
13
|
-
task :
|
15
|
+
task default: :test
|
data/benchmark/prep.rb
CHANGED
@@ -7,7 +7,7 @@ gemfile(true) do
|
|
7
7
|
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
8
8
|
|
9
9
|
gem "active_model_serializers"
|
10
|
-
gem "activerecord", "
|
10
|
+
gem "activerecord", "~> 7.1"
|
11
11
|
gem "alba", path: '../'
|
12
12
|
gem "benchmark-ips"
|
13
13
|
gem "benchmark-memory"
|
@@ -23,7 +23,7 @@ gemfile(true) do
|
|
23
23
|
gem "oj"
|
24
24
|
gem "representable"
|
25
25
|
gem "simple_ams"
|
26
|
-
gem "sqlite3"
|
26
|
+
gem "sqlite3", "~> 1.4"
|
27
27
|
end
|
28
28
|
|
29
29
|
# --- Test data model setup ---
|
data/docs/rails.md
CHANGED
@@ -44,3 +44,13 @@ render json: FooResource.new(foo), only: [:id]
|
|
44
44
|
# This is OK
|
45
45
|
render json: FooResource.new(foo), status: 200
|
46
46
|
```
|
47
|
+
|
48
|
+
### Shorthand for rendering JSON with Alba
|
49
|
+
|
50
|
+
Now you can render JSON with shorthand.
|
51
|
+
|
52
|
+
First, using `render json: serialize(target)` renders JSON for given target object. You can pass `with: SomeSerializer` option to render with `SomeSerializer` in this case. If you skip `with` option Alba tries to find the correct serialize automatically.
|
53
|
+
|
54
|
+
There is a shorter version: `render_serialized_json(target)`. It also accepts `with` option.
|
55
|
+
|
56
|
+
It's recommended to use `with` option now since it cannot automatically find correct serializers sometimes.
|
data/lib/alba/association.rb
CHANGED
@@ -29,6 +29,11 @@ module Alba
|
|
29
29
|
assign_resource(nesting, key_transformation, block, helper)
|
30
30
|
end
|
31
31
|
|
32
|
+
# This is the same API in `NestedAttribute`
|
33
|
+
def key_transformation=(type)
|
34
|
+
@resource.transform_keys(type) unless @resource.is_a?(Proc)
|
35
|
+
end
|
36
|
+
|
32
37
|
# Recursively converts an object into a Hash
|
33
38
|
#
|
34
39
|
# @param target [Object] the object having an association method
|
@@ -65,13 +70,9 @@ module Alba
|
|
65
70
|
end
|
66
71
|
end
|
67
72
|
|
68
|
-
def assign_resource(nesting, key_transformation, block, helper)
|
73
|
+
def assign_resource(nesting, key_transformation, block, helper)
|
69
74
|
@resource = if block
|
70
|
-
|
71
|
-
klass.helper(helper) if helper
|
72
|
-
klass.transform_keys(key_transformation)
|
73
|
-
klass.class_eval(&block)
|
74
|
-
klass
|
75
|
+
charged_resource_class(helper, key_transformation, block)
|
75
76
|
elsif Alba.inflector
|
76
77
|
Alba.infer_resource_class(@name, nesting: nesting)
|
77
78
|
else
|
@@ -79,6 +80,14 @@ module Alba
|
|
79
80
|
end
|
80
81
|
end
|
81
82
|
|
83
|
+
def charged_resource_class(helper, key_transformation, block)
|
84
|
+
klass = Alba.resource_class
|
85
|
+
klass.helper(helper) if helper
|
86
|
+
klass.transform_keys(key_transformation)
|
87
|
+
klass.class_eval(&block)
|
88
|
+
klass
|
89
|
+
end
|
90
|
+
|
82
91
|
def to_h_with_each_resource(object, within, params)
|
83
92
|
object.map do |item|
|
84
93
|
@resource.call(item).new(item, within: within, params: params).to_h
|
@@ -51,8 +51,6 @@ module Alba
|
|
51
51
|
|
52
52
|
# OpenStruct is used as a simple solution for converting Hash or Array of Hash into an object
|
53
53
|
# Using OpenStruct is not good in general, but in this case there's no other solution
|
54
|
-
# rubocop:disable Style/OpenStructUse
|
55
|
-
# rubocop:disable Performance/OpenStruct
|
56
54
|
def objectize(fetched_attribute)
|
57
55
|
return fetched_attribute unless @body.is_a?(Alba::Association)
|
58
56
|
|
@@ -64,7 +62,5 @@ module Alba
|
|
64
62
|
OpenStruct.new(fetched_attribute)
|
65
63
|
end
|
66
64
|
end
|
67
|
-
# rubocop:enable Style/OpenStructUse
|
68
|
-
# rubocop:enable Performance/OpenStruct
|
69
65
|
end
|
70
66
|
end
|
data/lib/alba/layout.rb
CHANGED
@@ -12,17 +12,17 @@ module Alba
|
|
12
12
|
# @param file [String] name of the layout file
|
13
13
|
# @param inline [Proc] a proc returning JSON string or a Hash representing JSON
|
14
14
|
def initialize(file:, inline:)
|
15
|
-
if file
|
16
|
-
|
15
|
+
@body = if file
|
16
|
+
raise ArgumentError, 'File layout must be a String representing filename' unless file.is_a?(String)
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
file
|
19
|
+
elsif inline
|
20
|
+
raise ArgumentError, 'Inline layout must be a Proc returning a Hash or a String' unless inline.is_a?(Proc)
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
inline
|
23
|
+
else
|
24
|
+
raise ArgumentError, 'Layout must be either String or Proc'
|
25
|
+
end
|
26
26
|
end
|
27
27
|
|
28
28
|
# Serialize within layout
|
@@ -2,6 +2,9 @@ module Alba
|
|
2
2
|
# Representing nested attribute
|
3
3
|
# @api private
|
4
4
|
class NestedAttribute
|
5
|
+
# Setter for key_transformation, used when it's changed after class definition
|
6
|
+
attr_writer :key_transformation
|
7
|
+
|
5
8
|
# @param key_transformation [Symbol] determines how to transform keys
|
6
9
|
# @param block [Proc] class body
|
7
10
|
def initialize(key_transformation: :none, &block)
|
data/lib/alba/railtie.rb
CHANGED
@@ -3,6 +3,17 @@ module Alba
|
|
3
3
|
class Railtie < Rails::Railtie
|
4
4
|
initializer 'alba.initialize' do
|
5
5
|
Alba.inflector = :active_support
|
6
|
+
|
7
|
+
ActiveSupport.on_load(:action_controller) do
|
8
|
+
ActionController::Base.define_method(:serialize) do |obj, with: nil, &block|
|
9
|
+
with.nil? ? Alba.resource_with(obj, &block) : with.new(obj)
|
10
|
+
end
|
11
|
+
|
12
|
+
ActionController::Base.define_method(:render_serialized_json) do |obj, with: nil, &block|
|
13
|
+
json = with.nil? ? Alba.resource_with(obj, &block) : with.new(obj)
|
14
|
+
render json: json
|
15
|
+
end
|
16
|
+
end
|
6
17
|
end
|
7
18
|
end
|
8
19
|
end
|
data/lib/alba/resource.rb
CHANGED
@@ -22,17 +22,17 @@ module Alba
|
|
22
22
|
# @private
|
23
23
|
def self.included(base) # rubocop:disable Metrics/MethodLength
|
24
24
|
super
|
25
|
-
setup_method_body = 'private def _setup;'
|
26
25
|
base.class_eval do
|
27
26
|
# Initialize
|
27
|
+
setup_method_body = +'private def _setup;'
|
28
28
|
INTERNAL_VARIABLES.each do |name, initial|
|
29
29
|
instance_variable_set(:"@#{name}", initial.dup) unless instance_variable_defined?(:"@#{name}")
|
30
30
|
setup_method_body << "@#{name} = self.class.#{name};"
|
31
31
|
end
|
32
|
-
|
32
|
+
setup_method_body << 'end'
|
33
|
+
class_eval(setup_method_body, __FILE__, __LINE__ + 1)
|
34
|
+
define_method(:encode, Alba.encoder)
|
33
35
|
end
|
34
|
-
setup_method_body << 'end'
|
35
|
-
base.class_eval(setup_method_body, __FILE__, __LINE__ + 1)
|
36
36
|
base.include InstanceMethods
|
37
37
|
base.extend ClassMethods
|
38
38
|
end
|
@@ -66,7 +66,14 @@ module Alba
|
|
66
66
|
# @see #serialize
|
67
67
|
# @see https://github.com/rails/rails/blob/7-0-stable/actionpack/lib/action_controller/metal/renderers.rb#L156
|
68
68
|
def to_json(options = {}, root_key: nil, meta: {})
|
69
|
-
|
69
|
+
confusing_options = options.keys.select { |k| k.to_sym == :only || k.to_sym == :except }
|
70
|
+
unless confusing_options.empty?
|
71
|
+
confusing_options.sort!
|
72
|
+
confusing_options.map! { |s| "\"#{s}\"" }
|
73
|
+
message = "You passed #{confusing_options.join(' and ')} options but ignored. Please refer to the document: https://github.com/okuramasafumi/alba/blob/main/docs/rails.md"
|
74
|
+
Kernel.warn(message)
|
75
|
+
end
|
76
|
+
serialize(root_key: root_key, meta: meta)
|
70
77
|
end
|
71
78
|
|
72
79
|
# Returns a Hash correspondng {#serialize}
|
@@ -96,17 +103,6 @@ module Alba
|
|
96
103
|
|
97
104
|
private
|
98
105
|
|
99
|
-
def _to_json(root_key, meta, options)
|
100
|
-
confusing_options = options.keys.select { |k| k.to_sym == :only || k.to_sym == :except }
|
101
|
-
unless confusing_options.empty?
|
102
|
-
confusing_options.sort!
|
103
|
-
confusing_options.map! { |s| "\"#{s}\"" }
|
104
|
-
message = "You passed #{confusing_options.join(' and ')} options but ignored. Please refer to the document: https://github.com/okuramasafumi/alba/blob/main/docs/rails.md"
|
105
|
-
Kernel.warn(message)
|
106
|
-
end
|
107
|
-
serialize(root_key: root_key, meta: meta)
|
108
|
-
end
|
109
|
-
|
110
106
|
def serialize_with(hash)
|
111
107
|
serialized_json = encode(hash)
|
112
108
|
return serialized_json unless @_layout
|
@@ -115,13 +111,22 @@ module Alba
|
|
115
111
|
end
|
116
112
|
|
117
113
|
def hash_with_metadata(hash, meta)
|
118
|
-
return hash if meta.empty? && @_meta.nil?
|
114
|
+
return hash if meta.empty? && @_meta&.last.nil?
|
119
115
|
|
120
|
-
|
121
|
-
|
116
|
+
key, block = @_meta || :meta
|
117
|
+
|
118
|
+
if key
|
119
|
+
hash[key] = _metadata(block, meta)
|
120
|
+
else
|
121
|
+
_metadata(block, meta).each { |k, v| hash[k] = v }
|
122
|
+
end
|
122
123
|
hash
|
123
124
|
end
|
124
125
|
|
126
|
+
def _metadata(block, meta)
|
127
|
+
block ? instance_eval(&block).merge(meta) : meta
|
128
|
+
end
|
129
|
+
|
125
130
|
def serializable_hash_for_collection
|
126
131
|
if @_collection_key
|
127
132
|
@object.to_h do |item|
|
@@ -212,14 +217,17 @@ module Alba
|
|
212
217
|
end
|
213
218
|
|
214
219
|
def handle_error(error, obj, key, attribute, hash)
|
215
|
-
on_error = @_on_error || :raise
|
216
|
-
case on_error # rubocop:disable Style/MissingElse
|
220
|
+
case (on_error = @_on_error || :raise)
|
217
221
|
when :raise, nil then raise(error)
|
218
222
|
when :nullify then hash[key] = nil
|
219
223
|
when :ignore then nil
|
220
224
|
when Proc
|
221
225
|
key, value = on_error.call(error, obj, key, attribute, self.class)
|
222
226
|
hash[key] = value
|
227
|
+
else
|
228
|
+
# :nocov:
|
229
|
+
raise Alba::Error, 'Impossible path'
|
230
|
+
# :nocov:
|
223
231
|
end
|
224
232
|
end
|
225
233
|
|
@@ -233,11 +241,15 @@ module Alba
|
|
233
241
|
end
|
234
242
|
|
235
243
|
def _transform_key(inflector, key)
|
236
|
-
case @_transform_type
|
244
|
+
case @_transform_type
|
237
245
|
when :camel then inflector.camelize(key)
|
238
246
|
when :lower_camel then inflector.camelize_lower(key)
|
239
247
|
when :dash then inflector.dasherize(key)
|
240
248
|
when :snake then inflector.underscore(key)
|
249
|
+
else
|
250
|
+
# :nocov:
|
251
|
+
raise Alba::Error, "Unknown transform type: #{@_transform_type}"
|
252
|
+
# :nocov:
|
241
253
|
end
|
242
254
|
end
|
243
255
|
|
@@ -249,7 +261,9 @@ module Alba
|
|
249
261
|
when TypedAttribute then attribute.value(obj)
|
250
262
|
when NestedAttribute then attribute.value(object: obj, params: params, within: @within)
|
251
263
|
when ConditionalAttribute then attribute.with_passing_condition(resource: self, object: obj) { |attr| fetch_attribute(obj, key, attr) }
|
264
|
+
# :nocov:
|
252
265
|
else raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
|
266
|
+
# :nocov:
|
253
267
|
end
|
254
268
|
value.nil? && nil_handler ? instance_exec(obj, key, attribute, &nil_handler) : value
|
255
269
|
end
|
@@ -266,11 +280,7 @@ module Alba
|
|
266
280
|
|
267
281
|
def _fetch_attribute_from_resource_first(obj, attribute)
|
268
282
|
if @_resource_methods.include?(attribute)
|
269
|
-
|
270
|
-
__send__(attribute, obj)
|
271
|
-
rescue NoMethodError
|
272
|
-
obj.__send__(attribute)
|
273
|
-
end
|
283
|
+
__send__(attribute, obj)
|
274
284
|
else
|
275
285
|
obj.__send__(attribute)
|
276
286
|
end
|
@@ -445,8 +455,8 @@ module Alba
|
|
445
455
|
end
|
446
456
|
|
447
457
|
# Set metadata
|
448
|
-
def meta(&block)
|
449
|
-
@_meta = block
|
458
|
+
def meta(key = :meta, &block)
|
459
|
+
@_meta = [key, block]
|
450
460
|
end
|
451
461
|
|
452
462
|
# Set layout
|
@@ -476,6 +486,26 @@ module Alba
|
|
476
486
|
@_key_transformation_cascade = cascade
|
477
487
|
end
|
478
488
|
|
489
|
+
# Transform keys as specified type AFTER the class is defined
|
490
|
+
# Note that this is an experimental API and may be removed/changed
|
491
|
+
#
|
492
|
+
# @see #transform_keys
|
493
|
+
def transform_keys!(type)
|
494
|
+
dup.class_eval do
|
495
|
+
transform_keys(type, root: @_transforming_root_key, cascade: @_key_transformation_cascade)
|
496
|
+
|
497
|
+
if @_key_transformation_cascade
|
498
|
+
# We need to update key transformation of associations and nested attributes
|
499
|
+
@_attributes.each_value do |attr|
|
500
|
+
next unless attr.is_a?(Association) || attr.is_a?(NestedAttribute)
|
501
|
+
|
502
|
+
attr.key_transformation = type
|
503
|
+
end
|
504
|
+
end
|
505
|
+
self # Return the new class
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
479
509
|
# Sets key for collection serialization
|
480
510
|
#
|
481
511
|
# @param key [String, Symbol]
|
data/lib/alba/version.rb
CHANGED
data/lib/alba.rb
CHANGED
@@ -114,14 +114,11 @@ module Alba
|
|
114
114
|
raise Alba::Error, 'Inference is disabled so Alba cannot infer resource name. Set inflector before use.' unless Alba.inflector
|
115
115
|
|
116
116
|
const_parent = nesting.nil? ? Object : Object.const_get(nesting)
|
117
|
-
# rubocop-performance 1.20.2 might resolve this
|
118
|
-
# rubocop:disable Performance/StringIdentifierArgument
|
119
117
|
begin
|
120
118
|
const_parent.const_get("#{inflector.classify(name)}Resource")
|
121
119
|
rescue NameError # Retry for serializer
|
122
120
|
const_parent.const_get("#{inflector.classify(name)}Serializer")
|
123
121
|
end
|
124
|
-
# rubocop:enable Performance/StringIdentifierArgument
|
125
122
|
end
|
126
123
|
|
127
124
|
# Configure Alba to symbolize keys
|
@@ -173,8 +170,6 @@ module Alba
|
|
173
170
|
register_default_types
|
174
171
|
end
|
175
172
|
|
176
|
-
private
|
177
|
-
|
178
173
|
# This method could be part of public API, but for now it's private
|
179
174
|
def resource_with(object, &block)
|
180
175
|
klass = block ? resource_class(&block) : infer_resource_class(object.class.name)
|
@@ -182,6 +177,8 @@ module Alba
|
|
182
177
|
klass.new(object)
|
183
178
|
end
|
184
179
|
|
180
|
+
private
|
181
|
+
|
185
182
|
def inflector_from(name_or_module)
|
186
183
|
case name_or_module
|
187
184
|
when nil then nil
|
@@ -242,11 +239,13 @@ module Alba
|
|
242
239
|
inflector
|
243
240
|
end
|
244
241
|
|
245
|
-
def register_default_types
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
242
|
+
def register_default_types
|
243
|
+
[String, :String].each do |t|
|
244
|
+
register_type(t, check: ->(obj) { obj.is_a?(String) }, converter: lambda(&:to_s))
|
245
|
+
end
|
246
|
+
[Integer, :Integer].each do |t|
|
247
|
+
register_type(t, check: ->(obj) { obj.is_a?(Integer) }, converter: ->(obj) { Integer(obj) })
|
248
|
+
end
|
250
249
|
register_type(:Boolean, check: ->(obj) { [true, false].include?(obj) }, converter: ->(obj) { !!obj })
|
251
250
|
end
|
252
251
|
end
|
data/script/perf_check.rb
CHANGED
@@ -2,74 +2,7 @@
|
|
2
2
|
# Fetch Alba from local, otherwise fetch latest from RubyGems
|
3
3
|
# exit(status)
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
require "bundler/inline"
|
8
|
-
|
9
|
-
gemfile(true) do
|
10
|
-
source "https://rubygems.org"
|
11
|
-
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
12
|
-
|
13
|
-
gem "activerecord", "~> 6.1.3"
|
14
|
-
gem "alba", path: '../'
|
15
|
-
gem "benchmark-ips"
|
16
|
-
gem "blueprinter"
|
17
|
-
gem "jbuilder"
|
18
|
-
gem "multi_json"
|
19
|
-
gem "oj"
|
20
|
-
gem "sqlite3"
|
21
|
-
end
|
22
|
-
|
23
|
-
# --- Test data model setup ---
|
24
|
-
|
25
|
-
require "active_record"
|
26
|
-
require "oj"
|
27
|
-
require "sqlite3"
|
28
|
-
Oj.optimize_rails
|
29
|
-
|
30
|
-
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
|
31
|
-
|
32
|
-
ActiveRecord::Schema.define do
|
33
|
-
create_table :posts, force: true do |t|
|
34
|
-
t.string :body
|
35
|
-
end
|
36
|
-
|
37
|
-
create_table :comments, force: true do |t|
|
38
|
-
t.integer :post_id
|
39
|
-
t.string :body
|
40
|
-
t.integer :commenter_id
|
41
|
-
end
|
42
|
-
|
43
|
-
create_table :users, force: true do |t|
|
44
|
-
t.string :name
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
class Post < ActiveRecord::Base
|
49
|
-
has_many :comments
|
50
|
-
has_many :commenters, through: :comments, class_name: 'User', source: :commenter
|
51
|
-
|
52
|
-
def attributes
|
53
|
-
{id: nil, body: nil, commenter_names: commenter_names}
|
54
|
-
end
|
55
|
-
|
56
|
-
def commenter_names
|
57
|
-
commenters.pluck(:name)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
class Comment < ActiveRecord::Base
|
62
|
-
belongs_to :post
|
63
|
-
belongs_to :commenter, class_name: 'User'
|
64
|
-
|
65
|
-
def attributes
|
66
|
-
{id: nil, body: nil}
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
class User < ActiveRecord::Base
|
71
|
-
has_many :comments
|
72
|
-
end
|
5
|
+
require_relative '../benchmark/prep'
|
73
6
|
|
74
7
|
# --- Alba serializers ---
|
75
8
|
|
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: 3.0
|
4
|
+
version: 3.2.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:
|
11
|
+
date: 2024-06-21 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.
|
@@ -94,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
94
|
- !ruby/object:Gem::Version
|
95
95
|
version: '0'
|
96
96
|
requirements: []
|
97
|
-
rubygems_version: 3.
|
97
|
+
rubygems_version: 3.5.11
|
98
98
|
signing_key:
|
99
99
|
specification_version: 4
|
100
100
|
summary: Alba is the fastest JSON serializer for Ruby.
|