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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a2aa4a1fc6859ca700fefbe63af11657031743b9ad6c4ec9f97c7d7eac2cef7
4
- data.tar.gz: 8703816ac23d30e432053c6836a8ed3e517f318a9924ae4bf67e5b3cff79f666
3
+ metadata.gz: b3b0683f5d43a9e7ce7eaea591c2a67267f56d7327a045ab0e2da081256a4543
4
+ data.tar.gz: 0dd80fd3c618fe35bc7b727f01c6ce8c9357fe1b0d303e2a756daf245f6d7e50
5
5
  SHA512:
6
- metadata.gz: abd511841f6100e00e53687a3f4b7a58a04645090f3e7204edec2c7f8ea438d70e9014b576e56aae302db293796ccd793aa4d976b4241f91f25a1cf15d0e19e6
7
- data.tar.gz: bf76202cdf514b5134b5a3f1e17b17cca78dbb9c9f1029f562d9d4ccc992c9d8aaeefbf01d1c45d1f4aff658d53a21e24b0caa279b6852e1516765c6ce62d99d
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@v2
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@v2
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@v2
70
+ uses: github/codeql-action/analyze@v3
@@ -1,6 +1,10 @@
1
1
  name: CI
2
2
 
3
- on: [push,pull_request]
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] # TODO: Add jruby after the error is resolved
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@v3
36
+ uses: codecov/codecov-action@v4
33
37
  with:
34
38
  files: ./coverage/coverage.xml
39
+ token: ${{ secrets.CODECOV_TOKEN }}
@@ -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: ${{ matrix.ruby }}
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.0.0/),
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', '>= 0.79.0', require: false # For lint
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', '>= 0.25.0', require: false # For lint
16
- gem 'rubocop-performance', '>= 1.15.0', require: false # For lint
17
- gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
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, 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).
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
- `helper`
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 "bundler/gem_tasks"
2
- require "rake/testtask"
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
3
 
4
- ENV["BUNDLE_GEMFILE"] = File.expand_path("Gemfile") if ENV["BUNDLE_GEMFILE"] == File.expand_path("Gemfile") || ENV["BUNDLE_GEMFILE"].empty? || ENV["BUNDLE_GEMFILE"].nil?
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 << "test"
8
- t.libs << "lib"
9
- file_list = ENV["BUNDLE_GEMFILE"] == File.expand_path("Gemfile") ? FileList["test/**/*_test.rb"] : FileList["test/dependencies/test_dependencies.rb"]
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 :default => :test
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", "6.1.3"
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.
@@ -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) # rubocop:disable Metrics/MethodLength
73
+ def assign_resource(nesting, key_transformation, block, helper)
69
74
  @resource = if block
70
- klass = Alba.resource_class
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
- raise ArgumentError, 'File layout must be a String representing filename' unless file.is_a?(String)
15
+ @body = if file
16
+ raise ArgumentError, 'File layout must be a String representing filename' unless file.is_a?(String)
17
17
 
18
- @body = file
19
- elsif inline
20
- raise ArgumentError, 'Inline layout must be a Proc returning a Hash or a String' unless inline.is_a?(Proc)
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
- @body = inline
23
- else
24
- raise ArgumentError, 'Layout must be either String or Proc'
25
- end
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
- base.define_method(:encode, Alba.encoder)
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
- _to_json(root_key, meta, options)
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
- metadata = @_meta ? instance_eval(&@_meta).merge(meta) : meta
121
- hash[:meta] = metadata
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 # rubocop:disable Style/MissingElse
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
- begin
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
@@ -1,3 +1,3 @@
1
1
  module Alba
2
- VERSION = '3.0.3'.freeze
2
+ VERSION = '3.2.0'.freeze
3
3
  end
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 # rubocop:disable Metrics/AbcSize
246
- register_type(:String, check: ->(obj) { obj.is_a?(String) }, converter: ->(obj) { obj.to_s })
247
- register_type(String, check: ->(obj) { obj.is_a?(String) }, converter: ->(obj) { obj.to_s })
248
- register_type(:Integer, check: ->(obj) { obj.is_a?(Integer) }, converter: ->(obj) { Integer(obj) })
249
- register_type(Integer, check: ->(obj) { obj.is_a?(Integer) }, converter: ->(obj) { Integer(obj) })
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
- # --- Bundle dependencies ---
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.3
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: 2023-12-25 00:00:00.000000000 Z
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.4.22
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.