alba 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c68ec07a949642b11dfd36b3bf748249d0d725178ece9dd331e373fc2339cb8a
4
- data.tar.gz: 7704431d69f96b88d9632c114dae90cfceae018c8372c8736c091efd2050b65a
3
+ metadata.gz: 3ba9736fa95cae41e16375e21770a7244b1ee5af9787090a1dadbe3f50839221
4
+ data.tar.gz: cb06fc313a5abb4914fceb3eb044911bd556d11cfa2239b3509147f5f992ab9e
5
5
  SHA512:
6
- metadata.gz: 98ca1b6fa7c6851f3a69d6d1cb54cd782a3142fccfb33391eb08cbc093ad691496a8fdb9e93e5ce89543032a0003ab8b60022ca8bbbba81c73de249b571a3fa6
7
- data.tar.gz: 3756fe21da92358031a722d83b7169ef81e8749ccf6430bef577dfaac7ab4a80e68c07c6e0205497e54692f74b16e66aabad390db6eda09e486a732387541630
6
+ metadata.gz: 4814af5d6e0a69d34ba6f3c9ef9292e7a83196ff3d231851d799857518888e9db4bc075a0cbc88ae8e7e39aa20e724085224e1944b843edc7b685ef372cefa71
7
+ data.tar.gz: 9cb559556d0e44ea4218328d645f4666e0dd5cf63a4b380b64e7e6bf1460724ecd3e04b8eb138ac1aac30176a5d0491db1451e3bdaf2130771ab06bdbb2e4d50
data/.codeclimate.yml CHANGED
@@ -9,3 +9,4 @@ checks:
9
9
  exclude_patterns:
10
10
  - "benchmark/**/*"
11
11
  - "script/**/*"
12
+ - "test/**/*"
data/.rubocop.yml CHANGED
@@ -3,7 +3,12 @@
3
3
  inherit_gem:
4
4
  rubocop-sensible: 'config/rubocop.yml'
5
5
 
6
+ inherit_mode:
7
+ merge:
8
+ - Exclude
9
+
6
10
  require:
11
+ - rubocop-md
7
12
  - rubocop-minitest
8
13
  - rubocop-performance
9
14
  - rubocop-rake
@@ -22,6 +27,11 @@ AllCops:
22
27
  Bundler/GemVersion:
23
28
  Enabled: false
24
29
 
30
+ # Test class is a class, but not really
31
+ Layout/ClassStructure:
32
+ Exclude:
33
+ - 'test/**/*'
34
+
25
35
  # We'd like to write something like:
26
36
  # assert_equal(
27
37
  # expected,
@@ -80,14 +90,26 @@ Style/Copyright:
80
90
  Style/DisableCopsWithinSourceCodeDirective:
81
91
  Enabled: false
82
92
 
93
+ Style/Documentation:
94
+ Exclude:
95
+ - 'test/**/*'
96
+
97
+ Style/DocumentationMethod:
98
+ Exclude:
99
+ - 'README.md'
100
+
83
101
  Style/FrozenStringLiteralComment:
84
102
  Enabled: false
85
103
 
104
+ Style/ImplicitRuntimeError:
105
+ Exclude:
106
+ - 'README.md'
107
+
86
108
  Style/InlineComment:
87
109
  Enabled: false
88
110
 
89
111
  Style/MethodCallWithArgsParentheses:
90
- IgnoredMethods: ['require', 'require_relative', 'include', 'extend', 'puts', 'p', 'warn', 'raise', 'send', 'public_send']
112
+ AllowedMethods: ['require', 'require_relative', 'include', 'extend', 'puts', 'p', 'warn', 'raise', 'send', 'public_send']
91
113
  Exclude:
92
114
  # There are so many `attributes` call without parenthese and that's absolutely fine
93
115
  - 'test/**/*.rb'
@@ -95,3 +117,7 @@ Style/MethodCallWithArgsParentheses:
95
117
  # There are so many cases we just want `if` expression!
96
118
  Style/MissingElse:
97
119
  EnforcedStyle: case
120
+
121
+ Style/OptionalBooleanParameter:
122
+ Exclude:
123
+ - 'README.md'
data/CHANGELOG.md CHANGED
@@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.3.0] 2023-04-24
10
+
11
+ ### Added
12
+
13
+ - Add compatibility option for key [#304](https://github.com/okuramasafumi/alba/pull/304)
14
+ - It now infers resource name from Serializer [#309](https://github.com/okuramasafumi/alba/pull/309)
15
+ - `Alba.serialize` is easier to use for multiple root keys [#311](https://github.com/okuramasafumi/alba/pull/311)
16
+ - Gives access to params in nested_attribute [#312](https://github.com/okuramasafumi/alba/pull/312)
17
+ - Thank you, @GabrielErbetta
18
+
9
19
  ## [2.2.0] 2023-02-17
10
20
 
11
21
  ### Added
data/Gemfile CHANGED
@@ -10,6 +10,7 @@ gem 'minitest', '~> 5.14' # For test
10
10
  gem 'railties', require: false # For Rails integration testing
11
11
  gem 'rake', '~> 13.0' # For test and automation
12
12
  gem 'rubocop', '>= 0.79.0', require: false # For lint
13
+ gem 'rubocop-md', '~> 1.0', require: false # For lint
13
14
  gem 'rubocop-minitest', '>= 0.25.0', require: false # For lint
14
15
  gem 'rubocop-performance', '>= 1.15.0', require: false # For lint
15
16
  gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
data/HACKING.md CHANGED
@@ -17,6 +17,7 @@ Class methods (DSL):
17
17
  * `attribute` for block style attribute
18
18
  * `attributes` for symbol style attribute
19
19
  * `association` and its aliases such as `one` for association
20
+ * `nested` for nested attribute
20
21
 
21
22
  Instance methods:
22
23
 
@@ -29,7 +30,7 @@ Other methods are rather trivial. They'll be added to this list when it turned o
29
30
 
30
31
  In `Alba::Resource` module there are some things to note.
31
32
 
32
- `@object` is an object for serialization. It's either singular object or collection.
33
+ `@object` is an object for serialization. It's either a singular object or a collection.
33
34
 
34
35
  Attribute object can be either `Symbol`, `Proc`, `Alba::Association` or `Alba::TypedAttribute`.
35
36
 
data/README.md CHANGED
@@ -43,7 +43,7 @@ Alba is faster than most of the alternatives. We have a [benchmark](https://gith
43
43
 
44
44
  ### Easy
45
45
 
46
- Alba is easy to use because there are only a few methods to remember. It's also easy to understand due to clean and short codebase. Finally it's easy to extend since it provides some methods for override to change default behavior of Alba.
46
+ Alba is easy to use because there are only a few methods to remember. It's also easy to understand due to clean and small codebase. Finally it's easy to extend since it provides some methods for override to change default behavior of Alba.
47
47
 
48
48
  ### Feature rich
49
49
 
@@ -143,13 +143,27 @@ Alba.inflector = nil
143
143
  To check if inference is enabled etc, inspect the return value of `inflector`:
144
144
 
145
145
  ```ruby
146
- if Alba.inflector == nil
147
- puts "inflector not set"
146
+ if Alba.inflector.nil?
147
+ puts 'inflector not set'
148
148
  else
149
149
  puts "inflector is set to #{Alba.inflector}"
150
150
  end
151
151
  ```
152
152
 
153
+ ### Naming
154
+
155
+ Alba tries to infer resource name from class name like the following.
156
+
157
+ |Class name|Resource name|
158
+ | --- | --- |
159
+ | FooResource | Foo |
160
+ | FooSerializer | Foo |
161
+ | FooElse | FooElse |
162
+
163
+ Resource name is used as the default name of the root key, so you might want to name it ending with "Resource" or "Serializer"
164
+
165
+ When you use Alba with Rails, it's recommended to put your resource/serializer classes in corresponding directory such as `app/resources` or `app/serializers`.
166
+
153
167
  ### Simple serialization with root key
154
168
 
155
169
  You can define attributes with (yes) `attributes` macro with attribute names. If your attribute need some calculations, you can use `attribute` with block.
@@ -157,6 +171,7 @@ You can define attributes with (yes) `attributes` macro with attribute names. If
157
171
  ```ruby
158
172
  class User
159
173
  attr_accessor :id, :name, :email, :created_at, :updated_at
174
+
160
175
  def initialize(id, name, email)
161
176
  @id = id
162
177
  @name = name
@@ -356,11 +371,11 @@ class UserResource
356
371
 
357
372
  # Second proc works as a filter
358
373
  many :articles,
359
- proc { |articles, params, user|
360
- filter = params[:filter] || :odd?
361
- articles.select {|a| a.id.send(filter) && !user.banned }
362
- },
363
- resource: ArticleResource
374
+ proc { |articles, params, user|
375
+ filter = params[:filter] || :odd?
376
+ articles.select { |a| a.id.__send__(filter) && !user.banned }
377
+ },
378
+ resource: ArticleResource
364
379
  end
365
380
 
366
381
  user = User.new(1)
@@ -384,8 +399,8 @@ class UserResource
384
399
  attributes :id
385
400
 
386
401
  many :articles,
387
- key: 'my_articles', # Set key here
388
- resource: ArticleResource
402
+ key: 'my_articles', # Set key here
403
+ resource: ArticleResource
389
404
  end
390
405
  UserResource.new(user).serialize
391
406
  # => '{"id":1,"my_articles":[{"title":"Hello World!"}]}'
@@ -452,7 +467,7 @@ class FooResourceWithParamsOverride
452
467
 
453
468
  root_key :foo
454
469
 
455
- one :bar, resource: BarResource, params: { expose_secret: false }
470
+ one :bar, resource: BarResource, params: {expose_secret: false}
456
471
  end
457
472
 
458
473
  Baz = Struct.new(:data, :secret)
@@ -544,6 +559,48 @@ Alba.serialize(something)
544
559
 
545
560
  Although this might be useful sometimes, it's generally recommended to define a class for Resource.
546
561
 
562
+ #### Inline definition for multiple root keys
563
+
564
+ While Alba doesn't directly support multiple root keys, you can simulate it with `Alba.serialize`.
565
+
566
+ ```ruby
567
+ # Define foo and bar local variables here
568
+
569
+ Alba.serialize do
570
+ attribute :key1 do
571
+ FooResource.new(foo).to_h
572
+ end
573
+
574
+ attribute :key2 do
575
+ BarResource.new(bar).to_h
576
+ end
577
+ end
578
+ # => JSON containing "key1" and "key2" as root keys
579
+ ```
580
+
581
+ Note that we must use `to_h`, not `serialize`, with resources.
582
+
583
+ We can also generate a JSON with multiple root keys without making any class by the combination of `Alba.serialize` and `Alba.hashify`.
584
+
585
+ ```ruby
586
+ # Define foo and bar local variables here
587
+
588
+ Alba.serialize do
589
+ attribute :foo do
590
+ Alba.hashify(foo) do
591
+ attributes :id, :name # For example
592
+ end
593
+ end
594
+
595
+ attribute :bar do
596
+ Alba.hashify(bar) do
597
+ attributes :id
598
+ end
599
+ end
600
+ end
601
+ # => JSON containing "foo" and "bar" as root keys
602
+ ```
603
+
547
604
  ### Serializable Hash
548
605
 
549
606
  Instead of serializing to JSON, you can also output a Hash by calling `serializable_hash` or the `to_h` alias. Note also that the `serialize` method is aliased as `to_json`.
@@ -796,20 +853,15 @@ A custom inflector can be plugged in as follows.
796
853
  module CustomInflector
797
854
  module_function
798
855
 
799
- def camelize(string)
800
- end
856
+ def camelize(string); end
801
857
 
802
- def camelize_lower(string)
803
- end
858
+ def camelize_lower(string); end
804
859
 
805
- def dasherize(string)
806
- end
860
+ def dasherize(string); end
807
861
 
808
- def underscore(string)
809
- end
862
+ def underscore(string); end
810
863
 
811
- def classify(string)
812
- end
864
+ def classify(string); end
813
865
  end
814
866
 
815
867
  Alba.inflector = CustomInflector
@@ -926,7 +978,7 @@ class User
926
978
  end
927
979
 
928
980
  def email
929
- raise RuntimeError, 'Error!'
981
+ raise 'Error!'
930
982
  end
931
983
  end
932
984
 
@@ -1122,8 +1174,10 @@ Sometimes we want to serialize a collection into a Hash, not an Array. It's poss
1122
1174
  ```ruby
1123
1175
  class User
1124
1176
  attr_reader :id, :name
1177
+
1125
1178
  def initialize(id, name)
1126
- @id, @name = id, name
1179
+ @id = id
1180
+ @name = name
1127
1181
  end
1128
1182
  end
1129
1183
 
@@ -1289,8 +1343,8 @@ module AlbaExtension
1289
1343
  # Here attrs are an Array of Symbol
1290
1344
  def formatted_time_attributes(*attrs)
1291
1345
  attrs.each do |attr|
1292
- attribute attr do |object|
1293
- time = object.send(attr)
1346
+ attribute(attr) do |object|
1347
+ time = object.__send__(attr)
1294
1348
  time.strftime('%m/%d/%Y')
1295
1349
  end
1296
1350
  end
@@ -1347,13 +1401,14 @@ Alba currently doesn't support logging directly, but you can add your own loggin
1347
1401
 
1348
1402
  ```ruby
1349
1403
  module Logging
1350
- def serialize(...) # `...` was added in Ruby 2.7
1404
+ # `...` was added in Ruby 2.7
1405
+ def serialize(...)
1351
1406
  puts serializable_hash
1352
1407
  super(...)
1353
1408
  end
1354
1409
  end
1355
1410
 
1356
- FooResource.prepend Logging
1411
+ FooResource.prepend(Logging)
1357
1412
  FooResource.new(foo).serialize
1358
1413
  # => "{:id=>1}" is printed
1359
1414
  ```
data/benchmark/README.md CHANGED
@@ -10,72 +10,76 @@ Machine spec:
10
10
 
11
11
  |Key|Value|
12
12
  |---|---|
13
- |OS|macOS 12.2.1|
13
+ |OS|macOS 13.2.1|
14
14
  |CPU|Intel Corei7 Quad Core 2.3Ghz|
15
15
  |RAM|32GB|
16
- |Ruby|ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-darwin19]|
16
+ |Ruby|ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-darwin21]|
17
+
18
+ Library versions:
19
+
20
+ |Library|Version|
21
+ |---|---|
22
+ |alba|2.2.0|
23
+ |blueprinter|0.25.3|
24
+ |fast_serializer_ruby|0.6.9|
25
+ |jserializer|0.2.1|
26
+ |oj|3.14.2|
27
+ |simple_ams|0.2.6|
28
+ |representable|3.2.0|
29
+ |turbostreamer|1.10.0|
30
+ |jbuilder|2.11.5|
31
+ |panko_serializer|0.7.9|
32
+ |active_model_serializers|0.10.13|
17
33
 
18
34
  `benchmark-ips` with `Oj.optimize_rails`:
19
35
 
20
36
  ```
21
37
  Comparison:
22
- panko: 267.6 i/s
23
- rails: 111.2 i/s - 2.41x (± 0.00) slower
24
- jserializer: 106.2 i/s - 2.52x (± 0.00) slower
25
- alba: 102.8 i/s - 2.60x (± 0.00) slower
26
- turbostreamer: 99.9 i/s - 2.68x (± 0.00) slower
27
- jbuilder: 90.7 i/s - 2.95x (± 0.00) slower
28
- alba_inline: 90.0 i/s - 2.97x (± 0.00) slower
29
- primalize: 82.1 i/s - 3.26x (± 0.00) slower
30
- fast_serializer: 62.7 i/s - 4.27x (± 0.00) slower
31
- jsonapi_same_format: 59.5 i/s - 4.50x (± 0.00) slower
32
- jsonapi: 55.3 i/s - 4.84x (± 0.00) slower
33
- blueprinter: 54.1 i/s - 4.95x (± 0.00) slower
34
- representable: 35.9 i/s - 7.46x (± 0.00) slower
35
- simple_ams: 25.4 i/s - 10.53x (± 0.00) slower
36
- ams: 9.1 i/s - 29.39x (± 0.00) slower
38
+ panko: 310.4 i/s
39
+ jserializer: 120.6 i/s - 2.57x slower
40
+ turbostreamer: 117.3 i/s - 2.65x slower
41
+ rails: 114.0 i/s - 2.72x slower
42
+ alba_inline: 99.3 i/s - 3.13x slower
43
+ alba: 94.1 i/s - 3.30x slower
44
+ fast_serializer: 67.8 i/s - 4.58x slower
45
+ blueprinter: 57.6 i/s - 5.39x slower
46
+ representable: 36.3 i/s - 8.56x slower
47
+ simple_ams: 23.3 i/s - 13.32x slower
48
+ ams: 10.9 i/s - 28.53x slower
37
49
  ```
38
50
 
39
51
  `benchmark-ips` without `Oj.optimize_rails`:
40
52
 
41
53
  ```
42
54
  Comparison:
43
- panko: 283.8 i/s
44
- turbostreamer: 102.9 i/s - 2.76x (± 0.00) slower
45
- alba: 102.4 i/s - 2.77x (± 0.00) slower
46
- alba_inline: 98.7 i/s - 2.87x (± 0.00) slower
47
- jserializer: 93.3 i/s - 3.04x (± 0.00) slower
48
- fast_serializer: 60.1 i/s - 4.73x (± 0.00) slower
49
- blueprinter: 53.8 i/s - 5.28x (± 0.00) slower
50
- rails: 37.1 i/s - 7.65x (± 0.00) slower
51
- jbuilder: 37.1 i/s - 7.66x (± 0.00) slower
52
- primalize: 31.2 i/s - 9.08x (± 0.00) slower
53
- jsonapi_same_format: 28.3 i/s - 10.03x (± 0.00) slower
54
- representable: 27.8 i/s - 10.23x (± 0.00) slower
55
- jsonapi: 27.5 i/s - 10.34x (± 0.00) slower
56
- simple_ams: 16.9 i/s - 16.75x (± 0.00) slower
57
- ams: 8.3 i/s - 34.36x (± 0.00) slower
55
+ panko: 326.1 i/s
56
+ turbostreamer: 120.6 i/s - 2.70x slower
57
+ jserializer: 119.2 i/s - 2.74x slower
58
+ alba_inline: 104.3 i/s - 3.13x slower
59
+ alba: 102.2 i/s - 3.19x slower
60
+ fast_serializer: 66.9 i/s - 4.88x slower
61
+ blueprinter: 56.7 i/s - 5.75x slower
62
+ rails: 33.9 i/s - 9.63x slower
63
+ representable: 30.3 i/s - 10.77x slower
64
+ simple_ams: 16.4 i/s - 19.84x slower
65
+ ams: 9.4 i/s - 34.56x slower
58
66
  ```
59
67
 
60
68
  `benchmark-memory`:
61
69
 
62
70
  ```
63
71
  Comparison:
64
- panko: 230418 allocated
65
- alba: 733217 allocated - 3.18x more
66
- alba_inline: 748297 allocated - 3.25x more
67
- turbostreamer: 781008 allocated - 3.39x more
68
- jserializer: 819705 allocated - 3.56x more
69
- primalize: 1195163 allocated - 5.19x more
70
- fast_serializer: 1232385 allocated - 5.35x more
71
- rails: 1236761 allocated - 5.37x more
72
- blueprinter: 1588937 allocated - 6.90x more
73
- jbuilder: 1774157 allocated - 7.70x more
74
- jsonapi_same_format: 2132489 allocated - 9.25x more
75
- jsonapi: 2279958 allocated - 9.89x more
76
- representable: 2869166 allocated - 12.45x more
77
- ams: 4473161 allocated - 19.41x more
78
- simple_ams: 7868345 allocated - 34.15x more
72
+ panko: 242426 allocated
73
+ turbostreamer: 817568 allocated - 3.37x more
74
+ jserializer: 831705 allocated - 3.43x more
75
+ alba: 1072217 allocated - 4.42x more
76
+ alba_inline: 1084889 allocated - 4.48x more
77
+ fast_serializer: 1244385 allocated - 5.13x more
78
+ rails: 1272761 allocated - 5.25x more
79
+ blueprinter: 1680137 allocated - 6.93x more
80
+ representable: 2892425 allocated - 11.93x more
81
+ ams: 4479569 allocated - 18.48x more
82
+ simple_ams: 6957913 allocated - 28.70x more
79
83
  ```
80
84
 
81
- Conclusion: panko is extremely fast but it's a C extension gem. As pure Ruby gems, Alba, turbostreamer and jserializer are notably faster than others. With `Oj.optimize_rails` jbuilder and Rails standard serialization are also fast.
85
+ Conclusion: panko is extremely fast but it's a C extension gem. As pure Ruby gems, Alba, `turbostreamer` and `jserializer` are notably faster than others, but Alba is slightly slower than other two. With `Oj.optimize_rails`, `jbuilder` and Rails standard serialization are also fast.
data/docs/rails.md CHANGED
@@ -14,11 +14,13 @@ You might want to add some configurations to initializer file such as `alba.rb`
14
14
  ```ruby
15
15
  # alba.rb
16
16
  Alba.backend = :active_support
17
- Alba.enable_inference!(with: :active_support)
17
+ Alba.inflector = :active_support
18
18
  ```
19
19
 
20
20
  You can also use `:oj_rails` for backend if you prefer using Oj.
21
21
 
22
+ Alba 2.2 introduced new Rails integration so that you don't have to add initializer file for setting inflector. You still need to add initializer file if you want to set backend or configure inflector with something different from `active_support`.
23
+
22
24
  ## Rendering JSON
23
25
 
24
26
  You can render JSON with Rails in two ways. One way is to pass JSON String.
@@ -32,7 +32,7 @@ module Alba
32
32
  # @param params [Hash] user-given Hash for arbitrary data
33
33
  # @return [Hash]
34
34
  def to_h(target, within: nil, params: {})
35
- params = params.merge(@params) unless @params.empty?
35
+ params = params.merge(@params)
36
36
  @object = target.__send__(@name)
37
37
  @object = @condition.call(object, params, target) if @condition
38
38
  return if @object.nil?
@@ -20,7 +20,7 @@ module Alba
20
20
  return Alba::REMOVE_KEY unless condition_passes?(resource, object)
21
21
 
22
22
  fetched_attribute = yield(@body)
23
- return fetched_attribute if !with_two_arity_proc_condition
23
+ return fetched_attribute unless with_two_arity_proc_condition
24
24
 
25
25
  return Alba::REMOVE_KEY unless resource.instance_exec(object, attribute_from_association_body_or(fetched_attribute), &@condition)
26
26
 
@@ -8,12 +8,14 @@ module Alba
8
8
  @block = block
9
9
  end
10
10
 
11
- # @return [Hash]
12
- def value(object)
11
+ # @param object [Object] the object being serialized
12
+ # @param params [Hash] params Hash inherited from Resource
13
+ # @return [Hash] hash serialized from running the class body in the object
14
+ def value(object:, params:)
13
15
  resource_class = Alba.resource_class
14
16
  resource_class.transform_keys(@key_transformation)
15
17
  resource_class.class_eval(&@block)
16
- resource_class.new(object).serializable_hash
18
+ resource_class.new(object, params: params).serializable_hash
17
19
  end
18
20
  end
19
21
  end
data/lib/alba/resource.rb CHANGED
@@ -17,15 +17,21 @@ module Alba
17
17
  WITHIN_DEFAULT = Object.new.freeze
18
18
  private_constant :WITHIN_DEFAULT
19
19
 
20
+ # `setup` method is meta-programmatically defined here for performance.
20
21
  # @private
21
- def self.included(base)
22
+ def self.included(base) # rubocop:disable Metrics/MethodLength
22
23
  super
24
+ setup_method_body = 'private def _setup;'
23
25
  base.class_eval do
24
26
  # Initialize
25
27
  DSLS.each do |name, initial|
26
28
  instance_variable_set("@#{name}", initial.dup) unless instance_variable_defined?("@#{name}")
29
+ setup_method_body << "@#{name} = self.class.#{name};"
27
30
  end
31
+ base.define_method(:encode, Alba.encoder)
28
32
  end
33
+ setup_method_body << 'end'
34
+ base.class_eval(setup_method_body, __FILE__, __LINE__ + 1)
29
35
  base.include InstanceMethods
30
36
  base.extend ClassMethods
31
37
  end
@@ -41,7 +47,7 @@ module Alba
41
47
  @object = object
42
48
  @params = params
43
49
  @within = within
44
- DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.__send__(name)) }
50
+ _setup
45
51
  end
46
52
 
47
53
  # Serialize object into JSON string
@@ -80,7 +86,8 @@ module Alba
80
86
  # @param meta [Hash] metadata for this seialization
81
87
  # @return [Hash]
82
88
  def as_json(root_key: nil, meta: {})
83
- key = root_key.nil? ? fetch_key : root_key.to_s
89
+ key = root_key.nil? ? fetch_key : root_key
90
+ key = Alba.regularize_key(key)
84
91
  if key && !key.empty?
85
92
  h = {key => serializable_hash}
86
93
  hash_with_metadata(h, meta)
@@ -99,15 +106,9 @@ module Alba
99
106
 
100
107
  private
101
108
 
102
- def encode(hash)
103
- Alba.encoder.call(hash)
104
- end
105
-
106
109
  def _to_json(root_key, meta, options)
107
110
  options.reject! { |k, _| %i[layout prefixes template status].include?(k) } # Rails specific guard
108
- # TODO: use `filter_map` after dropping support of Ruby 2.6
109
- names = options.map { |k, v| k unless v.nil? }
110
- names.compact!
111
+ names = options.filter_map { |k, v| k unless v.nil? }
111
112
  unless names.empty?
112
113
  names.sort!
113
114
  names.map! { |s| "\"#{s}\"" }
@@ -134,7 +135,11 @@ module Alba
134
135
 
135
136
  def serializable_hash_for_collection
136
137
  if @_collection_key
137
- @object.to_h { |item| [item.public_send(@_collection_key).to_s, converter.call(item)] }
138
+ @object.to_h do |item|
139
+ k = item.public_send(@_collection_key)
140
+ key = Alba.regularize_key(k)
141
+ [key, converter.call(item)]
142
+ end
138
143
  else
139
144
  @object.each_with_object([], &collection_converter)
140
145
  end
@@ -147,34 +152,26 @@ module Alba
147
152
  end
148
153
 
149
154
  def _key_for_collection
150
- if Alba.inflector
151
- @_key_for_collection == true ? resource_name(pluralized: true) : @_key_for_collection.to_s
152
- else
153
- @_key_for_collection == true ? raise_root_key_inference_error : @_key_for_collection.to_s
154
- end
155
+ k = @_key_for_collection == true ? resource_name(pluralized: true) : @_key_for_collection
156
+ Alba.regularize_key(k)
155
157
  end
156
158
 
157
- # @return [String]
158
159
  def _key
159
- if Alba.inflector
160
- @_key == true ? resource_name(pluralized: false) : @_key.to_s
161
- else
162
- @_key == true ? raise_root_key_inference_error : @_key.to_s
163
- end
160
+ k = @_key == true ? resource_name(pluralized: false) : @_key
161
+ Alba.regularize_key(k)
164
162
  end
165
163
 
166
164
  def resource_name(pluralized: false)
167
- class_name = self.class.name
168
165
  inflector = Alba.inflector
169
- name = inflector.demodulize(class_name).delete_suffix('Resource')
166
+ raise Alba::Error, 'You must set inflector when setting root key as true.' unless inflector
167
+
168
+ class_name = self.class.name
169
+ suffix = class_name.end_with?('Resource') ? 'Resource' : 'Serializer'
170
+ name = inflector.demodulize(class_name).delete_suffix(suffix)
170
171
  underscore_name = inflector.underscore(name)
171
172
  pluralized ? inflector.pluralize(underscore_name) : underscore_name
172
173
  end
173
174
 
174
- def raise_root_key_inference_error
175
- raise Alba::Error, 'You must set inflector when setting root key as true.'
176
- end
177
-
178
175
  def transforming_root_key?
179
176
  @_transforming_root_key
180
177
  end
@@ -237,14 +234,16 @@ module Alba
237
234
  end
238
235
  end
239
236
 
240
- # @return [Symbol]
241
- def transform_key(key) # rubocop:disable Metrics/CyclomaticComplexity
242
- key = key.to_s
243
- return key if @_transform_type == :none || key.empty? # We can skip transformation
237
+ def transform_key(key)
238
+ return Alba.regularize_key(key) if @_transform_type == :none || key.nil? || key.empty? # We can skip transformation
244
239
 
245
240
  inflector = Alba.inflector
246
241
  raise Alba::Error, 'Inflector is nil. You must set inflector before transforming keys.' unless inflector
247
242
 
243
+ Alba.regularize_key(_transform_key(inflector, key.to_s))
244
+ end
245
+
246
+ def _transform_key(inflector, key)
248
247
  case @_transform_type # rubocop:disable Style/MissingElse
249
248
  when :camel then inflector.camelize(key)
250
249
  when :lower_camel then inflector.camelize_lower(key)
@@ -258,7 +257,8 @@ module Alba
258
257
  when Symbol then fetch_attribute_from_object_and_resource(obj, attribute)
259
258
  when Proc then instance_exec(obj, &attribute)
260
259
  when Alba::Association then yield_if_within(attribute.name.to_sym) { |within| attribute.to_h(obj, params: params, within: within) }
261
- when TypedAttribute, NestedAttribute then attribute.value(obj)
260
+ when TypedAttribute then attribute.value(obj)
261
+ when NestedAttribute then attribute.value(object: obj, params: params)
262
262
  when ConditionalAttribute then attribute.with_passing_condition(resource: self, object: obj) { |attr| fetch_attribute(obj, key, attr) }
263
263
  else
264
264
  raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
@@ -327,8 +327,9 @@ module Alba
327
327
 
328
328
  def assign_attributes(attrs, if_value)
329
329
  attrs.each do |attr_name|
330
- attr = if_value ? ConditionalAttribute.new(body: attr_name.to_sym, condition: if_value) : attr_name.to_sym
331
- @_attributes[attr_name.to_sym] = attr
330
+ attr_name = attr_name.to_sym
331
+ attr = if_value ? ConditionalAttribute.new(body: attr_name, condition: if_value) : attr_name
332
+ @_attributes[attr_name] = attr
332
333
  end
333
334
  end
334
335
  private :assign_attributes
data/lib/alba/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Alba
2
- VERSION = '2.2.0'.freeze
2
+ VERSION = '2.3.0'.freeze
3
3
  end
data/lib/alba.rb CHANGED
@@ -44,13 +44,23 @@ module Alba
44
44
  # @param block [Block] resource block
45
45
  # @return [String] serialized JSON string
46
46
  # @raise [ArgumentError] if block is absent or `with` argument's type is wrong
47
- def serialize(object, root_key: nil, &block)
48
- klass = block ? resource_class(&block) : infer_resource_class(object.class.name)
49
-
50
- resource = klass.new(object)
47
+ def serialize(object = nil, root_key: nil, &block)
48
+ resource = resource_with(object, &block)
51
49
  resource.serialize(root_key: root_key)
52
50
  end
53
51
 
52
+ # Hashify the object with inline definitions
53
+ #
54
+ # @param object [Object] the object to be serialized
55
+ # @param root_key [Symbol, nil, true]
56
+ # @param block [Block] resource block
57
+ # @return [String] serialized JSON string
58
+ # @raise [ArgumentError] if block is absent or `with` argument's type is wrong
59
+ def hashify(object = nil, root_key: nil, &block)
60
+ resource = resource_with(object, &block)
61
+ resource.as_json(root_key: root_key)
62
+ end
63
+
54
64
  # Enable inference for key and resource name
55
65
  #
56
66
  # @param with [Symbol, Class, Module] inflector
@@ -107,16 +117,45 @@ module Alba
107
117
  const_parent.const_get("#{inflector.classify(name)}Resource")
108
118
  end
109
119
 
120
+ # Configure Alba to symbolize keys
121
+ def symbolize_keys!
122
+ @symbolize_keys = true
123
+ end
124
+
125
+ # Configure Alba to stringify (not symbolize) keys
126
+ def stringify_keys!
127
+ @symbolize_keys = false
128
+ end
129
+
130
+ # Regularize key to be either Symbol or String depending on @symbolize_keys
131
+ # Returns nil if key is nil
132
+ #
133
+ # @param key [String, Symbol, nil]
134
+ # @return [Symbol, String, nil]
135
+ def regularize_key(key)
136
+ return if key.nil?
137
+
138
+ @symbolize_keys ? key.to_sym : key.to_s
139
+ end
140
+
110
141
  # Reset config variables
111
142
  # Useful for test cleanup
112
143
  def reset!
113
144
  @encoder = default_encoder
145
+ @symbolize_keys = false
114
146
  @_on_error = :raise
115
147
  @_on_nil = nil
116
148
  end
117
149
 
118
150
  private
119
151
 
152
+ # This method could be part of public API, but for now it's private
153
+ def resource_with(object, &block)
154
+ klass = block ? resource_class(&block) : infer_resource_class(object.class.name)
155
+
156
+ klass.new(object)
157
+ end
158
+
120
159
  def inflector_from(name_or_module)
121
160
  case name_or_module
122
161
  when nil then nil
@@ -126,8 +165,7 @@ module Alba
126
165
  when :dry
127
166
  require 'dry/inflector'
128
167
  Dry::Inflector.new
129
- else
130
- validate_inflector(name_or_module)
168
+ else validate_inflector(name_or_module)
131
169
  end
132
170
  end
133
171
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alba
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OKURA Masafumi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-02-17 00:00:00.000000000 Z
11
+ date: 2023-04-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Alba is the fastest JSON serializer for Ruby. It focuses on performance,
14
14
  flexibility and usability.
@@ -91,7 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
91
  - !ruby/object:Gem::Version
92
92
  version: '0'
93
93
  requirements: []
94
- rubygems_version: 3.4.6
94
+ rubygems_version: 3.4.10
95
95
  signing_key:
96
96
  specification_version: 4
97
97
  summary: Alba is the fastest JSON serializer for Ruby.