alba 3.9.0 → 3.10.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: 9f883beafbee55bdfc1bb9b55e821c6df5b167e5311cbeced743ae7c60de5657
4
- data.tar.gz: e9c031cd63e51f160f1ec1f1b42ac8676b0ff172ff2438f549ab83d38d848817
3
+ metadata.gz: 12a2e005c86ae86d746947d98732a5931340e378e0557a34de97d0eee533dff1
4
+ data.tar.gz: 409d8d97cd7313a17d869a7504aa596b5e8e8bb594136cb24ab658408c609ab9
5
5
  SHA512:
6
- metadata.gz: 73b22da7dc5d360297838b326c3f130e18027b9fc203e3bdec48c13f80f4105a5fa019304a947dbc85aea40ee94e796cb20ba4c7c51f9a8c3cc02d29336f80f9
7
- data.tar.gz: 2d82dfd6188126db9726982440465e9bc4cf6165e2eaf2c51f0d729eee1e8fa2d2c54b5d8aafce710979ba67be437f4bc152dc1e91352cea421925b32068c73f
6
+ metadata.gz: 68e629879e301c3a92dc7c903094739a28dd019d4b1ddcd5fe8606b9c0419e3409e921483bc8b81b4c399bb375a3658a08ef1608d3da010e55f23f0d5b665a00
7
+ data.tar.gz: 8fba53a8294646354e47afd9a20afc9c48d9d3878df43e653ee09c7de98ab04604168edb19fb30d14430e641069c485d1151b371dc48ac96277ed1795f88d0c5
data/CHANGELOG.md CHANGED
@@ -6,6 +6,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## 3.10.0 2025-11-11
10
+
11
+ ### Added
12
+
13
+ - Support type validation and coercion for a single attribute method [#470](https://github.com/okuramasafumi/alba/pull/470)
14
+ - Thank you [@denblackstache](https://github.com/denblackstache) for requesting the feature
15
+ - Fix hash object resource methods [#475](https://github.com/okuramasafumi/alba/pull/475)
16
+ - Thank you, [@jkmcrg](https://github.com/jkmcrg)
17
+
18
+ ## 3.9.1 2025-09-27
19
+
20
+ ### Added
21
+
22
+ - RBS types
23
+ - This change itself is just an addition, but not feature addition. Here I picked `3.9.1`, but I don't know how semver works in this case...
24
+
9
25
  ## 3.9.0 2025-08-14
10
26
 
11
27
  ### Added
@@ -22,7 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
22
38
  ### Added
23
39
 
24
40
  - add support for ignoring key when using proc for on_error [#450](https://github.com/okuramasafumi/alba/pull/450)
25
- - Thank you, @mainmethod
41
+ - Thank you, @mainmethod
26
42
 
27
43
  ### Note
28
44
 
@@ -182,7 +198,7 @@ This change is supposed to be released as 3.8.0 with the change in 3.7.4, but I
182
198
 
183
199
  ## [2.4.1] 2023-08-02
184
200
 
185
- #### Fixed
201
+ ### Fixed
186
202
 
187
203
  - Fix the bug of resource name inference for classes whose name end with "Serializer" [No PR](https://github.com/okuramasafumi/alba/commit/1695af4351981725231fd071aaef5b2e4174fb26)
188
204
 
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/alba.svg)](https://badge.fury.io/rb/alba)
4
4
  [![CI](https://github.com/okuramasafumi/alba/actions/workflows/main.yml/badge.svg)](https://github.com/okuramasafumi/alba/actions/workflows/main.yml)
5
5
  [![codecov](https://codecov.io/gh/okuramasafumi/alba/branch/main/graph/badge.svg?token=3D3HEZ5OXT)](https://codecov.io/gh/okuramasafumi/alba)
6
- [![Maintainability](https://qlty.sh/gh/okuramasafumi/projects/alba/maintainability.svg)](https://qlty.sh/gh/okuramasafumi/projects/alba)
6
+ [![Codacy Badge](https://app.codacy.com/project/badge/Grade/acf6a96689c349ecbb806db07fa6948a)](https://app.codacy.com/gh/okuramasafumi/alba/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
7
7
  ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/okuramasafumi/alba)
8
8
  ![GitHub](https://img.shields.io/github/license/okuramasafumi/alba)
9
9
  [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md)
@@ -100,6 +100,26 @@ While Alba's core is simple, it provides additional features when you need them.
100
100
  - Well tested, the test coverage is 99%
101
101
  - Well maintained, getting frequent update and new releases (see [version history](https://rubygems.org/gems/alba/versions))
102
102
 
103
+ ## Comparison with other serializers
104
+
105
+ Alba aims to provide a well-balanced combination of simplicity, performance, and features. Here's how it compares to other popular Ruby JSON serializers:
106
+
107
+ | Feature | Alba | [AMS](https://github.com/rails-api/active_model_serializers) | [Blueprinter](https://github.com/procore/blueprinter) | [JSONAPI::Serializer](https://github.com/jsonapi-serializer/jsonapi-serializer) | [JBuilder](https://github.com/rails/jbuilder) |
108
+ |---------|------|-----|-------------|---------------------|----------|
109
+ | **Dependencies** | [None](https://github.com/okuramasafumi/alba#other-reasons) | ActiveSupport | Minimal | Minimal | Rails |
110
+ | **JSON:API Compliance** | Manual | [Planned](https://github.com/rails-api/active_model_serializers#status-of-ams) | Via extension | [Full support](https://github.com/jsonapi-serializer/jsonapi-serializer#json-api-serializer) | Manual |
111
+ | **Caching** | [Not built-in](https://github.com/okuramasafumi/alba#caching) | Yes | Via extension | [Supported](https://github.com/jsonapi-serializer/jsonapi-serializer) | [Fragment caching](https://github.com/rails/jbuilder#caching) |
112
+ | **Key Transformation** | [Yes](https://github.com/okuramasafumi/alba#key-transformation) | Yes | [Yes](https://github.com/procore/blueprinter) | [Yes](https://goithub.com/jsonapi-serializer/jsonapi-serializer) | [Yes](https://github.com/rails/jbuilder#key-formatting) |
113
+ | **Conditional Attributes** | [Yes](https://github.com/okuramasafumi/alba#conditional-attributes) | Yes | [Yes](https://github.com/procore/blueprinter) | [Yes](https://github.com/jsonapi-serializer/jsonapi-serializer) | [Yes](https://github.com/rails/jbuilder) |
114
+ | **Type Validation & Coercion** | [Yes](https://github.com/okuramasafumi/alba#types) | No | No | No | No |
115
+ | **Custom Type System** | [Yes](https://github.com/okuramasafumi/alba#custom-types) | No | No | No | No |
116
+ | **Nested Attributes** | [Yes](https://github.com/okuramasafumi/alba#nested-attribute) | No | No | No | Partial |
117
+ | **Circular Reference Control** | [Yes with `within`](https://github.com/okuramasafumi/alba#circular-associations-control) | Limited | No | No | No |
118
+ | **Error Handling Strategies** | [Flexible](https://github.com/okuramasafumi/alba#error-handling) | Limited | Limited | Basic | Basic |
119
+ | **Nil Handling** | [Yes](https://github.com/okuramasafumi/alba#nil-handling) | No | No | No | No |
120
+ | **Layout System** | [Yes](https://github.com/okuramasafumi/alba#layout) | No | No | No | No |
121
+ | **Maintenance Status** | Active | [Minimal](https://github.com/rails-api/active_model_serializers#status-of-ams) | Active | Active | Active |
122
+
103
123
  ## Installation
104
124
 
105
125
  Add this line to your application's Gemfile:
@@ -725,6 +745,10 @@ Alba.serialize(something)
725
745
 
726
746
  Although this might be useful sometimes, it's generally recommended to define a class for Resource. Defining a class is often more readable and more maintainable, and inline definitions cannot levarage the benefit of YJIT (it's the slowest with the benchmark YJIT enabled).
727
747
 
748
+ #### Alba.hashify
749
+
750
+ `Alba.hashify` is similar to `Alba.serialize`, but returns a Hash instead of JSON string.
751
+
728
752
  #### Inline definition for multiple root keys
729
753
 
730
754
  While Alba doesn't directly support multiple root keys, you can simulate it with `Alba.serialize`.
@@ -1974,6 +1998,23 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
1974
1998
 
1975
1999
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
1976
2000
 
2001
+ ### Type Checking
2002
+
2003
+ Alba uses RBS for type signatures and Steep for type checking.
2004
+ To run type checking:
2005
+
2006
+ ```bash
2007
+ # Install type checking dependencies (CRuby only)
2008
+ bundle install --with type
2009
+
2010
+ # Run all type checks
2011
+ bundle exec rake typecheck
2012
+
2013
+ # Or run individual checks
2014
+ bundle exec rake rbs # Validate RBS signatures
2015
+ bundle exec rake steep # Run Steep type checker
2016
+ ```
2017
+
1977
2018
  ## Contributing
1978
2019
 
1979
2020
  Thank you for begin interested in contributing to Alba! Please see [contributors guide](https://github.com/okuramasafumi/alba/blob/main/CONTRIBUTING.md) before start contributing. If you have any questions, please feel free to ask in [Discussions](https://github.com/okuramasafumi/alba/discussions).
data/lib/alba/resource.rb CHANGED
@@ -20,6 +20,9 @@ module Alba
20
20
  WITHIN_DEFAULT = Object.new.freeze
21
21
  private_constant :WITHIN_DEFAULT
22
22
 
23
+ EMPTY_HASH = {}.freeze
24
+ private_constant :EMPTY_HASH
25
+
23
26
  # `setup` method is meta-programmatically defined here for performance.
24
27
  # @api private
25
28
  def self.included(base) # rubocop:disable Metrics/MethodLength
@@ -45,10 +48,11 @@ module Alba
45
48
 
46
49
  # @param object [Object] the object to be serialized
47
50
  # @param params [Hash] user-given Hash for arbitrary data
48
- # @param within [Object, nil, false, true] determines what associations to be serialized. If not set, it serializes all associations.
51
+ # @param within [Alba::WITHIN_DEFAULT, Hash, Array, nil, false, true]
52
+ # determines what associations to be serialized. If not set, it serializes all associations.
49
53
  # @param with_traits [Symbol, Array<Symbol>, nil] specified traits
50
54
  # @param select [Method] select method object used with `nested_attribute` and `trait`
51
- def initialize(object, params: {}, within: WITHIN_DEFAULT, with_traits: nil, select: nil)
55
+ def initialize(object, params: EMPTY_HASH, within: WITHIN_DEFAULT, with_traits: nil, select: nil)
52
56
  @object = object
53
57
  @params = params
54
58
  @within = within
@@ -61,10 +65,10 @@ module Alba
61
65
 
62
66
  # Serialize object into JSON string
63
67
  #
64
- # @param root_key [Symbol, nil, true]
68
+ # @param root_key [Symbol, nil]
65
69
  # @param meta [Hash] metadata for this serialization
66
70
  # @return [String] serialized JSON string
67
- def serialize(root_key: nil, meta: {})
71
+ def serialize(root_key: nil, meta: EMPTY_HASH)
68
72
  serialize_with(as_json(root_key: root_key, meta: meta))
69
73
  end
70
74
 
@@ -73,7 +77,7 @@ module Alba
73
77
  #
74
78
  # @see #serialize
75
79
  # @see https://github.com/rails/rails/blob/7-0-stable/actionpack/lib/action_controller/metal/renderers.rb#L156
76
- def to_json(options = {}, root_key: nil, meta: {})
80
+ def to_json(options = EMPTY_HASH, root_key: nil, meta: EMPTY_HASH)
77
81
  confusing_keys = [:only, :except]
78
82
  confusing_options = options.keys.select { |k| confusing_keys.include?(k.to_sym) }
79
83
  unless confusing_options.empty?
@@ -88,10 +92,10 @@ module Alba
88
92
  # Returns a Hash corresponding {#serialize}
89
93
  #
90
94
  # @param _options [Hash] dummy parameter for Rails compatibility
91
- # @param root_key [Symbol, nil, true]
95
+ # @param root_key [Symbol, nil]
92
96
  # @param meta [Hash] metadata for this serialization
93
97
  # @return [Hash]
94
- def as_json(_options = {}, root_key: nil, meta: {})
98
+ def as_json(_options = EMPTY_HASH, root_key: nil, meta: EMPTY_HASH)
95
99
  key = root_key.nil? ? fetch_key : root_key
96
100
  key = Alba.regularize_key(key)
97
101
  if key && !key.empty?
@@ -289,7 +293,7 @@ module Alba
289
293
  when Symbol then fetch_attribute_from_object_and_resource(obj, attribute)
290
294
  when Proc then instance_exec(obj, &attribute)
291
295
  when Alba::Association then yield_if_within(attribute.name.to_sym) { |within| attribute.to_h(obj, params: params, within: within) }
292
- when TypedAttribute then attribute.value { |attr| fetch_attribute(obj, key, attr) }
296
+ when TypedAttribute then attribute.value(object: obj) { |attr| fetch_attribute(obj, key, attr) }
293
297
  when NestedAttribute then attribute.value(object: obj, params: params, within: @within, select: method(:select))
294
298
  when ConditionalAttribute then attribute.with_passing_condition(resource: self, object: obj) { |attr| fetch_attribute(obj, key, attr) }
295
299
  # :nocov:
@@ -307,7 +311,7 @@ module Alba
307
311
  return obj.fetch(attribute) if obj.is_a?(Hash)
308
312
 
309
313
  obj.__send__(attribute)
310
- rescue NoMethodError
314
+ rescue NoMethodError, KeyError
311
315
  __send__(attribute, obj)
312
316
  end
313
317
 
@@ -319,6 +323,8 @@ module Alba
319
323
  else
320
324
  obj.__send__(attribute)
321
325
  end
326
+ rescue KeyError
327
+ __send__(attribute, obj)
322
328
  end
323
329
 
324
330
  def nil_handler
@@ -398,11 +404,11 @@ module Alba
398
404
  end
399
405
  private :assign_attributes
400
406
 
401
- def assign_attributes_with_types(attrs_with_types, if_value)
407
+ def assign_attributes_with_types(attrs_with_types, if_value, &block)
402
408
  attrs_with_types.each do |attr_name, type_and_converter|
403
409
  attr_name = attr_name.to_sym
404
410
  type, type_converter = type_and_converter
405
- typed_attr = TypedAttribute.new(name: attr_name, type: type, converter: type_converter)
411
+ typed_attr = TypedAttribute.new(name: attr_name, type: type, converter: type_converter, &block)
406
412
  attr = if_value ? ConditionalAttribute.new(body: typed_attr, condition: if_value) : typed_attr
407
413
  @_attributes[attr_name] = attr
408
414
  end
@@ -412,15 +418,21 @@ module Alba
412
418
  # Set an attribute with the given block
413
419
  #
414
420
  # @param name [String, Symbol] key name
415
- # @param options [Hash<Symbol, Proc>]
416
- # @option options [Proc] if a condition to decide if this attribute should be serialized
421
+ # @param if [Proc] condition to decide if it should serialize these attributes
417
422
  # @param block [Block] the block called during serialization
418
423
  # @raise [ArgumentError] if block is absent
419
424
  # @return [void]
420
- def attribute(name, **options, &block)
425
+ def attribute(name = nil, if: nil, **name_with_type, &block)
426
+ if_value = binding.local_variable_get(:if)
421
427
  raise ArgumentError, 'No block given in attribute method' unless block
428
+ raise ArgumentError, 'You must specify either name or name with type' if name.nil? && name_with_type.empty?
422
429
 
423
- @_attributes[name.to_sym] = options[:if] ? ConditionalAttribute.new(body: block, condition: options[:if]) : block
430
+ if name.nil?
431
+ assign_attributes_with_types(name_with_type, if_value, &block)
432
+ else # Symbol
433
+ attr = if_value ? ConditionalAttribute.new(body: block, condition: if_value) : block
434
+ @_attributes[name.to_sym] = attr
435
+ end
424
436
  end
425
437
 
426
438
  # Set association
@@ -434,11 +446,13 @@ module Alba
434
446
  # @param with_traits [Symbol, Array<Symbol>, nil] specified traits
435
447
  # @param params [Hash] params override for the association
436
448
  # @param options [Hash<Symbol, Proc>]
437
- # @option options [Proc] if a condition to decide if this association should be serialized
449
+ # @option options [Proc, Symbol, nil] if a condition to decide if this association should be serialized
450
+ # When it's Proc, it's called to check condition
451
+ # When it's Symbol, it's treated as a method name on the Resource and the method is called
438
452
  # @param block [Block]
439
453
  # @return [void]
440
454
  # @see Alba::Association#initialize
441
- def association(name, condition = nil, resource: nil, serializer: nil, source: nil, key: nil, with_traits: nil, params: {}, **options, &block)
455
+ def association(name, condition = nil, resource: nil, serializer: nil, source: nil, key: nil, with_traits: nil, params: EMPTY_HASH, **options, &block)
442
456
  resource ||= serializer
443
457
  transformation = @_key_transformation_cascade ? @_transform_type : :none
444
458
  assoc = Association.new(
@@ -497,6 +511,7 @@ module Alba
497
511
  # @param key [String, Symbol]
498
512
  # @param key_for_collection [String, Symbol]
499
513
  # @raise [NoMethodError] when key doesn't respond to `to_sym` method
514
+ # @return [void]
500
515
  def root_key(key, key_for_collection = nil)
501
516
  @_key = key.to_sym
502
517
  @_key_for_collection = key_for_collection&.to_sym
@@ -506,18 +521,21 @@ module Alba
506
521
  #
507
522
  # @param key [String, Symbol]
508
523
  # @raise [NoMethodError] when key doesn't respond to `to_sym` method
524
+ # @return [void]
509
525
  def root_key_for_collection(key)
510
526
  @_key = true
511
527
  @_key_for_collection = key.to_sym
512
528
  end
513
529
 
514
530
  # Set root key to true
531
+ # @return [void]
515
532
  def root_key!
516
533
  @_key = true
517
534
  @_key_for_collection = true
518
535
  end
519
536
 
520
537
  # Set metadata
538
+ # @return [void]
521
539
  def meta(key = :meta, &block)
522
540
  @_meta = [key, block]
523
541
  end
@@ -526,6 +544,7 @@ module Alba
526
544
  #
527
545
  # @param file [String] name of the layout file
528
546
  # @param inline [Proc] a proc returning JSON string or a Hash representing JSON
547
+ # @return [void]
529
548
  def layout(file: nil, inline: nil)
530
549
  @_layout = Layout.new(file: file, inline: inline)
531
550
  end
@@ -537,6 +556,7 @@ module Alba
537
556
  # @param cascade [Boolean] decides if key transformation cascades into inline association
538
557
  # Default is true but can be set false for old (v1) behavior
539
558
  # @raise [Alba::Error] when type is not supported
559
+ # @return [void]
540
560
  def transform_keys(type, root: true, cascade: true)
541
561
  type = type.to_sym
542
562
  unless %i[none snake camel lower_camel dash].include?(type)
@@ -570,6 +590,7 @@ module Alba
570
590
  # Sets key for collection serialization
571
591
  #
572
592
  # @param key [String, Symbol]
593
+ # @return [void]
573
594
  def collection_key(key)
574
595
  @_collection_key = key.to_sym
575
596
  end
@@ -579,6 +600,7 @@ module Alba
579
600
  #
580
601
  # @param handler [Symbol] `:raise`, `:ignore` or `:nullify`
581
602
  # @param block [Block]
603
+ # @return [void]
582
604
  def on_error(handler = nil, &block)
583
605
  raise ArgumentError, 'You cannot specify error handler with both Symbol and block' if handler && block
584
606
  raise ArgumentError, 'You must specify error handler with either Symbol or block' unless handler || block
@@ -600,6 +622,7 @@ module Alba
600
622
  # Set nil handler
601
623
  #
602
624
  # @param block [Block]
625
+ # @return [void]
603
626
  def on_nil(&block)
604
627
  @_on_nil = block
605
628
  end
@@ -607,6 +630,7 @@ module Alba
607
630
  # Define helper methods
608
631
  #
609
632
  # @param mod [Module] a module to extend
633
+ # @return [void]
610
634
  def helper(mod = @_helper || Module.new, &block)
611
635
  mod.module_eval(&block) if block
612
636
  extend mod
@@ -615,11 +639,13 @@ module Alba
615
639
  end
616
640
 
617
641
  # DSL for alias, purely for readability
642
+ # @return [void]
618
643
  def prefer_resource_method!
619
644
  alias_method :fetch_attribute_from_object_and_resource, :_fetch_attribute_from_resource_first
620
645
  end
621
646
 
622
647
  # DSL for alias, purely for readability
648
+ # @return [void]
623
649
  def prefer_object_method!
624
650
  alias_method :fetch_attribute_from_object_and_resource, :_fetch_attribute_from_object_first
625
651
  end
@@ -8,8 +8,8 @@ module Alba
8
8
 
9
9
  # @param name [Symbol, String]
10
10
  # @param type [Symbol, Class]
11
- # @param converter [Proc]
12
- def initialize(name:, type:, converter:)
11
+ # @param converter [Proc, true, false, nil]
12
+ def initialize(name:, type:, converter:, &block)
13
13
  @name = name
14
14
  t = Alba.find_type(type)
15
15
  @type = case converter
@@ -18,11 +18,12 @@ module Alba
18
18
  else
19
19
  t.dup.tap { _1.auto_convert_with(converter) }
20
20
  end
21
+ @block = block
21
22
  end
22
23
 
23
24
  # @return [String, Integer, Boolean] type-checked or type-converted object
24
- def value
25
- v = yield(@name)
25
+ def value(object: nil)
26
+ v = @block ? @block.call(object) : yield(@name)
26
27
  result = @type.check(v)
27
28
  result ? v : @type.convert(v)
28
29
  rescue TypeError
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.9.0'
4
+ VERSION = '3.10.0'
5
5
  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.9.0
4
+ version: 3.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OKURA Masafumi
@@ -9,8 +9,8 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
- description: Alba is the fastest JSON serializer for Ruby. It focuses on performance,
13
- flexibility and usability.
12
+ description: Alba is a JSON serializer for Ruby, JRuby and TruffleRuby. It focuses
13
+ on performance, flexibility and usability.
14
14
  email:
15
15
  - masafumi.o1988@gmail.com
16
16
  executables: []
@@ -57,7 +57,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
57
57
  - !ruby/object:Gem::Version
58
58
  version: '0'
59
59
  requirements: []
60
- rubygems_version: 3.7.0.dev
60
+ rubygems_version: 3.6.7
61
61
  specification_version: 4
62
- summary: Alba is the fastest JSON serializer for Ruby.
62
+ summary: Alba is a JSON serializer for Ruby, JRuby and TruffleRuby.
63
63
  test_files: []