alba 3.5.0 → 3.6.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/README.md +5 -39
  4. data/lib/alba/association.rb +4 -1
  5. data/lib/alba/conditional_attribute.rb +11 -14
  6. data/lib/alba/layout.rb +8 -6
  7. data/lib/alba/railtie.rb +2 -2
  8. data/lib/alba/resource.rb +59 -15
  9. data/lib/alba/typed_attribute.rb +2 -0
  10. data/lib/alba/version.rb +1 -1
  11. data/lib/alba.rb +51 -32
  12. metadata +4 -52
  13. data/.codeclimate.yml +0 -12
  14. data/.editorconfig +0 -10
  15. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -26
  16. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  17. data/.github/dependabot.yml +0 -12
  18. data/.github/workflows/codeql-analysis.yml +0 -70
  19. data/.github/workflows/lint.yml +0 -17
  20. data/.github/workflows/main.yml +0 -41
  21. data/.gitignore +0 -11
  22. data/.rubocop.yml +0 -156
  23. data/.yardopts +0 -4
  24. data/CODE_OF_CONDUCT.md +0 -132
  25. data/CONTRIBUTING.md +0 -30
  26. data/Gemfile +0 -39
  27. data/HACKING.md +0 -42
  28. data/Rakefile +0 -17
  29. data/SECURITY.md +0 -12
  30. data/alba.gemspec +0 -33
  31. data/benchmark/Gemfile +0 -26
  32. data/benchmark/README.md +0 -137
  33. data/benchmark/collection.rb +0 -297
  34. data/benchmark/prep.rb +0 -56
  35. data/benchmark/single_resource.rb +0 -300
  36. data/bin/console +0 -15
  37. data/bin/setup +0 -8
  38. data/codecov.yml +0 -8
  39. data/docs/migrate_from_active_model_serializers.md +0 -359
  40. data/docs/migrate_from_jbuilder.md +0 -237
  41. data/docs/rails.md +0 -56
  42. data/gemfiles/without_active_support.gemfile +0 -19
  43. data/gemfiles/without_oj.gemfile +0 -19
  44. data/logo/alba-card.png +0 -0
  45. data/logo/alba-sign.png +0 -0
  46. data/logo/alba-typography.png +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad9473eff6bbd8fb5f282d2ad7c9d84ad3f104b0a5e190999bd7788fcec2d8be
4
- data.tar.gz: f778882ced7677d043694ba257df58bea4fb09fd108804fd4c0a28b7c722cc20
3
+ metadata.gz: 9013289126b33b20a7460d7c5092a3df8a39c7d79c7114d6f33e7111a2703a20
4
+ data.tar.gz: ae73ef30b48cdc1dfee93cd5c7cb55576ec0bd818d899c0fc2a041fbe09a90f7
5
5
  SHA512:
6
- metadata.gz: 484f8725e3a71a7d7714f2d19e0c005c11fd995c33e59431bef48f1dac8336360fef3fdd7657f47984bded1753e366a85b4e8bd7101bd0e418c658a508788ccf
7
- data.tar.gz: 0e7c53e58d3e9146b775b06d929b37e9860dba366114db7aca21e53628e78f946dd0ac13c7dc2788b0cc7fb10eb7211b9afc0c7ecf7c4bedd892786a07c04ea2
6
+ metadata.gz: ed86e0c309632cb9fe70752f5c7d5ddacbd529ea5c8b06ec802736e2fd745604470cf3163b43d287a6409529babf0d6c4d713c0d7b6035d4248ea33eec8e3a49
7
+ data.tar.gz: 93fc142fabd4a9358f279dccd0a85c8d0a22cabb8ff452d76ef221bb7ca929aa28bbd58a20bb29bc4a0bbe955e64a2616adf2f882728540e349eef3f82418906
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.6.0] 2025-03-11
10
+
11
+ ### Added
12
+
13
+ - Add serializer keyword argument as an alias for resource [#408](https://github.com/okuramasafumi/alba/pull/408)
14
+ - `Alba.resource_for` as a replacement to `resource_with`
15
+ - Hash serialization [#427](https://github.com/okuramasafumi/alba/pull/427)
16
+
17
+ ### Changed
18
+
19
+ - Performance improvements [#421](https://github.com/okuramasafumi/alba/pull/421) and [#423](https://github.com/okuramasafumi/alba/pull/423)
20
+
21
+ ### Fixed
22
+
23
+ - Range is not a collection [#417](https://github.com/okuramasafumi/alba/issues/417)
24
+
25
+ ### Deprecated
26
+
27
+ - `Alba.resource_with` is deprecated in favor of `Alba.resource_for`
28
+ - Overriding `Alba::Resource#attributes` is now deprecated in favor of `Alba::Resource#select`
29
+
9
30
  ## [3.5.0] 2025-01-01
10
31
 
11
32
  ### Added
data/README.md CHANGED
@@ -836,48 +836,11 @@ In this example we add `baz` attribute and change `root_key`. This way, you can
836
836
 
837
837
  ### Filtering attributes
838
838
 
839
- Filtering attributes can be done in two ways - with `attributes` and `select`. They have different semantics and usage.
840
-
841
- `select` is a new and more intuitive API, so generally it's recommended to use `select`.
842
-
843
- #### Filtering attributes with `attributes`
844
-
845
- You can filter out certain attributes by overriding `attributes` instance method. This is useful when you want to customize existing resource with inheritance.
846
-
847
- You can access raw attributes via `super` call. It returns a Hash whose keys are the name of the attribute and whose values are the body. Usually you need only keys to filter out, like below.
848
-
849
- ```ruby
850
- class Foo
851
- attr_accessor :id, :name, :body
852
-
853
- def initialize(id, name, body)
854
- @id = id
855
- @name = name
856
- @body = body
857
- end
858
- end
859
-
860
- class GenericFooResource
861
- include Alba::Resource
862
-
863
- attributes :id, :name, :body
864
- end
865
-
866
- class RestrictedFooResource < GenericFooResource
867
- def attributes
868
- super.select { |key, _| key.to_sym == :name }
869
- end
870
- end
871
-
872
- foo = Foo.new(1, 'my foo', 'body')
873
-
874
- RestrictedFooResource.new(foo).serialize
875
- # => '{"name":"my foo"}'
876
- ```
839
+ To filter attributes, you can use `select` instance method. Using `attributes` instance method is deprecated and will be removed in the future.
877
840
 
878
841
  #### Filtering attributes with `select`
879
842
 
880
- When you want to filter attributes based on more complex logic, you can use `select` instance method. `select` takes two parameters, the name of an attribute and the value of an attribute. If it returns false that attribute is rejected.
843
+ `select` takes two or three parameters, the name of an attribute, the value of an attribute and the attribute object (`Alba::Association`, for example). If it returns false that attribute is rejected.
881
844
 
882
845
  ```ruby
883
846
  class Foo
@@ -900,6 +863,9 @@ class RestrictedFooResource < GenericFooResource
900
863
  def select(_key, value)
901
864
  !value.nil?
902
865
  end
866
+
867
+ # This is also possible
868
+ # def select(_key, _value, _attribute)
903
869
  end
904
870
 
905
871
  foo = Foo.new(1, nil, 'body')
@@ -32,6 +32,9 @@ module Alba
32
32
  end
33
33
 
34
34
  # This is the same API in `NestedAttribute`
35
+ #
36
+ # @param type [String, Symbol] one of `snake`, `:camel`, `:lower_camel`, `:dash` and `none`
37
+ # @return [void]
35
38
  def key_transformation=(type)
36
39
  @resource.transform_keys(type) unless @resource.is_a?(Proc)
37
40
  end
@@ -44,7 +47,7 @@ module Alba
44
47
  # @return [Hash]
45
48
  def to_h(target, within: nil, params: {})
46
49
  params = params.merge(@params)
47
- object = target.__send__(@name)
50
+ object = target.is_a?(Hash) ? target.fetch(@name) : target.__send__(@name)
48
51
  object = @condition.call(object, params, target) if @condition
49
52
  return if object.nil?
50
53
 
@@ -2,13 +2,12 @@
2
2
 
3
3
  require_relative 'association'
4
4
  require_relative 'constants'
5
- require 'ostruct'
6
5
 
7
6
  module Alba
8
7
  # Represents attribute with `if` option
9
8
  # @api private
10
9
  class ConditionalAttribute
11
- # @param body [Symbol, Proc, Alba::Association, Alba::TypedAttribute] real attribute wrapped with condition
10
+ # @param body [Symbol, Proc, Alba::Association, Alba::TypedAttribute, Alba::NestedAttribute] real attribute wrapped with condition
12
11
  # @param condition [Symbol, Proc] condition to check
13
12
  def initialize(body:, condition:)
14
13
  @body = body
@@ -26,7 +25,7 @@ module Alba
26
25
  fetched_attribute = yield(@body)
27
26
  return fetched_attribute unless with_two_arity_proc_condition
28
27
 
29
- return Alba::REMOVE_KEY unless resource.instance_exec(object, objectize(fetched_attribute), &@condition)
28
+ return Alba::REMOVE_KEY unless resource.instance_exec(object, second_object(object), &@condition)
30
29
 
31
30
  fetched_attribute
32
31
  end
@@ -51,17 +50,15 @@ module Alba
51
50
  @condition.is_a?(Proc) && @condition.arity >= 2
52
51
  end
53
52
 
54
- # OpenStruct is used as a simple solution for converting Hash or Array of Hash into an object
55
- # Using OpenStruct is not good in general, but in this case there's no other solution
56
- def objectize(fetched_attribute)
57
- return fetched_attribute unless @body.is_a?(Alba::Association)
58
-
59
- if fetched_attribute.is_a?(Array)
60
- fetched_attribute.map do |hash|
61
- OpenStruct.new(hash)
62
- end
63
- else
64
- OpenStruct.new(fetched_attribute)
53
+ def second_object(object)
54
+ case @body
55
+ when Symbol, Alba::Association, Alba::TypedAttribute
56
+ object.__send__(@body.name)
57
+ when Alba::NestedAttribute
58
+ nil
59
+ when Proc
60
+ @body.call(object)
61
+ else raise Alba::Error, "Unreachable code, @body is: #{@body.inspect}"
65
62
  end
66
63
  end
67
64
  end
data/lib/alba/layout.rb CHANGED
@@ -15,13 +15,9 @@ module Alba
15
15
  # @param inline [Proc] a proc returning JSON string or a Hash representing JSON
16
16
  def initialize(file:, inline:)
17
17
  @body = if file
18
- raise ArgumentError, 'File layout must be a String representing filename' unless file.is_a?(String)
19
-
20
- file
18
+ check_and_return(file, 'File layout must be a String representing filename', String)
21
19
  elsif inline
22
- raise ArgumentError, 'Inline layout must be a Proc returning a Hash or a String' unless inline.is_a?(Proc)
23
-
24
- inline
20
+ check_and_return(inline, 'Inline layout must be a Proc returning a Hash or a String', Proc)
25
21
  else
26
22
  raise ArgumentError, 'Layout must be either String or Proc'
27
23
  end
@@ -47,6 +43,12 @@ module Alba
47
43
 
48
44
  attr_reader :serialized_json
49
45
 
46
+ def check_and_return(obj, message, klass)
47
+ raise ArgumentError, message unless obj.is_a?(klass)
48
+
49
+ obj
50
+ end
51
+
50
52
  def serialize_within_string_layout(bnd)
51
53
  ERB.new(File.read(@body)).result(bnd)
52
54
  end
data/lib/alba/railtie.rb CHANGED
@@ -8,12 +8,12 @@ module Alba
8
8
 
9
9
  ActiveSupport.on_load(:action_controller) do
10
10
  define_method(:serialize) do |obj, with: nil, root_key: nil, meta: {}, &block|
11
- resource = with.nil? ? Alba.resource_with(obj, &block) : with.new(obj)
11
+ resource = with.nil? ? Alba.resource_for(obj, &block) : with.new(obj)
12
12
  resource.to_json(root_key: root_key, meta: meta)
13
13
  end
14
14
 
15
15
  define_method(:render_serialized_json) do |obj, with: nil, root_key: nil, meta: {}, &block|
16
- json = with.nil? ? Alba.resource_with(obj, &block) : with.new(obj)
16
+ json = with.nil? ? Alba.resource_for(obj, &block) : with.new(obj)
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
@@ -14,14 +14,14 @@ module Alba
14
14
  module Resource
15
15
  # @!parse include InstanceMethods
16
16
  # @!parse extend ClassMethods
17
- INTERNAL_VARIABLES = {_attributes: {}, _key: nil, _key_for_collection: nil, _meta: nil, _transform_type: :none, _transforming_root_key: false, _key_transformation_cascade: true, _on_error: nil, _on_nil: nil, _layout: nil, _collection_key: nil, _helper: nil, _resource_methods: []}.freeze # rubocop:disable Layout/LineLength
17
+ INTERNAL_VARIABLES = {_attributes: {}, _key: nil, _key_for_collection: nil, _meta: nil, _transform_type: :none, _transforming_root_key: false, _key_transformation_cascade: true, _on_error: nil, _on_nil: nil, _layout: nil, _collection_key: nil, _helper: nil, _resource_methods: [], _select_arity: nil}.freeze # rubocop:disable Layout/LineLength
18
18
  private_constant :INTERNAL_VARIABLES
19
19
 
20
20
  WITHIN_DEFAULT = Object.new.freeze
21
21
  private_constant :WITHIN_DEFAULT
22
22
 
23
23
  # `setup` method is meta-programmatically defined here for performance.
24
- # @private
24
+ # @api private
25
25
  def self.included(base) # rubocop:disable Metrics/MethodLength
26
26
  super
27
27
  base.class_eval do
@@ -68,7 +68,8 @@ module Alba
68
68
  # @see #serialize
69
69
  # @see https://github.com/rails/rails/blob/7-0-stable/actionpack/lib/action_controller/metal/renderers.rb#L156
70
70
  def to_json(options = {}, root_key: nil, meta: {})
71
- confusing_options = options.keys.select { |k| k.to_sym == :only || k.to_sym == :except }
71
+ confusing_keys = [:only, :except]
72
+ confusing_options = options.keys.select { |k| confusing_keys.include?(k.to_sym) }
72
73
  unless confusing_options.empty?
73
74
  confusing_options.sort!
74
75
  confusing_options.map! { |s| "\"#{s}\"" }
@@ -99,12 +100,16 @@ module Alba
99
100
  #
100
101
  # @return [Hash]
101
102
  def serializable_hash
102
- Alba.collection?(@object) ? serializable_hash_for_collection : converter.call(@object)
103
+ Alba.collection?(@object) ? serializable_hash_for_collection : attributes_to_hash(@object, {})
103
104
  end
104
105
  alias to_h serializable_hash
105
106
 
106
107
  private
107
108
 
109
+ def deprecated_serializable_hash
110
+ Alba.collection?(@object) ? serializable_hash_for_collection : converter.call(@object)
111
+ end
112
+
108
113
  def serialize_with(hash)
109
114
  serialized_json = encode(hash)
110
115
  return serialized_json unless @_layout
@@ -130,6 +135,18 @@ module Alba
130
135
  end
131
136
 
132
137
  def serializable_hash_for_collection
138
+ if @_collection_key
139
+ @object.to_h do |item|
140
+ k = item.public_send(@_collection_key)
141
+ key = Alba.regularize_key(k)
142
+ [key, attributes_to_hash(item, {})]
143
+ end
144
+ else
145
+ @object.map { |obj| attributes_to_hash(obj, {}) }
146
+ end
147
+ end
148
+
149
+ def deprecated_serializable_hash_for_collection
133
150
  if @_collection_key
134
151
  @object.to_h do |item|
135
152
  k = item.public_send(@_collection_key)
@@ -200,22 +217,30 @@ module Alba
200
217
 
201
218
  # This is default behavior for getting attributes for serialization
202
219
  # Override this method to filter certain attributes
220
+ #
221
+ # @deprecated in favor of `select`
203
222
  def attributes
204
223
  @_attributes
205
224
  end
206
225
 
207
226
  # Default implementation for selecting attributes
208
227
  # Override this method to filter attributes based on key and value
209
- def select(_key, _value)
228
+ def select(_key, _value, _attribute)
210
229
  true
211
230
  end
212
231
 
213
232
  def set_key_and_attribute_body_from(obj, key, attribute, hash)
214
233
  key = transform_key(key)
215
234
  value = fetch_attribute(obj, key, attribute)
216
- return unless select(key, value)
235
+ # When `select` is not overridden, skip calling it for better performance
236
+ unless @_select_arity.nil?
237
+ # `select` can be overridden with both 2 and 3 parameters
238
+ # Here we check the arity and build arguments accordingly
239
+ args = @_select_arity == 3 ? [key, value, attribute] : [key, value]
240
+ return unless select(*args)
241
+ end
217
242
 
218
- hash[key] = value unless value == Alba::REMOVE_KEY
243
+ hash[key] = value unless Alba::REMOVE_KEY == value # rubocop:disable Style/YodaCondition
219
244
  end
220
245
 
221
246
  def handle_error(error, obj, key, attribute, hash)
@@ -259,12 +284,16 @@ module Alba
259
284
  end
260
285
 
261
286
  def _fetch_attribute_from_object_first(obj, attribute)
287
+ return obj.fetch(attribute) if obj.is_a?(Hash)
288
+
262
289
  obj.__send__(attribute)
263
290
  rescue NoMethodError
264
291
  __send__(attribute, obj)
265
292
  end
266
293
 
267
294
  def _fetch_attribute_from_resource_first(obj, attribute)
295
+ return obj.fetch(attribute) if obj.is_a?(Hash)
296
+
268
297
  if @_resource_methods.include?(attribute)
269
298
  __send__(attribute, obj)
270
299
  else
@@ -299,12 +328,27 @@ module Alba
299
328
  attr_reader(*INTERNAL_VARIABLES.keys)
300
329
 
301
330
  # This `method_added` is used for defining "resource methods"
302
- def method_added(method_name)
303
- _resource_methods << method_name.to_sym unless method_name.to_sym == :_setup
331
+ def method_added(method_name) # rubocop:disable Metrics/MethodLength
332
+ case method_name
333
+ when :collection_converter, :converter
334
+ warn "Defining ##{method_name} methods is deprecated", category: :deprecated, uplevel: 1
335
+ alias_method :serializable_hash_for_collection, :deprecated_serializable_hash_for_collection
336
+ private(:serializable_hash_for_collection)
337
+ alias_method :serializable_hash, :deprecated_serializable_hash
338
+ alias_method :to_h, :deprecated_serializable_hash
339
+ when :attributes
340
+ warn 'Overriding `attributes` is deprecated, use `select` instead.', category: :deprecated, uplevel: 1
341
+ when :select
342
+ @_select_arity = instance_method(:select).arity
343
+ when :_setup # noop
344
+ else
345
+ _resource_methods << method_name.to_sym
346
+ end
347
+
304
348
  super
305
349
  end
306
350
 
307
- # @private
351
+ # @api private
308
352
  def inherited(subclass)
309
353
  super
310
354
  INTERNAL_VARIABLES.each_key { |name| subclass.instance_variable_set(:"@#{name}", instance_variable_get(:"@#{name}").clone) }
@@ -316,7 +360,7 @@ module Alba
316
360
  #
317
361
  # @param attrs [Array<String, Symbol>]
318
362
  # @param if [Proc] condition to decide if it should serialize these attributes
319
- # @param attrs_with_types [Hash<[Symbol, String], [Array<Symbol, Proc>, Symbol]>]
363
+ # @param attrs_with_types [Hash{Symbol, String => Array<Symbol, Proc>, Symbol}]
320
364
  # attributes with name in its key and type and optional type converter in its value
321
365
  # @return [void]
322
366
  def attributes(*attrs, if: nil, **attrs_with_types)
@@ -364,6 +408,7 @@ module Alba
364
408
  # @param name [String, Symbol] name of the association, used as key when `key` param doesn't exist
365
409
  # @param condition [Proc, nil] a Proc to modify the association
366
410
  # @param resource [Class<Alba::Resource>, String, Proc, nil] representing resource for this association
411
+ # @param serializer [Class<Alba::Resource>, String, Proc, nil] alias for `resource`
367
412
  # @param key [String, Symbol, nil] used as key when given
368
413
  # @param params [Hash] params override for the association
369
414
  # @param options [Hash<Symbol, Proc>]
@@ -371,7 +416,8 @@ module Alba
371
416
  # @param block [Block]
372
417
  # @return [void]
373
418
  # @see Alba::Association#initialize
374
- def association(name, condition = nil, resource: nil, key: nil, params: {}, **options, &block)
419
+ def association(name, condition = nil, resource: nil, serializer: nil, key: nil, params: {}, **options, &block)
420
+ resource ||= serializer
375
421
  transformation = @_key_transformation_cascade ? @_transform_type : :none
376
422
  assoc = Association.new(
377
423
  name: name, condition: condition, resource: resource, params: params, nesting: nesting, key_transformation: transformation, helper: @_helper, &block
@@ -477,9 +523,7 @@ module Alba
477
523
  if @_key_transformation_cascade
478
524
  # We need to update key transformation of associations and nested attributes
479
525
  @_attributes.each_value do |attr|
480
- next unless attr.is_a?(Association) || attr.is_a?(NestedAttribute)
481
-
482
- attr.key_transformation = type
526
+ attr.key_transformation = type if attr.is_a?(Association) || attr.is_a?(NestedAttribute)
483
527
  end
484
528
  end
485
529
  self # Return the new class
@@ -4,6 +4,8 @@ module Alba
4
4
  # Representing typed attributes to encapsulate logic about types
5
5
  # @api private
6
6
  class TypedAttribute
7
+ attr_reader :name
8
+
7
9
  # @param name [Symbol, String]
8
10
  # @param type [Symbol, Class]
9
11
  # @param converter [Proc]
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.5.0'
4
+ VERSION = '3.6.0'
5
5
  end
data/lib/alba.rb CHANGED
@@ -46,13 +46,15 @@ module Alba
46
46
  # @param root_key [Symbol, nil, true]
47
47
  # @param block [Block] resource block
48
48
  # @return [String] serialized JSON string
49
- # @raise [ArgumentError] if block is absent or `with` argument's type is wrong
49
+ # @raise [ArgumentError] if both object and block are not given
50
50
  def serialize(object = nil, with: :inference, root_key: nil, &block)
51
+ raise ArgumentError, 'Either object or block must be given' if object.nil? && block.nil?
52
+
51
53
  if collection?(object)
52
- h = hashify_collection(object, with, &block)
54
+ h = hashify_collection(object, with, root_key, &block)
53
55
  Alba.encoder.call(h)
54
56
  else
55
- resource = resource_with(object, &block)
57
+ resource = resource_for(object, &block)
56
58
  resource.serialize(root_key: root_key)
57
59
  end
58
60
  end
@@ -64,22 +66,24 @@ module Alba
64
66
  # @param root_key [Symbol, nil, true]
65
67
  # @param block [Block] resource block
66
68
  # @return [String] serialized JSON string
67
- # @raise [ArgumentError] if block is absent or `with` argument's type is wrong
69
+ # @raise [ArgumentError] if both object and block are not given
68
70
  def hashify(object = nil, with: :inference, root_key: nil, &block)
71
+ raise ArgumentError, 'Either object or block must be given' if object.nil? && block.nil?
72
+
69
73
  if collection?(object)
70
- hashify_collection(object, with, &block)
74
+ hashify_collection(object, with, root_key, &block)
71
75
  else
72
- resource = resource_with(object, &block)
76
+ resource = resource_for(object, &block)
73
77
  resource.as_json(root_key: root_key)
74
78
  end
75
79
  end
76
80
 
77
81
  # Detect if object is a collection or not.
78
- # When object is a Struct, it's Enumerable but not a collection
82
+ # When object is a Struct or a Range, it's Enumerable but not a collection
79
83
  #
80
84
  # @api private
81
85
  def collection?(object)
82
- object.is_a?(Enumerable) && !object.is_a?(Struct)
86
+ object.is_a?(Enumerable) && !object.is_a?(Struct) && !object.is_a?(Range) && !object.is_a?(Hash)
83
87
  end
84
88
 
85
89
  # Enable inference for key and resource name
@@ -162,8 +166,9 @@ module Alba
162
166
  # @return [Symbol, String, nil]
163
167
  def regularize_key(key)
164
168
  return if key.nil?
169
+ return key.to_sym if @symbolize_keys
165
170
 
166
- @symbolize_keys ? key.to_sym : key.to_s
171
+ key.is_a?(Symbol) ? key.name : key.to_s
167
172
  end
168
173
 
169
174
  # Transform a key with given transform_type
@@ -218,19 +223,45 @@ module Alba
218
223
  register_default_types
219
224
  end
220
225
 
226
+ # @deprecated Use resource_for instead
227
+ def resource_with(object, with: :inference, &block)
228
+ Kernel.warn('Alba.resource_with is deprecated. Use `Alba.resource_for` instead.')
229
+ _resource_for(object, with: with, &block)
230
+ end
231
+
221
232
  # Get a resource object from arguments
222
233
  # If block is given, it creates a resource class with the block
223
- # Otherwise, it infers resource class from the object's class name
234
+ # Otherwise, it behaves depending on `with` argument
224
235
  #
225
- # @ param object [Object] the object whose class name is used for inferring resource class
226
- def resource_with(object, &block)
227
- klass = block ? resource_class(&block) : infer_resource_class(object.class.name)
228
-
229
- klass.new(object)
236
+ # @param object [Object] the object whose class name is used for inferring resource class
237
+ # @param with [:inference, Proc, Class<Alba::Resource>] determines how to get resource class for `object`
238
+ # When it's `:inference`, it infers resource class from `object`'s class name
239
+ # When it's a Proc, it calls the Proc with `object` as an argument
240
+ # When it's a Class, it uses the Class as a resource class
241
+ # Otherwise, it raises an ArgumentError
242
+ # @return [Alba::Resource] resource class with `object` as its target object
243
+ # @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)
230
246
  end
231
247
 
232
248
  private
233
249
 
250
+ def _resource_for(object, with: :inference, &block) # rubocop:disable Metrics/MethodLength
251
+ klass = if block
252
+ resource_class(&block)
253
+ else
254
+ case with
255
+ when :inference then infer_resource_class(object.class.name)
256
+ when Class then with
257
+ when Proc then with.call(object)
258
+ else raise ArgumentError, '`with` argument must be either :inference, Proc or Class'
259
+ end
260
+ end
261
+
262
+ klass.new(object)
263
+ end
264
+
234
265
  def inflector_from(name_or_module)
235
266
  case name_or_module
236
267
  when nil then nil
@@ -283,22 +314,14 @@ module Alba
283
314
  end
284
315
  end
285
316
 
286
- def hashify_collection(collection, with, &block) # rubocop:disable Metrics/MethodLength
287
- collection.map do |obj|
288
- resource = if block
289
- resource_class(&block)
290
- else
291
- case with
292
- when Class then with
293
- when :inference then infer_resource_class(obj.class.name)
294
- when Proc then with.call(obj)
295
- else raise ArgumentError, '`with` argument must be either :inference, Proc or Class'
296
- end
297
- end
317
+ def hashify_collection(collection, with, root_key, &block)
318
+ array = collection.map do |obj|
319
+ resource = resource_for(obj, with: with, &block)
298
320
  raise Alba::Error if resource.nil?
299
321
 
300
- resource.new(obj).to_h
322
+ resource.to_h
301
323
  end
324
+ root_key ? {root_key => array} : array
302
325
  end
303
326
 
304
327
  def validate_inflector(inflector)
@@ -329,7 +352,3 @@ module Alba
329
352
 
330
353
  reset!
331
354
  end
332
-
333
- # Monkey patch for TruffleRuby
334
- # Delete this after 24.1.2 is released
335
- File::SHARE_DELETE = 0 if defined?(Truffle)
metadata CHANGED
@@ -1,28 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alba
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.0
4
+ version: 3.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OKURA Masafumi
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-01-01 00:00:00.000000000 Z
11
- dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: ostruct
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - "~>"
17
- - !ruby/object:Gem::Version
18
- version: '0.6'
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - "~>"
24
- - !ruby/object:Gem::Version
25
- version: '0.6'
10
+ date: 2025-04-01 00:00:00.000000000 Z
11
+ dependencies: []
26
12
  description: Alba is the fastest JSON serializer for Ruby. It focuses on performance,
27
13
  flexibility and usability.
28
14
  email:
@@ -31,40 +17,9 @@ executables: []
31
17
  extensions: []
32
18
  extra_rdoc_files: []
33
19
  files:
34
- - ".codeclimate.yml"
35
- - ".editorconfig"
36
- - ".github/ISSUE_TEMPLATE/bug_report.md"
37
- - ".github/ISSUE_TEMPLATE/feature_request.md"
38
- - ".github/dependabot.yml"
39
- - ".github/workflows/codeql-analysis.yml"
40
- - ".github/workflows/lint.yml"
41
- - ".github/workflows/main.yml"
42
- - ".gitignore"
43
- - ".rubocop.yml"
44
- - ".yardopts"
45
20
  - CHANGELOG.md
46
- - CODE_OF_CONDUCT.md
47
- - CONTRIBUTING.md
48
- - Gemfile
49
- - HACKING.md
50
21
  - LICENSE.txt
51
22
  - README.md
52
- - Rakefile
53
- - SECURITY.md
54
- - alba.gemspec
55
- - benchmark/Gemfile
56
- - benchmark/README.md
57
- - benchmark/collection.rb
58
- - benchmark/prep.rb
59
- - benchmark/single_resource.rb
60
- - bin/console
61
- - bin/setup
62
- - codecov.yml
63
- - docs/migrate_from_active_model_serializers.md
64
- - docs/migrate_from_jbuilder.md
65
- - docs/rails.md
66
- - gemfiles/without_active_support.gemfile
67
- - gemfiles/without_oj.gemfile
68
23
  - lib/alba.rb
69
24
  - lib/alba/association.rb
70
25
  - lib/alba/conditional_attribute.rb
@@ -79,9 +34,6 @@ files:
79
34
  - lib/alba/type.rb
80
35
  - lib/alba/typed_attribute.rb
81
36
  - lib/alba/version.rb
82
- - logo/alba-card.png
83
- - logo/alba-sign.png
84
- - logo/alba-typography.png
85
37
  homepage: https://github.com/okuramasafumi/alba
86
38
  licenses:
87
39
  - MIT
@@ -105,7 +57,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
57
  - !ruby/object:Gem::Version
106
58
  version: '0'
107
59
  requirements: []
108
- rubygems_version: 3.6.0.dev
60
+ rubygems_version: 3.7.0.dev
109
61
  specification_version: 4
110
62
  summary: Alba is the fastest JSON serializer for Ruby.
111
63
  test_files: []
data/.codeclimate.yml DELETED
@@ -1,12 +0,0 @@
1
- version: "2" # required to adjust maintainability checks
2
-
3
- checks:
4
- method-complexity:
5
- enabled: true
6
- config:
7
- threshold: 7
8
-
9
- exclude_patterns:
10
- - "benchmark/**/*"
11
- - "script/**/*"
12
- - "test/**/*"