alba 3.10.0 → 3.11.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: 12a2e005c86ae86d746947d98732a5931340e378e0557a34de97d0eee533dff1
4
- data.tar.gz: 409d8d97cd7313a17d869a7504aa596b5e8e8bb594136cb24ab658408c609ab9
3
+ metadata.gz: 18b9be1d51ec5b7bed2d31fc59904a57c8dc7f4e24eb1a7cb6364a42ec8a27ec
4
+ data.tar.gz: 4112d0ace0fda10a9e6aee754e2b056b6488924fddbd5f12040652a2fcbbda75
5
5
  SHA512:
6
- metadata.gz: 68e629879e301c3a92dc7c903094739a28dd019d4b1ddcd5fe8606b9c0419e3409e921483bc8b81b4c399bb375a3658a08ef1608d3da010e55f23f0d5b665a00
7
- data.tar.gz: 8fba53a8294646354e47afd9a20afc9c48d9d3878df43e653ee09c7de98ab04604168edb19fb30d14430e641069c485d1151b371dc48ac96277ed1795f88d0c5
6
+ metadata.gz: 7bee8581bad84469254b1282b3c81bdaf493be4f2c5ee1b31747b16f2908f4e2639d783ac9fa7e6f345b361117512f0ce4b208a81a2f000e05455d3bc875b08b
7
+ data.tar.gz: 6939052cd6b8ea09f23df209874c858aadc032193a0ab5729bd04d33bbf7d24e3223cc642494a0095ca4a8f7a4b182fdd4cbd4e9781c1d4689e205953c2ca2e7
data/CHANGELOG.md CHANGED
@@ -6,6 +6,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## 3.11.0 2026-07-25
10
+
11
+ ### Added
12
+
13
+ - Rails: `serialize` and `render_serialized_json` now takes `params` [#509](https://github.com/okuramasafumi/alba/pull/509)
14
+ - Add `Alba.non_collection_types` for declaring Enumerable classes that should not be treated as collections [#532](https://github.com/okuramasafumi/alba/pull/532)
15
+ - Add default superclass configuration [#547](https://github.com/okuramasafumi/alba/pull/547)
16
+
17
+ ### Fixed
18
+
19
+ - Trait redefining base-level attribute causes wrong result with multiple traits [#498](https://github.com/okuramasafumi/alba/issues/498) and [#502](https://github.com/okuramasafumi/alba/pull/502)
20
+ - Thank you for reporting this issue, @yukihiro-numata
21
+ - Pass params to resource in serialize when with is given [#524](https://github.com/okuramasafumi/alba/pull/524)
22
+ - Thank you, @JohnnyKei
23
+ - `Alba.serialize(single, with: SomeResource)` crash
24
+ - helpers are now available in NestedAttribute [#503](https://github.com/okuramasafumi/alba/pull/503)
25
+
26
+ ### Changed
27
+
28
+ - More strict types
29
+
9
30
  ## 3.10.0 2025-11-11
10
31
 
11
32
  ### Added
data/README.md CHANGED
@@ -56,6 +56,12 @@ UserResource.new(user).serialize
56
56
 
57
57
  Seems useful? Continue reading!
58
58
 
59
+ Pro tip: You can find more examples in separate Ruby files under "examples" directory.
60
+
61
+ ### Coming from ActiveModelSerializers(AMS)?
62
+
63
+ If you are using ActiveModelSerializers and interested in Alba, we have an automatic converter! [alba_migration](https://github.com/ShoheiMitani/alba_migration) gem from [ShoheiMitani](https://github.com/ShoheiMitani) "provides a command-line tool to convert AMS serializer classes to Alba resource classes, making your migration process easier and less error-prone."
64
+
59
65
  ## Discussions
60
66
 
61
67
  Alba uses [GitHub Discussions](https://github.com/okuramasafumi/alba/discussions) to openly discuss the project.
@@ -100,6 +106,7 @@ While Alba's core is simple, it provides additional features when you need them.
100
106
  - Well tested, the test coverage is 99%
101
107
  - Well maintained, getting frequent update and new releases (see [version history](https://rubygems.org/gems/alba/versions))
102
108
 
109
+
103
110
  ## Comparison with other serializers
104
111
 
105
112
  Alba aims to provide a well-balanced combination of simplicity, performance, and features. Here's how it compares to other popular Ruby JSON serializers:
@@ -138,7 +145,7 @@ Or install it yourself as:
138
145
 
139
146
  ## Supported Ruby versions
140
147
 
141
- Alba supports CRuby 3.0 and higher and latest JRuby and TruffleRuby.
148
+ Alba works with CRuby 3.0 and higher and latest JRuby and TruffleRuby. However, Ruby 3.0 is not actively tested, so use it at your own risk.
142
149
 
143
150
  ## Documentation
144
151
 
@@ -228,6 +235,20 @@ else
228
235
  end
229
236
  ```
230
237
 
238
+ #### Default superclass configuration
239
+
240
+ You can set the default superclass for resource classes created for Nested Attributes and Traits.
241
+
242
+ ```ruby
243
+ Alba.default_superclass = MyBaseSerializer
244
+ ```
245
+
246
+ Note that in some environments like Rails applications, the class object is not loaded in the initializer phase. You can also set `String`.
247
+
248
+ ```ruby
249
+ Alba.default_superclass = 'MyBaseSerializer'
250
+ ```
251
+
231
252
  ### Naming
232
253
 
233
254
  Alba tries to infer resource name from class name like the following.
@@ -349,7 +370,7 @@ end
349
370
  class FooResource
350
371
  include Alba::Resource
351
372
 
352
- attributes :bar
373
+ attributes :format
353
374
 
354
375
  # Here, `format` method is available
355
376
  end
@@ -1921,16 +1942,18 @@ Don't forget calling `super` in this way.
1921
1942
 
1922
1943
  ### Treating specific classes as non-collection
1923
1944
 
1924
- Sometimes we need to serialize an object that's `Enumerable` but not a collection. By default, Alba treats `Hash`, `Range` and `Struct` as non-collection object, but if we want to add some classes to this list, we can override `Alba.collection?` method like following:
1945
+ Sometimes we need to serialize an object that's `Enumerable` but not a collection. By default, Alba treats `Hash`, `Range` and `Struct` as non-collection objects. You can add additional classes to this list via `Alba.non_collection_types`:
1925
1946
 
1926
1947
  ```ruby
1927
- Alba.singleton_class.prepend(
1928
- Module.new do
1929
- def collection?(object)
1930
- super && !object.is_a?(SomeClass)
1931
- end
1932
- end
1933
- )
1948
+ Alba.non_collection_types << Stripe::StripeObject
1949
+ Alba.non_collection_types << MyEnumerableEntity
1950
+ ```
1951
+
1952
+ You can inspect the current list of excluded types (including the defaults) at any time:
1953
+
1954
+ ```ruby
1955
+ Alba.non_collection_types
1956
+ # => [Struct, Range, Hash, Stripe::StripeObject, MyEnumerableEntity]
1934
1957
  ```
1935
1958
 
1936
1959
  ### Adding indexes to `many` association
@@ -101,7 +101,7 @@ module Alba
101
101
 
102
102
  def assign_resource(nesting, key_transformation, block, helper)
103
103
  @resource = if block
104
- charged_resource_class(helper, key_transformation, block)
104
+ Alba.resource_class(helper: helper, key_transformation: key_transformation, &block)
105
105
  elsif Alba.inflector
106
106
  Alba.infer_resource_class(@name, nesting: nesting)
107
107
  else
@@ -109,14 +109,6 @@ module Alba
109
109
  end
110
110
  end
111
111
 
112
- def charged_resource_class(helper, key_transformation, block)
113
- klass = Alba.resource_class
114
- klass.helper(helper) if helper
115
- klass.transform_keys(key_transformation)
116
- klass.class_eval(&block)
117
- klass
118
- end
119
-
120
112
  def to_h_with_each_resource(object, within, params)
121
113
  object.map do |item|
122
114
  @resource.call(item).new(item, within: within, params: params, with_traits: @with_traits).to_h
@@ -7,9 +7,11 @@ module Alba
7
7
  # Setter for key_transformation, used when it's changed after class definition
8
8
  attr_writer :key_transformation
9
9
 
10
+ # @param klass [Class<Alba::Resource>] the parent for this nested attribute
10
11
  # @param key_transformation [Symbol] determines how to transform keys
11
12
  # @param block [Proc] class body
12
- def initialize(key_transformation: :none, &block)
13
+ def initialize(klass:, key_transformation: :none, &block)
14
+ @klass = klass
13
15
  @key_transformation = key_transformation
14
16
  @block = block
15
17
  end
@@ -17,13 +19,13 @@ module Alba
17
19
  # @param object [Object] the object being serialized
18
20
  # @param params [Hash] params Hash inherited from Resource
19
21
  # @param within [Object, nil, false, true] determines what associations to be serialized. If not set, it serializes all associations.
20
- # @param select [Method] select method object from its origin
21
22
  # @return [Hash] hash serialized from running the class body in the object
22
- def value(object:, params:, within:, select: nil)
23
- resource_class = Alba.resource_class
23
+ def value(object:, params:, within:)
24
+ resource_class = Class.new(@klass)
25
+ resource_class.instance_variable_set(:@_attributes, {}) # reset
24
26
  resource_class.transform_keys(@key_transformation)
25
27
  resource_class.class_eval(&@block)
26
- resource_class.new(object, params: params, within: within, select: select).serializable_hash
28
+ resource_class.new(object, params: params, within: within).serializable_hash
27
29
  end
28
30
  end
29
31
  end
data/lib/alba/railtie.rb CHANGED
@@ -7,13 +7,13 @@ module Alba
7
7
  Alba.inflector = :active_support
8
8
 
9
9
  ActiveSupport.on_load(:action_controller) do
10
- define_method(:serialize) do |obj, with: nil, root_key: nil, meta: {}, &block|
11
- resource = with.nil? ? Alba.resource_for(obj, &block) : with.new(obj)
10
+ define_method(:serialize) do |obj, params: {}, with: nil, root_key: nil, meta: {}, &block|
11
+ resource = with.nil? ? Alba.resource_for(obj, params: params, &block) : with.new(obj, params: params)
12
12
  resource.to_json(root_key: root_key, meta: meta)
13
13
  end
14
14
 
15
- define_method(:render_serialized_json) do |obj, with: nil, root_key: nil, meta: {}, &block|
16
- json = with.nil? ? Alba.resource_for(obj, &block) : with.new(obj)
15
+ define_method(:render_serialized_json) do |obj, params: {}, with: nil, root_key: nil, meta: {}, &block|
16
+ json = with.nil? ? Alba.resource_for(obj, params: params, &block) : with.new(obj, params: params)
17
17
  render json: json.to_json(root_key: root_key, meta: meta)
18
18
  end
19
19
  end
data/lib/alba/resource.rb CHANGED
@@ -51,15 +51,13 @@ module Alba
51
51
  # @param within [Alba::WITHIN_DEFAULT, Hash, Array, nil, false, true]
52
52
  # determines what associations to be serialized. If not set, it serializes all associations.
53
53
  # @param with_traits [Symbol, Array<Symbol>, nil] specified traits
54
- # @param select [Method] select method object used with `nested_attribute` and `trait`
54
+ # @param select [Method] DEPRECATED noop
55
55
  def initialize(object, params: EMPTY_HASH, within: WITHIN_DEFAULT, with_traits: nil, select: nil)
56
56
  @object = object
57
57
  @params = params
58
58
  @within = within
59
59
  @with_traits = with_traits
60
- # select override to share the same method with `trait` and `nested_attribute`
61
- # Trait and NestedAttribute generates anonymous class so it checks if it's anonymous class to prevent accidental overriding
62
- self.class.define_method(:select, &select) if select && self.class.name.nil?
60
+ warn '`select` keyword for Alba::Resource is deprecated', category: :deprecated, uplevel: 1 if select
63
61
  _setup
64
62
  end
65
63
 
@@ -122,9 +120,10 @@ module Alba
122
120
  Array(@with_traits).each_with_object({}) do |trait, hash|
123
121
  body = @_traits.fetch(trait) { raise Alba::Error, "Trait not found: #{trait}" }
124
122
  resource_class = Class.new(self.class)
123
+ resource_class.instance_variable_set(:@_attributes, {})
125
124
  resource_class.class_eval(&body)
126
125
  resource_class.transform_keys(@_transform_type) unless @_transform_type == :none
127
- hash.merge!(resource_class.new(obj, params: params, within: @within, select: method(:select)).serializable_hash)
126
+ hash.merge!(resource_class.new(obj, params: params, within: @within).serializable_hash)
128
127
  end
129
128
  end
130
129
 
@@ -294,7 +293,7 @@ module Alba
294
293
  when Proc then instance_exec(obj, &attribute)
295
294
  when Alba::Association then yield_if_within(attribute.name.to_sym) { |within| attribute.to_h(obj, params: params, within: within) }
296
295
  when TypedAttribute then attribute.value(object: obj) { |attr| fetch_attribute(obj, key, attr) }
297
- when NestedAttribute then attribute.value(object: obj, params: params, within: @within, select: method(:select))
296
+ when NestedAttribute then attribute.value(object: obj, params: params, within: @within)
298
297
  when ConditionalAttribute then attribute.with_passing_condition(resource: self, object: obj) { |attr| fetch_attribute(obj, key, attr) }
299
298
  # :nocov:
300
299
  else raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
@@ -488,7 +487,7 @@ module Alba
488
487
  raise ArgumentError, 'No block given in attribute method' unless block
489
488
 
490
489
  key_transformation = @_key_transformation_cascade ? @_transform_type : :none
491
- attribute = NestedAttribute.new(key_transformation: key_transformation, &block)
490
+ attribute = NestedAttribute.new(klass: self, key_transformation: key_transformation, &block)
492
491
  @_attributes[name.to_sym] = options[:if] ? ConditionalAttribute.new(body: attribute, condition: options[:if]) : attribute
493
492
  end
494
493
  alias nested nested_attribute
@@ -633,7 +632,8 @@ module Alba
633
632
  # @return [void]
634
633
  def helper(mod = @_helper || Module.new, &block)
635
634
  mod.module_eval(&block) if block
636
- extend mod
635
+ include(mod)
636
+ extend(mod)
637
637
 
638
638
  @_helper = mod
639
639
  end
data/lib/alba/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alba
4
- VERSION = '3.10.0'
4
+ VERSION = '3.11.0'
5
5
  end
data/lib/alba.rb CHANGED
@@ -16,6 +16,16 @@ module Alba
16
16
  # Getter for inflector, a module responsible for inflecting strings
17
17
  attr_reader :inflector
18
18
 
19
+ # @return [Array<Class>] classes that include Enumerable but should not be treated as collections
20
+ attr_reader :non_collection_types
21
+
22
+ # Set the default superclass for resource classes created with {.resource_class}
23
+ #
24
+ # @param value [Class, String, Symbol] the default superclass
25
+ # @example
26
+ # Alba.default_superclass = '::MyApp::BaseResource'
27
+ attr_writer :default_superclass
28
+
19
29
  # Set the backend, which actually serializes object into JSON
20
30
  #
21
31
  # @param backend [#to_sym, nil] the name of the backend
@@ -54,7 +64,7 @@ module Alba
54
64
  h = hashify_collection(object, with, root_key, &block)
55
65
  Alba.encoder.call(h)
56
66
  else
57
- resource = resource_for(object, &block)
67
+ resource = resource_for(object, with: with, &block)
58
68
  resource.serialize(root_key: root_key)
59
69
  end
60
70
  end
@@ -73,17 +83,18 @@ module Alba
73
83
  if collection?(object)
74
84
  hashify_collection(object, with, root_key, &block)
75
85
  else
76
- resource = resource_for(object, &block)
86
+ resource = resource_for(object, with: with, &block)
77
87
  resource.as_json(root_key: root_key)
78
88
  end
79
89
  end
80
90
 
81
91
  # Detect if object is a collection or not.
82
- # When object is a Struct or a Range, it's Enumerable but not a collection
92
+ # Types in {.non_collection_types} (default: Struct, Range, Hash) are
93
+ # considered non-collection even if they include Enumerable.
83
94
  #
84
95
  # @api private
85
96
  def collection?(object)
86
- object.is_a?(Enumerable) && !object.is_a?(Struct) && !object.is_a?(Range) && !object.is_a?(Hash)
97
+ object.is_a?(Enumerable) && @non_collection_types.none? { |type| object.is_a?(type) }
87
98
  end
88
99
 
89
100
  # Enable inference for key and resource name
@@ -124,11 +135,15 @@ module Alba
124
135
  reset_transform_keys
125
136
  end
126
137
 
138
+ # @param helper [Module] helper module to include
139
+ # @param key_transformation [Symbol] key transformation type
127
140
  # @param block [Block] resource body
128
141
  # @return [Class<Alba::Resource>] resource class
129
- def resource_class(&block)
130
- klass = Class.new
142
+ def resource_class(helper: nil, key_transformation: :none, &block)
143
+ klass = Class.new(resolved_default_superclass)
131
144
  klass.include(Alba::Resource)
145
+ klass.helper(helper) if helper
146
+ klass.transform_keys(key_transformation)
132
147
  klass.class_eval(&block) if block
133
148
  klass
134
149
  end
@@ -219,6 +234,8 @@ module Alba
219
234
  @_on_error = :raise
220
235
  @_on_nil = nil
221
236
  @types = {}
237
+ @non_collection_types = [Struct, Range, Hash]
238
+ @default_superclass = ::Object
222
239
  reset_transform_keys
223
240
  register_default_types
224
241
  end
@@ -234,6 +251,7 @@ module Alba
234
251
  # Otherwise, it behaves depending on `with` argument
235
252
  #
236
253
  # @param object [Object] the object whose class name is used for inferring resource class
254
+ # @param params [Hash] user-given Hash for arbitrary data
237
255
  # @param with [:inference, Proc, Class<Alba::Resource>] determines how to get resource class for `object`
238
256
  # When it's `:inference`, it infers resource class from `object`'s class name
239
257
  # When it's a Proc, it calls the Proc with `object` as an argument
@@ -241,13 +259,13 @@ module Alba
241
259
  # Otherwise, it raises an ArgumentError
242
260
  # @return [Alba::Resource] resource class with `object` as its target object
243
261
  # @raise [ArgumentError] if `with` argument is not one of `:inference`, Proc or Class
244
- def resource_for(object, with: :inference, &block)
245
- _resource_for(object, with: with, &block)
262
+ def resource_for(object, params: {}, with: :inference, &block)
263
+ _resource_for(object, params: params, with: with, &block)
246
264
  end
247
265
 
248
266
  private
249
267
 
250
- def _resource_for(object, with: :inference, &block) # rubocop:disable Metrics/MethodLength
268
+ def _resource_for(object, params: {}, with: :inference, &block) # rubocop:disable Metrics/MethodLength
251
269
  klass = if block
252
270
  resource_class(&block)
253
271
  else
@@ -259,7 +277,7 @@ module Alba
259
277
  end
260
278
  end
261
279
 
262
- klass.new(object)
280
+ klass.new(object, params: params)
263
281
  end
264
282
 
265
283
  def inflector_from(name_or_module)
@@ -336,6 +354,10 @@ module Alba
336
354
  @_transformed_keys = Hash.new { |h, k| h[k] = {} }
337
355
  end
338
356
 
357
+ def resolved_default_superclass
358
+ @default_superclass.is_a?(Class) ? @default_superclass : Object.const_get(@default_superclass.to_s)
359
+ end
360
+
339
361
  def register_default_types # rubocop:disable Metrics/AbcSize
340
362
  [String, :String].each do |t|
341
363
  register_type(t, check: ->(obj) { obj.is_a?(String) }, converter: lambda(&:to_s))
@@ -345,7 +367,7 @@ module Alba
345
367
  end
346
368
  register_type(:Boolean, check: ->(obj) { [true, false].include?(obj) }, converter: ->(obj) { !!obj })
347
369
  [String, Integer].each do |t|
348
- register_type(:"ArrayOf#{t}", check: ->(d) { d.is_a?(Array) && d.all? { _1.is_a?(t) } })
370
+ register_type(:"ArrayOf#{t}", check: ->(d) { d.is_a?(Array) && d.all?(t) })
349
371
  end
350
372
  end
351
373
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alba
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.10.0
4
+ version: 3.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OKURA Masafumi