alba 3.6.0 → 3.7.1
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 +12 -0
- data/README.md +91 -1
- data/lib/alba/association.rb +7 -2
- data/lib/alba/nested_attribute.rb +3 -2
- data/lib/alba/resource.rb +45 -10
- data/lib/alba/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9179eabfb7cd37ad4403470b3c83229f7dc558085d8946d84302473f8076694c
|
4
|
+
data.tar.gz: cb7092762e4881c13171447cc2dc4a6ec2ceb077bd6527a154d5dac6d3369967
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8baf63e1b739731f221c6a76bb452d730c703fe848853f26f91219357001832599196902e98f2266fa04631873e3bd791dca46c7898c3f16fc2909acf1a74a10
|
7
|
+
data.tar.gz: '0872f1ca1d1a263f8d7dff94ed1e965d88e9ec423213d0b631c3bf155ddaabc5905da4d3b74344950ede8a238761777100abb42749e378beba55f3796cf85dda'
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [3.7.1] 2025-05-27
|
10
|
+
|
11
|
+
### Fixed
|
12
|
+
|
13
|
+
- `select` now works with `trait` and `nested_attribute` [#436](https://github.com/okuramasafumi/alba/pull/436)
|
14
|
+
|
15
|
+
## [3.7.0] 2025-05-08
|
16
|
+
|
17
|
+
### Added
|
18
|
+
|
19
|
+
- Traits [#434](https://github.com/okuramasafumi/alba/pull/434)
|
20
|
+
|
9
21
|
## [3.6.0] 2025-03-11
|
10
22
|
|
11
23
|
### Added
|
data/README.md
CHANGED
@@ -122,7 +122,7 @@ Alba supports CRuby 3.0 and higher and latest JRuby and TruffleRuby.
|
|
122
122
|
|
123
123
|
## Documentation
|
124
124
|
|
125
|
-
You can find the documentation on [
|
125
|
+
You can find the documentation on [GitHub Pages](https://okuramasafumi.github.io/alba/).
|
126
126
|
|
127
127
|
## Features
|
128
128
|
|
@@ -1073,6 +1073,40 @@ class UserResource2
|
|
1073
1073
|
end
|
1074
1074
|
```
|
1075
1075
|
|
1076
|
+
### Traits
|
1077
|
+
|
1078
|
+
Traits is an easy way to a group of attributes and apply it to the resource.
|
1079
|
+
|
1080
|
+
```ruby
|
1081
|
+
class User
|
1082
|
+
attr_accessor :id, :name, :email
|
1083
|
+
|
1084
|
+
def initialize(id, name, email)
|
1085
|
+
@id = id
|
1086
|
+
@name = name
|
1087
|
+
@email = email
|
1088
|
+
end
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
class UserResource
|
1092
|
+
include Alba::Resource
|
1093
|
+
|
1094
|
+
attributes :id
|
1095
|
+
|
1096
|
+
trait :additional do
|
1097
|
+
attributes :name, :email
|
1098
|
+
end
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
user = User.new(1, 'Foo', 'foo@example.org')
|
1102
|
+
UserResource.new(user).serialize # => '{"id":1}'
|
1103
|
+
UserResource.new(user, with_traits: :additional).serialize # => '{"id":1,"name":"Foo","email":"foo@example.com"}'
|
1104
|
+
```
|
1105
|
+
|
1106
|
+
This way, we can keep the resource class simple and inject conditions from outside. We can get the same result with the combination of `if` and `params`, but using `traits` DSL can make the resource class readable.
|
1107
|
+
|
1108
|
+
We can specify multiple traits at once with `with_traits: []` keyword argument.
|
1109
|
+
|
1076
1110
|
### Default
|
1077
1111
|
|
1078
1112
|
Alba doesn't support default value for attributes, but it's easy to set a default value.
|
@@ -1807,6 +1841,58 @@ Here, we override `serialize` method with `prepend`. In overridden method we pri
|
|
1807
1841
|
|
1808
1842
|
Don't forget calling `super` in this way.
|
1809
1843
|
|
1844
|
+
## Tips and Tricks
|
1845
|
+
|
1846
|
+
### Treating specific classes as non-collection
|
1847
|
+
|
1848
|
+
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:
|
1849
|
+
|
1850
|
+
```ruby
|
1851
|
+
Alba.singleton_class.prepend(
|
1852
|
+
Module.new do
|
1853
|
+
def collection?(object)
|
1854
|
+
super && !object.is_a?(SomeClass)
|
1855
|
+
end
|
1856
|
+
end
|
1857
|
+
)
|
1858
|
+
```
|
1859
|
+
|
1860
|
+
### Adding indexes to `many` association
|
1861
|
+
|
1862
|
+
Let's say an author has many books. We want returned JSON to include indexes of each book. In this case, we can reduce the number of executed SQL by fetching indexes ahead and push indexes into `param`.
|
1863
|
+
|
1864
|
+
```ruby
|
1865
|
+
Author = Data.define(:id, :books)
|
1866
|
+
Book = Data.define(:id, :name)
|
1867
|
+
|
1868
|
+
book1 = Book.new(1, 'book1')
|
1869
|
+
book2 = Book.new(2, 'book2')
|
1870
|
+
book3 = Book.new(3, 'book3')
|
1871
|
+
|
1872
|
+
author = Author.new(2, [book2, book3, book1])
|
1873
|
+
|
1874
|
+
class AuthorResource
|
1875
|
+
include Alba::Resource
|
1876
|
+
|
1877
|
+
attributes :id
|
1878
|
+
many :books do
|
1879
|
+
attributes :id, :name
|
1880
|
+
attribute :index do |bar|
|
1881
|
+
params[:index][bar.id]
|
1882
|
+
end
|
1883
|
+
end
|
1884
|
+
end
|
1885
|
+
|
1886
|
+
AuthorResource.new(
|
1887
|
+
author,
|
1888
|
+
params: {
|
1889
|
+
index: author.books.map.with_index { |book, index| [book.id, index] }
|
1890
|
+
.to_h
|
1891
|
+
}
|
1892
|
+
).serialize
|
1893
|
+
# => {"id":2,"books":[{"id":2,"name":"book2","index":0},{"id":3,"name":"book3","index":1},{"id":1,"name":"book1","index":2}]}
|
1894
|
+
```
|
1895
|
+
|
1810
1896
|
## Rails
|
1811
1897
|
|
1812
1898
|
When you use Alba in Rails, you can create an initializer file with the line below for compatibility with Rails JSON encoder.
|
@@ -1840,6 +1926,10 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
1840
1926
|
|
1841
1927
|
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).
|
1842
1928
|
|
1929
|
+
## Versioning
|
1930
|
+
|
1931
|
+
Alba follows [Semver 2.0.0](https://semver.org/spec/v2.0.0.html).
|
1932
|
+
|
1843
1933
|
## License
|
1844
1934
|
|
1845
1935
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/lib/alba/association.rb
CHANGED
@@ -47,8 +47,7 @@ module Alba
|
|
47
47
|
# @return [Hash]
|
48
48
|
def to_h(target, within: nil, params: {})
|
49
49
|
params = params.merge(@params)
|
50
|
-
object =
|
51
|
-
object = @condition.call(object, params, target) if @condition
|
50
|
+
object = object_from(target, params)
|
52
51
|
return if object.nil?
|
53
52
|
|
54
53
|
if @resource.is_a?(Proc)
|
@@ -62,6 +61,12 @@ module Alba
|
|
62
61
|
|
63
62
|
private
|
64
63
|
|
64
|
+
def object_from(target, params)
|
65
|
+
o = target.is_a?(Hash) ? target.fetch(@name) : target.__send__(@name)
|
66
|
+
o = @condition.call(o, params, target) if @condition
|
67
|
+
o
|
68
|
+
end
|
69
|
+
|
65
70
|
def constantize(resource)
|
66
71
|
case resource
|
67
72
|
when Class
|
@@ -17,12 +17,13 @@ module Alba
|
|
17
17
|
# @param object [Object] the object being serialized
|
18
18
|
# @param params [Hash] params Hash inherited from Resource
|
19
19
|
# @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
|
20
21
|
# @return [Hash] hash serialized from running the class body in the object
|
21
|
-
def value(object:, params:, within:)
|
22
|
+
def value(object:, params:, within:, select: nil)
|
22
23
|
resource_class = Alba.resource_class
|
23
24
|
resource_class.transform_keys(@key_transformation)
|
24
25
|
resource_class.class_eval(&@block)
|
25
|
-
resource_class.new(object, params: params, within: within).serializable_hash
|
26
|
+
resource_class.new(object, params: params, within: within, select: select).serializable_hash
|
26
27
|
end
|
27
28
|
end
|
28
29
|
end
|
data/lib/alba/resource.rb
CHANGED
@@ -14,7 +14,7 @@ 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: [], _select_arity: nil}.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, _traits: {}}.freeze # rubocop:disable Layout/LineLength
|
18
18
|
private_constant :INTERNAL_VARIABLES
|
19
19
|
|
20
20
|
WITHIN_DEFAULT = Object.new.freeze
|
@@ -46,10 +46,16 @@ module Alba
|
|
46
46
|
# @param object [Object] the object to be serialized
|
47
47
|
# @param params [Hash] user-given Hash for arbitrary data
|
48
48
|
# @param within [Object, nil, false, true] determines what associations to be serialized. If not set, it serializes all associations.
|
49
|
-
|
49
|
+
# @param with_traits [Symbol, Array<Symbol>, nil] specified traits
|
50
|
+
# @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)
|
50
52
|
@object = object
|
51
53
|
@params = params
|
52
54
|
@within = within
|
55
|
+
@with_traits = with_traits
|
56
|
+
# select override to share the same method with `trait` and `nested_attribute`
|
57
|
+
# Trait and NestedAttribute generates anonymous class so it checks if it's anonymous class to prevent accidental overriding
|
58
|
+
self.class.define_method(:select, &select) if select && self.class.name.nil?
|
53
59
|
_setup
|
54
60
|
end
|
55
61
|
|
@@ -106,6 +112,20 @@ module Alba
|
|
106
112
|
|
107
113
|
private
|
108
114
|
|
115
|
+
def hash_from_traits(obj)
|
116
|
+
h = {}
|
117
|
+
return h if @with_traits.nil?
|
118
|
+
|
119
|
+
Array(@with_traits).each do |trait|
|
120
|
+
body = @_traits.fetch(trait) { raise Alba::Error, "Trait not found: #{trait}" }
|
121
|
+
|
122
|
+
resource_class = Alba.resource_class
|
123
|
+
resource_class.class_eval(&body)
|
124
|
+
h.merge!(resource_class.new(obj, params: params, within: @within, select: method(:select)).serializable_hash)
|
125
|
+
end
|
126
|
+
h
|
127
|
+
end
|
128
|
+
|
109
129
|
def deprecated_serializable_hash
|
110
130
|
Alba.collection?(@object) ? serializable_hash_for_collection : converter.call(@object)
|
111
131
|
end
|
@@ -212,7 +232,7 @@ module Alba
|
|
212
232
|
rescue StandardError => e
|
213
233
|
handle_error(e, obj, key, attribute, hash)
|
214
234
|
end
|
215
|
-
hash
|
235
|
+
@with_traits.nil? ? hash : hash.merge!(hash_from_traits(obj))
|
216
236
|
end
|
217
237
|
|
218
238
|
# This is default behavior for getting attributes for serialization
|
@@ -233,16 +253,18 @@ module Alba
|
|
233
253
|
key = transform_key(key)
|
234
254
|
value = fetch_attribute(obj, key, attribute)
|
235
255
|
# When `select` is not overridden, skip calling it for better performance
|
236
|
-
|
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
|
256
|
+
return if !@_select_arity.nil? && !do_select(key, value, attribute)
|
242
257
|
|
243
258
|
hash[key] = value unless Alba::REMOVE_KEY == value # rubocop:disable Style/YodaCondition
|
244
259
|
end
|
245
260
|
|
261
|
+
def do_select(key, value, attribute)
|
262
|
+
# `select` can be overridden with both 2 and 3 parameters
|
263
|
+
# Here we check the arity and build arguments accordingly
|
264
|
+
args = @_select_arity == 3 ? [key, value, attribute] : [key, value]
|
265
|
+
select(*args)
|
266
|
+
end
|
267
|
+
|
246
268
|
def handle_error(error, obj, key, attribute, hash)
|
247
269
|
case (on_error = @_on_error || :raise)
|
248
270
|
when :raise, nil then raise(error)
|
@@ -270,7 +292,7 @@ module Alba
|
|
270
292
|
when Proc then instance_exec(obj, &attribute)
|
271
293
|
when Alba::Association then yield_if_within(attribute.name.to_sym) { |within| attribute.to_h(obj, params: params, within: within) }
|
272
294
|
when TypedAttribute then attribute.value { |attr| fetch_attribute(obj, key, attr) }
|
273
|
-
when NestedAttribute then attribute.value(object: obj, params: params, within: @within)
|
295
|
+
when NestedAttribute then attribute.value(object: obj, params: params, within: @within, select: method(:select))
|
274
296
|
when ConditionalAttribute then attribute.with_passing_condition(resource: self, object: obj) { |attr| fetch_attribute(obj, key, attr) }
|
275
297
|
# :nocov:
|
276
298
|
else raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
|
@@ -455,6 +477,19 @@ module Alba
|
|
455
477
|
end
|
456
478
|
alias nested nested_attribute
|
457
479
|
|
480
|
+
# Set a trait
|
481
|
+
#
|
482
|
+
# @param name [String, Symbol] name of the trait
|
483
|
+
# @param block [Block] the "content" of the trait
|
484
|
+
# @raise [ArgumentError] if block is absent
|
485
|
+
# @return [void]
|
486
|
+
def trait(name, &block)
|
487
|
+
raise ArgumentError, 'No block given in trait method' unless block
|
488
|
+
|
489
|
+
name = name.to_sym
|
490
|
+
@_traits[name] = block
|
491
|
+
end
|
492
|
+
|
458
493
|
# Set root key
|
459
494
|
#
|
460
495
|
# @param key [String, Symbol]
|
data/lib/alba/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: alba
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- OKURA Masafumi
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies: []
|
12
12
|
description: Alba is the fastest JSON serializer for Ruby. It focuses on performance,
|
13
13
|
flexibility and usability.
|
@@ -40,7 +40,7 @@ licenses:
|
|
40
40
|
metadata:
|
41
41
|
bug_tracker_uri: https://github.com/okuramasafumi/alba/issues
|
42
42
|
changelog_uri: https://github.com/okuramasafumi/alba/blob/main/CHANGELOG.md
|
43
|
-
documentation_uri: https://
|
43
|
+
documentation_uri: https://okuramasafumi.github.io/alba/
|
44
44
|
source_code_uri: https://github.com/okuramasafumi/alba
|
45
45
|
rubygems_mfa_required: 'true'
|
46
46
|
rdoc_options: []
|