alba 1.1.0 → 1.2.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: 36890dfa4b9b73b60f1d6a09cf674de7ff7a6dd495ff4b74bbb3d048f9cf5a85
4
- data.tar.gz: 3e3d49619f646be866262e14e21e5fba11e2caafbc24064d57eb9c549cb64e4d
3
+ metadata.gz: bbb5260b5a6f3697f2e9c42ba198e3e17a1b0dc2aa0ea1bd2feb9ed4b18364a2
4
+ data.tar.gz: 794c146a493068865d7284de00bedda11c3b2fabd2ce7dccaa781e72181d957e
5
5
  SHA512:
6
- metadata.gz: fc7e025a035b41dadaab5300096cf920eb3557a4be900d31b514dfc66e8ae803b0fb63d77ec06174d72d70ad2e07da81c491502c8462ad306f2379720222fc65
7
- data.tar.gz: 741f5c1c69b2809aec51d2a2a139d4d8e00060c9aa174bf5fb68d5dbcec7996096aced5fb377d77fa147d7b6086a65191c8c764802c65bbd88d3806751ec0eb4
6
+ metadata.gz: 46d179154f3981879a4cda7f5ea0360d7eeb3481788d895acefcbb5df156fde10ee1b0eb78bd399ab247449ea85100aceaa2dcb757a54ba64cce9450d82d1825
7
+ data.tar.gz: a3ee5f28b7bbc626d4dcf92db32017eba85f60df42df02f07d464cdaa8c190f5c07914db5e9f7db21f73fd1356529add373ef2c79ac82038d22f2c5dd5ee273c
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: ''
5
+ labels: bug
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ ## Describe the bug
11
+ A clear and concise description of what the bug is.
12
+
13
+ ## To Reproduce
14
+ Steps to reproduce the behavior:
15
+
16
+ ## Expected behavior
17
+ A clear and concise description of what you expected to happen.
18
+
19
+ ## Actual behavior
20
+ A clear and concise description of what actually happened.
21
+
22
+ ## Environment
23
+ - Ruby version:
24
+
25
+ ## Additional context
26
+ Add any other context about the problem here.
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an idea for this project
4
+ title: ''
5
+ labels: enhancement
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ ## Is your feature request related to a problem? Please describe.
11
+ A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12
+
13
+ ## Describe the solution you'd like
14
+ A clear and concise description of what you want to happen.
15
+
16
+ ## Describe alternatives you've considered
17
+ A clear and concise description of any alternative solutions or features you've considered.
18
+
19
+ ## Additional context
20
+ Add any other context or screenshots about the feature request here.
@@ -0,0 +1,26 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: daily
7
+ time: "20:00"
8
+ open-pull-requests-limit: 10
9
+ ignore:
10
+ - dependency-name: rubocop
11
+ versions:
12
+ - 1.12.0
13
+ - 1.9.0
14
+ - dependency-name: rubocop-performance
15
+ versions:
16
+ - 1.10.0
17
+ - 1.10.2
18
+ - dependency-name: oj
19
+ versions:
20
+ - 3.11.3
21
+ - dependency-name: minitest
22
+ versions:
23
+ - 5.14.4
24
+ - dependency-name: activesupport
25
+ versions:
26
+ - 6.1.2
data/.rubocop.yml CHANGED
@@ -21,6 +21,15 @@ AllCops:
21
21
  Bundler/GemComment:
22
22
  Enabled: false
23
23
 
24
+ # We'd like to write something like:
25
+ # assert_equal(
26
+ # expected,
27
+ # actual
28
+ # )
29
+ Layout/RedundantLineBreak:
30
+ Exclude:
31
+ - 'test/**/*'
32
+
24
33
  Layout/SpaceInsideHashLiteralBraces:
25
34
  EnforcedStyle: no_space
26
35
 
data/.yardopts CHANGED
@@ -1,2 +1,4 @@
1
1
  --no-private
2
2
  --exclude lib/alba/version.rb
3
+ --embed-mixin ClassMethods
4
+ --embed-mixin InstanceMethods
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
+ ## [1.2.0] 2021-05-09
10
+
11
+ - [Fix] multiple word key inference [6c18e73]
12
+ - https://github.com/okuramasafumi/alba/pull/120
13
+ - Thank you @alfonsojimenez !
14
+ - [Feat] Add `Alba.enable_root_key_transformation!` [f172839]
15
+ - https://github.com/okuramasafumi/alba/pull/121
16
+ - [Feat] Implement type validation and auto conversion [cbe00c7]
17
+ - https://github.com/okuramasafumi/alba/pull/122
18
+
9
19
  ## [1.1.0] - 2021-04-23
10
20
 
11
21
  - [Feat] Implement circular associations control [71e1543]
data/Gemfile CHANGED
@@ -8,7 +8,7 @@ gem 'ffaker', require: false # For testing
8
8
  gem 'minitest', '~> 5.14' # For test
9
9
  gem 'rake', '~> 13.0' # For test and automation
10
10
  gem 'rubocop', '>= 0.79.0', require: false # For lint
11
- gem 'rubocop-minitest', '~> 0.11.0', require: false # For lint
11
+ gem 'rubocop-minitest', '~> 0.12.0', require: false # For lint
12
12
  gem 'rubocop-performance', '~> 1.11.0', require: false # For lint
13
13
  gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
14
14
  gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
data/README.md CHANGED
@@ -59,11 +59,6 @@ You can find the documentation on [RubyDoc](https://rubydoc.info/github/okuramas
59
59
 
60
60
  ## Features
61
61
 
62
- * Resource-based serialization
63
- * Arbitrary attribute definition
64
- * One and many association with the ability to define them inline
65
- * Adding condition and filter to association
66
- * Parameters can be injected and used in attributes and associations
67
62
  * Conditional attributes and associations
68
63
  * Selectable backend
69
64
  * Key transformation
@@ -71,6 +66,7 @@ You can find the documentation on [RubyDoc](https://rubydoc.info/github/okuramas
71
66
  * Error handling
72
67
  * Resource name inflection based on association name
73
68
  * Circular associations control
69
+ * Types for validation and conversion
74
70
  * No runtime dependencies
75
71
 
76
72
  ## Anti features
@@ -248,7 +244,7 @@ RestrictedFooResouce.new(foo).serialize
248
244
  end
249
245
  ```
250
246
 
251
- ### Attribute key transformation
247
+ ### Key transformation
252
248
 
253
249
  ** Note: You need to install `active_support` gem to use `transform_keys` DSL.
254
250
 
@@ -278,6 +274,39 @@ UserResourceCamel.new(user).serialize
278
274
  # => '{"id":1,"firstName":"Masafumi","lastName":"Okura"}'
279
275
  ```
280
276
 
277
+ You can also transform root key when:
278
+
279
+ * `Alba.enable_inference!` is called
280
+ * `key!` is called in Resource class
281
+ * `root` option of `transform_keys` is set to true or `Alba.enable_root_key_transformation!` is called.
282
+
283
+ ```ruby
284
+ Alba.enable_inference!
285
+
286
+ class BankAccount
287
+ attr_reader :account_number
288
+
289
+ def initialize(account_number)
290
+ @account_number = account_number
291
+ end
292
+ end
293
+
294
+ class BankAccountResource
295
+ include Alba::Resource
296
+
297
+ key!
298
+
299
+ attributes :account_number
300
+ transform_keys :dash, root: true
301
+ end
302
+
303
+ bank_account = BankAccount.new(123_456_789)
304
+ BankAccountResource.new(bank_account).serialize
305
+ # => '{"bank-account":{"account-number":123456789}}'
306
+ ```
307
+
308
+ This behavior to transform root key will become default at version 2.
309
+
281
310
  Supported transformation types are :camel, :lower_camel and :dash.
282
311
 
283
312
  ### Filtering attributes
@@ -457,6 +486,45 @@ You can control circular associations with `within` option. `within` option is a
457
486
 
458
487
  For more details, please refer to [test code](https://github.com/okuramasafumi/alba/blob/master/test/usecases/circular_association_test.rb)
459
488
 
489
+ ### Types
490
+
491
+ You can validate and convert input with types.
492
+
493
+ ```ruby
494
+ class User
495
+ attr_reader :id, :name, :age, :bio, :admin, :created_at
496
+
497
+ def initialize(id, name, age, bio = '', admin = false) # rubocop:disable Style/OptionalBooleanParameter
498
+ @id = id
499
+ @name = name
500
+ @age = age
501
+ @admin = admin
502
+ @bio = bio
503
+ @created_at = Time.new(2020, 10, 10)
504
+ end
505
+ end
506
+
507
+ class UserResource
508
+ include Alba::Resource
509
+
510
+ attributes :name, id: [String, true], age: [Integer, true], bio: String, admin: [:Boolean, true], created_at: [String, ->(object) { object.strftime('%F') }]
511
+ end
512
+
513
+ user = User.new(1, 'Masafumi OKURA', '32', 'Ruby dev')
514
+ UserResource.new(user).serialize
515
+ # => '{"name":"Masafumi OKURA","id":"1","age":32,"bio":"Ruby dev","admin":false,"created_at":"2020-10-10"}'
516
+ ```
517
+
518
+ Notice that `id` and `created_at` are converted to String and `age` is converted to Integer.
519
+
520
+ If type is not correct and auto conversion is disabled (default), `TypeError` occurs.
521
+
522
+ ```ruby
523
+ user = User.new(1, 'Masafumi OKURA', '32', nil) # bio is nil and auto conversion is disabled for bio
524
+ UserResource.new(user).serialize
525
+ # => TypeError, 'Attribute bio is expected to be String but actually nil.'
526
+ ```
527
+
460
528
  ### Caching
461
529
 
462
530
  Currently, Alba doesn't support caching, primarily due to the behavior of `ActiveRecord::Relation`'s cache. See [the issue](https://github.com/rails/rails/issues/41784).
data/SECURITY.md ADDED
@@ -0,0 +1,12 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ | ------- | ------------------ |
7
+ | 1.x.y | :white_check_mark: |
8
+ | < 1.0 | :x: |
9
+
10
+ ## Reporting a Vulnerability
11
+
12
+ If you find a vulnerability of Alba, please contact me (OKURA Masafumi) via [email](masafumi.o1988@gmail.com). I'll report back within a few days.
data/benchmark/local.rb CHANGED
@@ -17,6 +17,7 @@ gemfile(true) do
17
17
  gem "jbuilder"
18
18
  gem "jsonapi-serializer" # successor of fast_jsonapi
19
19
  gem "multi_json"
20
+ gem "primalize"
20
21
  gem "oj"
21
22
  gem "representable"
22
23
  gem "sqlite3"
@@ -213,6 +214,27 @@ class JsonApiSameFormatPostSerializer < JsonApiSameFormatSerializer
213
214
  end
214
215
  end
215
216
 
217
+ # --- Primalize serializers ---
218
+ #
219
+ class PrimalizeCommentResource < Primalize::Single
220
+ attributes id: integer, body: string
221
+ end
222
+
223
+ class PrimalizePostResource < Primalize::Single
224
+ alias post object
225
+
226
+ attributes(
227
+ id: integer,
228
+ body: string,
229
+ comments: array(primalize(PrimalizeCommentResource)),
230
+ commenter_names: array(string),
231
+ )
232
+
233
+ def commenter_names
234
+ post.commenters.pluck(:name)
235
+ end
236
+ end
237
+
216
238
  # --- Representable serializers ---
217
239
 
218
240
  require "representable"
@@ -265,6 +287,7 @@ blueprinter = Proc.new { PostBlueprint.render(post) }
265
287
  jbuilder = Proc.new { post.to_builder.target! }
266
288
  jsonapi = proc { JsonApiStandardPostSerializer.new(post).to_json }
267
289
  jsonapi_same_format = proc { JsonApiSameFormatPostSerializer.new(post).to_json }
290
+ primalize = proc { PrimalizePostResource.new(post).to_json }
268
291
  rails = Proc.new { ActiveSupport::JSON.encode(post.serializable_hash(include: :comments)) }
269
292
  representable = Proc.new { PostRepresenter.new(post).to_json }
270
293
 
@@ -279,6 +302,7 @@ puts "Serializer outputs ----------------------------------"
279
302
  jbuilder: jbuilder, # different order
280
303
  jsonapi: jsonapi, # nested JSON:API format
281
304
  jsonapi_same_format: jsonapi_same_format,
305
+ primalize: primalize,
282
306
  rails: rails,
283
307
  representable: representable
284
308
  }.each { |name, serializer| puts "#{name.to_s.ljust(24, ' ')} #{serializer.call}" }
@@ -295,6 +319,7 @@ Benchmark.bmbm do |x|
295
319
  x.report(:jbuilder) { time.times(&jbuilder) }
296
320
  x.report(:jsonapi) { time.times(&jsonapi) }
297
321
  x.report(:jsonapi_same_format) { time.times(&jsonapi_same_format) }
322
+ x.report(:primalize) { time.times(&primalize) }
298
323
  x.report(:rails) { time.times(&rails) }
299
324
  x.report(:representable) { time.times(&representable) }
300
325
  end
@@ -308,6 +333,7 @@ Benchmark.ips do |x|
308
333
  x.report(:jbuilder, &jbuilder)
309
334
  x.report(:jsonapi, &jsonapi)
310
335
  x.report(:jsonapi_same_format, &jsonapi_same_format)
336
+ x.report(:primalize, &primalize)
311
337
  x.report(:rails, &rails)
312
338
  x.report(:representable, &representable)
313
339
 
data/codecov.yml CHANGED
@@ -3,3 +3,6 @@ coverage:
3
3
  project:
4
4
  default:
5
5
  informational: true
6
+ patch:
7
+ default:
8
+ target: 90%
data/lib/alba.rb CHANGED
@@ -9,8 +9,11 @@ module Alba
9
9
  # Error class for backend which is not supported
10
10
  class UnsupportedBackend < Error; end
11
11
 
12
+ # Error class for type which is not supported
13
+ class UnsupportedType < Error; end
14
+
12
15
  class << self
13
- attr_reader :backend, :encoder, :inferring, :_on_error
16
+ attr_reader :backend, :encoder, :inferring, :_on_error, :transforming_root_key
14
17
 
15
18
  # Set the backend, which actually serializes object into JSON
16
19
  #
@@ -66,6 +69,16 @@ module Alba
66
69
  @_on_error = handler || block
67
70
  end
68
71
 
72
+ # Enable root key transformation
73
+ def enable_root_key_transformation!
74
+ @transforming_root_key = true
75
+ end
76
+
77
+ # Disable root key transformation
78
+ def disable_root_key_transformation!
79
+ @transforming_root_key = false
80
+ end
81
+
69
82
  private
70
83
 
71
84
  def set_encoder
@@ -109,4 +122,5 @@ module Alba
109
122
 
110
123
  @encoder = default_encoder
111
124
  @_on_error = :raise
125
+ @transforming_root_key = false # TODO: This will be true since 2.0
112
126
  end
data/lib/alba/resource.rb CHANGED
@@ -6,7 +6,7 @@ module Alba
6
6
  module Resource
7
7
  # @!parse include InstanceMethods
8
8
  # @!parse extend ClassMethods
9
- DSLS = {_attributes: {}, _key: nil, _transform_keys: nil, _on_error: nil}.freeze
9
+ DSLS = {_attributes: {}, _key: nil, _transform_keys: nil, _transforming_root_key: false, _on_error: nil}.freeze
10
10
  private_constant :DSLS
11
11
 
12
12
  # @private
@@ -58,13 +58,12 @@ module Alba
58
58
 
59
59
  # @return [String]
60
60
  def _key
61
- if @_key == true && Alba.inferring
62
- demodulized = ActiveSupport::Inflector.demodulize(self.class.name)
63
- meth = collection? ? :tableize : :singularize
64
- ActiveSupport::Inflector.public_send(meth, demodulized.delete_suffix('Resource').downcase)
65
- else
66
- @_key.to_s
67
- end
61
+ return @_key.to_s unless @_key == true && Alba.inferring
62
+
63
+ resource_name = self.class.name.demodulize.delete_suffix('Resource').underscore
64
+ key = collection? ? resource_name.pluralize : resource_name
65
+ transforming_root_key = @_transforming_root_key.nil? ? Alba.transforming_root_key : @_transforming_root_key
66
+ transforming_root_key ? transform_key(key) : key
68
67
  end
69
68
 
70
69
  def converter
@@ -76,7 +75,7 @@ module Alba
76
75
  else
77
76
  [key, fetch_attribute(object, attribute)]
78
77
  end
79
- rescue ::Alba::Error, FrozenError
78
+ rescue ::Alba::Error, FrozenError, TypeError
80
79
  raise
81
80
  rescue StandardError => e
82
81
  handle_error(e, object, key, attribute)
@@ -136,11 +135,55 @@ module Alba
136
135
  return unless within
137
136
 
138
137
  attribute.to_hash(object, params: params, within: within)
138
+ when Hash # Typed Attribute
139
+ typed_attribute(object, attribute)
139
140
  else
140
141
  raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
141
142
  end
142
143
  end
143
144
 
145
+ def typed_attribute(object, hash)
146
+ attr_name = hash[:attr_name]
147
+ type = hash[:type]
148
+ type_converter = hash[:type_converter]
149
+ value, result = type_check(object, attr_name, type)
150
+ return value if result
151
+ raise TypeError if !result && !type_converter
152
+
153
+ type_converter = type_converter_for(type) if type_converter == true
154
+ type_converter.call(value)
155
+ rescue TypeError
156
+ raise TypeError, "Attribute #{attr_name} is expected to be #{type} but actually #{value.nil? ? 'nil' : value.class.name}."
157
+ end
158
+
159
+ def type_check(object, attr_name, type)
160
+ value = object.public_send(attr_name)
161
+ type_correct = case type
162
+ when :String, ->(klass) { klass == String }
163
+ value.is_a?(String)
164
+ when :Integer, ->(klass) { klass == Integer }
165
+ value.is_a?(Integer)
166
+ when :Boolean
167
+ [true, false].include?(attr_name)
168
+ else
169
+ raise Alba::UnsupportedType, "Unknown type: #{type}"
170
+ end
171
+ [value, type_correct]
172
+ end
173
+
174
+ def type_converter_for(type)
175
+ case type
176
+ when :String, ->(klass) { klass == String }
177
+ ->(object) { object.to_s }
178
+ when :Integer, ->(klass) { klass == Integer }
179
+ ->(object) { Integer(object) }
180
+ when :Boolean
181
+ ->(object) { !!object }
182
+ else
183
+ raise Alba::UnsupportedType, "Unknown type: #{type}"
184
+ end
185
+ end
186
+
144
187
  def check_within
145
188
  case @within
146
189
  when Hash # Traverse within tree
@@ -177,11 +220,19 @@ module Alba
177
220
  #
178
221
  # @param attrs [Array<String, Symbol>]
179
222
  # @param options [Hash] option hash including `if` that is a condition to render these attributes
180
- def attributes(*attrs, **options)
223
+ def attributes(*attrs, if: nil, **attrs_with_types) # rubocop:disable Naming/MethodParameterName
224
+ if_value = binding.local_variable_get(:if)
181
225
  attrs.each do |attr_name|
182
- attr = options[:if] ? [attr_name.to_sym, options[:if]] : attr_name.to_sym
226
+ attr = if_value ? [attr_name.to_sym, if_value] : attr_name.to_sym
183
227
  @_attributes[attr_name.to_sym] = attr
184
228
  end
229
+ attrs_with_types.each do |attr_name, type_and_converter|
230
+ attr_name = attr_name.to_sym
231
+ type, type_converter = type_and_converter
232
+ typed_attr = {attr_name: attr_name, type: type, type_converter: type_converter}
233
+ attr = if_value ? [typed_attr, if_value] : typed_attr
234
+ @_attributes[attr_name] = attr
235
+ end
185
236
  end
186
237
 
187
238
  # Set an attribute with the given block
@@ -254,8 +305,10 @@ module Alba
254
305
  # Transform keys as specified type
255
306
  #
256
307
  # @param type [String, Symbol]
257
- def transform_keys(type)
308
+ # @param root [Boolean] decides if root key also should be transformed
309
+ def transform_keys(type, root: nil)
258
310
  @_transform_keys = type.to_sym
311
+ @_transforming_root_key = root
259
312
  end
260
313
 
261
314
  # Set error handler
data/lib/alba/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Alba
2
- VERSION = '1.1.0'.freeze
2
+ VERSION = '1.2.0'.freeze
3
3
  end
data/sider.yml CHANGED
@@ -49,10 +49,8 @@ linter:
49
49
  # norc: true
50
50
 
51
51
  # # https://help.sider.review/getting-started/custom-configuration#ignore
52
- # ignore:
53
- # - "*.pdf"
54
- # - "*.mp4"
55
- # - "images/**"
52
+ ignore:
53
+ - 'test/**/*'
56
54
 
57
55
  # # https://help.sider.review/getting-started/custom-configuration#branchesexclude
58
56
  # branches:
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: 1.1.0
4
+ version: 1.2.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: 2021-04-23 00:00:00.000000000 Z
11
+ date: 2021-05-09 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Alba is designed to be a simple, easy to use and fast alternative to
14
14
  existing JSON serializers. Its performance is better than almost all gems which
@@ -19,6 +19,9 @@ executables: []
19
19
  extensions: []
20
20
  extra_rdoc_files: []
21
21
  files:
22
+ - ".github/ISSUE_TEMPLATE/bug_report.md"
23
+ - ".github/ISSUE_TEMPLATE/feature_request.md"
24
+ - ".github/dependabot.yml"
22
25
  - ".github/workflows/main.yml"
23
26
  - ".gitignore"
24
27
  - ".rubocop.yml"
@@ -29,6 +32,7 @@ files:
29
32
  - LICENSE.txt
30
33
  - README.md
31
34
  - Rakefile
35
+ - SECURITY.md
32
36
  - alba.gemspec
33
37
  - benchmark/local.rb
34
38
  - bin/console