alba 3.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -0
- data/README.md +7 -41
- data/lib/alba/association.rb +4 -1
- data/lib/alba/conditional_attribute.rb +11 -14
- data/lib/alba/layout.rb +8 -6
- data/lib/alba/railtie.rb +6 -5
- data/lib/alba/resource.rb +59 -15
- data/lib/alba/typed_attribute.rb +2 -0
- data/lib/alba/version.rb +1 -1
- data/lib/alba.rb +72 -38
- metadata +4 -52
- data/.codeclimate.yml +0 -12
- data/.editorconfig +0 -10
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -26
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- data/.github/dependabot.yml +0 -12
- data/.github/workflows/codeql-analysis.yml +0 -70
- data/.github/workflows/lint.yml +0 -17
- data/.github/workflows/main.yml +0 -39
- data/.gitignore +0 -11
- data/.rubocop.yml +0 -156
- data/.yardopts +0 -4
- data/CODE_OF_CONDUCT.md +0 -132
- data/CONTRIBUTING.md +0 -30
- data/Gemfile +0 -29
- data/HACKING.md +0 -42
- data/Rakefile +0 -17
- data/SECURITY.md +0 -12
- data/alba.gemspec +0 -33
- data/benchmark/Gemfile +0 -24
- data/benchmark/README.md +0 -119
- data/benchmark/collection.rb +0 -275
- data/benchmark/prep.rb +0 -56
- data/benchmark/single_resource.rb +0 -300
- data/bin/console +0 -15
- data/bin/setup +0 -8
- data/codecov.yml +0 -8
- data/docs/migrate_from_active_model_serializers.md +0 -359
- data/docs/migrate_from_jbuilder.md +0 -237
- data/docs/rails.md +0 -56
- data/gemfiles/without_active_support.gemfile +0 -19
- data/gemfiles/without_oj.gemfile +0 -19
- data/logo/alba-card.png +0 -0
- data/logo/alba-sign.png +0 -0
- data/logo/alba-typography.png +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9013289126b33b20a7460d7c5092a3df8a39c7d79c7114d6f33e7111a2703a20
|
4
|
+
data.tar.gz: ae73ef30b48cdc1dfee93cd5c7cb55576ec0bd818d899c0fc2a041fbe09a90f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed86e0c309632cb9fe70752f5c7d5ddacbd529ea5c8b06ec802736e2fd745604470cf3163b43d287a6409529babf0d6c4d713c0d7b6035d4248ea33eec8e3a49
|
7
|
+
data.tar.gz: 93fc142fabd4a9358f279dccd0a85c8d0a22cabb8ff452d76ef221bb7ca929aa28bbd58a20bb29bc4a0bbe955e64a2616adf2f882728540e349eef3f82418906
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,37 @@ 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
|
+
|
30
|
+
## [3.5.0] 2025-01-01
|
31
|
+
|
32
|
+
### Added
|
33
|
+
|
34
|
+
- `render_serialized_json` now works with `root_key` and `meta` [#398](https://github.com/okuramasafumi/alba/pull/398)
|
35
|
+
|
36
|
+
### Improved
|
37
|
+
|
38
|
+
- Add transform_keys caching [#402](https://github.com/okuramasafumi/alba/pull/402)
|
39
|
+
|
9
40
|
## [3.4.0] 2024-12-01
|
10
41
|
|
11
42
|
### Added
|
data/README.md
CHANGED
@@ -686,7 +686,7 @@ Alba.serialize(something)
|
|
686
686
|
# => Same as `FooResource.new(something).serialize` when `something` is an instance of `Foo`.
|
687
687
|
```
|
688
688
|
|
689
|
-
Although this might be useful sometimes, it's generally recommended to define a class for Resource.
|
689
|
+
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).
|
690
690
|
|
691
691
|
#### Inline definition for multiple root keys
|
692
692
|
|
@@ -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
|
-
|
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
|
-
|
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')
|
@@ -1156,7 +1122,7 @@ end
|
|
1156
1122
|
class UserResource
|
1157
1123
|
include Alba::Resource
|
1158
1124
|
|
1159
|
-
root_key!
|
1125
|
+
root_key! # This is required to add inferred root key, otherwise it has no root key
|
1160
1126
|
|
1161
1127
|
attributes :id
|
1162
1128
|
|
data/lib/alba/association.rb
CHANGED
@@ -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,
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
19
|
-
|
20
|
-
file
|
18
|
+
check_and_return(file, 'File layout must be a String representing filename', String)
|
21
19
|
elsif inline
|
22
|
-
|
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
@@ -7,13 +7,14 @@ 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, &block|
|
11
|
-
with.nil? ? Alba.
|
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)
|
12
|
+
resource.to_json(root_key: root_key, meta: meta)
|
12
13
|
end
|
13
14
|
|
14
|
-
define_method(:render_serialized_json) do |obj, with: nil, &block|
|
15
|
-
json = with.nil? ? Alba.
|
16
|
-
render json: json
|
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)
|
17
|
+
render json: json.to_json(root_key: root_key, meta: meta)
|
17
18
|
end
|
18
19
|
end
|
19
20
|
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
|
-
|
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 :
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
data/lib/alba/typed_attribute.rb
CHANGED
data/lib/alba/version.rb
CHANGED
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
|
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 =
|
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
|
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 =
|
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
|
@@ -117,6 +121,7 @@ module Alba
|
|
117
121
|
# When it's a Class or a Module, it should have some methods, see {Alba::DefaultInflector}
|
118
122
|
def inflector=(inflector)
|
119
123
|
@inflector = inflector_from(inflector)
|
124
|
+
reset_transform_keys
|
120
125
|
end
|
121
126
|
|
122
127
|
# @param block [Block] resource body
|
@@ -144,11 +149,13 @@ module Alba
|
|
144
149
|
|
145
150
|
# Configure Alba to symbolize keys
|
146
151
|
def symbolize_keys!
|
152
|
+
reset_transform_keys unless @symbolize_keys
|
147
153
|
@symbolize_keys = true
|
148
154
|
end
|
149
155
|
|
150
156
|
# Configure Alba to stringify (not symbolize) keys
|
151
157
|
def stringify_keys!
|
158
|
+
reset_transform_keys if @symbolize_keys
|
152
159
|
@symbolize_keys = false
|
153
160
|
end
|
154
161
|
|
@@ -159,8 +166,9 @@ module Alba
|
|
159
166
|
# @return [Symbol, String, nil]
|
160
167
|
def regularize_key(key)
|
161
168
|
return if key.nil?
|
169
|
+
return key.to_sym if @symbolize_keys
|
162
170
|
|
163
|
-
|
171
|
+
key.is_a?(Symbol) ? key.name : key.to_s
|
164
172
|
end
|
165
173
|
|
166
174
|
# Transform a key with given transform_type
|
@@ -168,19 +176,22 @@ module Alba
|
|
168
176
|
# @param key [String] a target key
|
169
177
|
# @param transform_type [Symbol] a transform type, either one of `camel`, `lower_camel`, `dash` or `snake`
|
170
178
|
# @return [String]
|
171
|
-
def transform_key(key, transform_type:)
|
179
|
+
def transform_key(key, transform_type:) # rubocop:disable Metrics/MethodLength
|
172
180
|
raise Alba::Error, 'Inflector is nil. You must set inflector before transforming keys.' unless inflector
|
173
181
|
|
174
|
-
key
|
182
|
+
@_transformed_keys[transform_type][key] ||= begin
|
183
|
+
key = key.to_s
|
175
184
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
185
|
+
k = case transform_type
|
186
|
+
when :camel then inflector.camelize(key)
|
187
|
+
when :lower_camel then inflector.camelize_lower(key)
|
188
|
+
when :dash then inflector.dasherize(key)
|
189
|
+
when :snake then inflector.underscore(key)
|
190
|
+
else raise Alba::Error, "Unknown transform type: #{transform_type}"
|
191
|
+
end
|
192
|
+
|
193
|
+
regularize_key(k)
|
194
|
+
end
|
184
195
|
end
|
185
196
|
|
186
197
|
# Register types, used for both builtin and custom types
|
@@ -208,22 +219,49 @@ module Alba
|
|
208
219
|
@_on_error = :raise
|
209
220
|
@_on_nil = nil
|
210
221
|
@types = {}
|
222
|
+
reset_transform_keys
|
211
223
|
register_default_types
|
212
224
|
end
|
213
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
|
+
|
214
232
|
# Get a resource object from arguments
|
215
233
|
# If block is given, it creates a resource class with the block
|
216
|
-
# Otherwise, it
|
234
|
+
# Otherwise, it behaves depending on `with` argument
|
217
235
|
#
|
218
|
-
# @
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
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)
|
223
246
|
end
|
224
247
|
|
225
248
|
private
|
226
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
|
+
|
227
265
|
def inflector_from(name_or_module)
|
228
266
|
case name_or_module
|
229
267
|
when nil then nil
|
@@ -276,22 +314,14 @@ module Alba
|
|
276
314
|
end
|
277
315
|
end
|
278
316
|
|
279
|
-
def hashify_collection(collection, with, &block)
|
280
|
-
collection.map do |obj|
|
281
|
-
resource =
|
282
|
-
resource_class(&block)
|
283
|
-
else
|
284
|
-
case with
|
285
|
-
when Class then with
|
286
|
-
when :inference then infer_resource_class(obj.class.name)
|
287
|
-
when Proc then with.call(obj)
|
288
|
-
else raise ArgumentError, '`with` argument must be either :inference, Proc or Class'
|
289
|
-
end
|
290
|
-
end
|
317
|
+
def hashify_collection(collection, with, root_key, &block)
|
318
|
+
array = collection.map do |obj|
|
319
|
+
resource = resource_for(obj, with: with, &block)
|
291
320
|
raise Alba::Error if resource.nil?
|
292
321
|
|
293
|
-
resource.
|
322
|
+
resource.to_h
|
294
323
|
end
|
324
|
+
root_key ? {root_key => array} : array
|
295
325
|
end
|
296
326
|
|
297
327
|
def validate_inflector(inflector)
|
@@ -302,6 +332,10 @@ module Alba
|
|
302
332
|
inflector
|
303
333
|
end
|
304
334
|
|
335
|
+
def reset_transform_keys
|
336
|
+
@_transformed_keys = Hash.new { |h, k| h[k] = {} }
|
337
|
+
end
|
338
|
+
|
305
339
|
def register_default_types # rubocop:disable Metrics/AbcSize
|
306
340
|
[String, :String].each do |t|
|
307
341
|
register_type(t, check: ->(obj) { obj.is_a?(String) }, converter: lambda(&:to_s))
|