typed_params 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: 532f36036d10c99438c7764f96edee2a81609957e91549be1d39c09eeac3b986
4
- data.tar.gz: 2780ca74ef0c99371dc87486134e37b25c8de93a4814f297d7d1955fab18ba32
3
+ metadata.gz: 014c42e82286c1ffc175d08aca51e631da96a5b2aee8e9bbf575fc0bea7e1d57
4
+ data.tar.gz: 3a0ea8bac6b1021143b768767763899a6f5328f16bb6011a87b2f1ed694b6325
5
5
  SHA512:
6
- metadata.gz: 52d65870463fa00390b94bb2b469f3c12dda164518d4a32ed3151c5f09303ceb546ab4fcc76388eb0c42902a19a64ba249eb44119492ab771175c876821e0d7e
7
- data.tar.gz: cd70318ed475aa00a8cbdafc89656957a93abe17c1108b0d39bca744102a1e91546d483168b8e5a9e8ac1ae9b58bb17870594d8611b8a05d46d46862f744d7ae
6
+ metadata.gz: c563758456e6359e37c75926124925141c59f535fc77070be23d46d3f86e0772f2cf2c52d2e57f165e2ba11f93717c6a0b15f90e61fcbe00baa19b3c1985f2a8
7
+ data.tar.gz: a277c1c13a5d0ac14ef293fbc9611bec82eba5df00686f9afaf8e324e89148aef7c49fa672bf025570a9482ec277e330edc1049a1e59051e41a559a31a4b6584
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.0
4
+
5
+ - Add `aliases:` keyword to `#param` to allow the schema to be accessible by different names.
6
+ - Add `polymorphic:` keyword to `#param` to define a polymorphic schema.
7
+
8
+ ## 1.1.1
9
+
10
+ - Fix compatibility with Ruby 3.3.0 due to [#20091](https://bugs.ruby-lang.org/issues/20091).
11
+
12
+ ## 1.1.0
13
+
14
+ - Add memoization to `#typed_params` and `#typed_query` methods.
15
+
3
16
  ## 1.0.3
4
17
 
5
18
  - Revert 0b0aaa6b66edd3e4c3336e51fa340592e7ef9e86.
@@ -18,4 +31,4 @@
18
31
 
19
32
  ## 0.2.0
20
33
 
21
- - Test release.
34
+ - Test release.
data/README.md CHANGED
@@ -110,6 +110,7 @@ _We're working on improving the docs._
110
110
  - Reuse schemas across controllers by defining named schemas.
111
111
  - Run validations on params, similar to active model validations.
112
112
  - Run transforms on params before they hit your controller.
113
+ - Support formatters such as JSON:API.
113
114
 
114
115
  ## Usage
115
116
 
@@ -435,6 +436,7 @@ Parameters can have validations, transforms, and more.
435
436
  - [`:optional`](#optional-parameter)
436
437
  - [`:if` and `:unless`](#conditional-parameter)
437
438
  - [`:as`](#rename-parameter)
439
+ - [`:alias`](#alias-parameter)
438
440
  - [`:noop`](#noop-parameter)
439
441
  - [`:coerce`](#coerce-parameter)
440
442
  - [`:allow_blank`](#allow-blank)
@@ -447,6 +449,7 @@ Parameters can have validations, transforms, and more.
447
449
  - [`:length`](#length-validation)
448
450
  - [`:transform`](#transform-parameter)
449
451
  - [`:validate`](#validate-parameter)
452
+ - [`:polymorphic`](#polymorphic-parameter)
450
453
 
451
454
  #### Parameter key
452
455
 
@@ -519,6 +522,17 @@ typed_params # => { user_id: 1 }
519
522
  In this example, the parameter would be accepted as `:user`, but renamed
520
523
  to `:user_id` for use inside of the controller.
521
524
 
525
+ #### Alias parameter
526
+
527
+ Allow a parameter to be provided via an alias.
528
+
529
+ ```ruby
530
+ param :owner_id, type: :integer, alias: :user_id
531
+ ```
532
+
533
+ In this example, the parameter would be accepted as both `:owner_id` and
534
+ `:user_id`, but accessible as `:owner_id` inside the controller.
535
+
522
536
  #### Noop parameter
523
537
 
524
538
  The parameter is accepted but immediately thrown out.
@@ -673,6 +687,33 @@ param :invalid, type: :string, validate: -> v {
673
687
  }
674
688
  ```
675
689
 
690
+ #### Polymorphic parameter
691
+
692
+ ***Currently, this option is only utilized by the JSONAPI formatter. Define
693
+ a polymorphic parameter, used when formatting JSONAPI relationships.***
694
+
695
+ ```ruby
696
+ format :jsonapi
697
+
698
+ param :data, type: :hash do
699
+ param :relationships, type: :hash do
700
+ param :owner, type: :hash, polymorphic: true do
701
+ param :data, type: :hash do
702
+ param :type, type: :string, inclusion: { in: %w[users user] }
703
+ param :id, type: :integer
704
+ end
705
+ end
706
+ end
707
+ end
708
+
709
+ typed_params # => { owner_type: 'User', owner_id: 1 }
710
+ ```
711
+
712
+ In this example, a polymorphic `:owner` relationship is defined. When run
713
+ through the JSONAPI formatter, instead of formatting the relationship
714
+ into the `:owner_id` key, it also includes the `:owner_type` key
715
+ for a polymorphic association.
716
+
676
717
  ### Shared options
677
718
 
678
719
  You can define a set of options that will be applied to immediate
@@ -747,7 +788,7 @@ which may be a nested schema.
747
788
 
748
789
  ```ruby
749
790
  # array of hashes
750
- param :boundless_array, type: :array do
791
+ param :endless_array, type: :array do
751
792
  items type: :hash do
752
793
  # ...
753
794
  end
@@ -14,7 +14,7 @@ module TypedParams
14
14
 
15
15
  def decorator? = decorator.present?
16
16
 
17
- delegate :arity, :call, to: :@transform
17
+ delegate :arity, :parameters, :call, to: :@transform
18
18
  end
19
19
  end
20
20
  end
@@ -32,12 +32,16 @@ module TypedParams
32
32
  # }
33
33
  #
34
34
  module JSONAPI
35
- def self.call(params)
35
+ def self.call(params, schema:)
36
36
  case params
37
37
  in data: Array => data
38
- format_array_data(data)
38
+ child = schema.children.fetch(:data)
39
+
40
+ format_array_data(data, schema: child)
39
41
  in data: Hash => data
40
- format_hash_data(data)
42
+ child = schema.children.fetch(:data)
43
+
44
+ format_hash_data(data, schema: child)
41
45
  else
42
46
  params
43
47
  end
@@ -45,11 +49,15 @@ module TypedParams
45
49
 
46
50
  private
47
51
 
48
- def self.format_array_data(data)
49
- data.map { format_hash_data(_1) }
52
+ def self.format_array_data(data, schema:)
53
+ data.each_with_index.map do |value, i|
54
+ child = schema.children.fetch(i) { schema.endless? ? schema.children.first : nil }
55
+
56
+ format_hash_data(value, schema: child)
57
+ end
50
58
  end
51
59
 
52
- def self.format_hash_data(data)
60
+ def self.format_hash_data(data, schema:)
53
61
  rels = data[:relationships]
54
62
  attrs = data[:attributes]
55
63
  res = data.except(
@@ -69,6 +77,8 @@ module TypedParams
69
77
  # relationship data only contains :type and :id, otherwise it
70
78
  # will use the x_attributes key.
71
79
  rels&.each do |key, rel|
80
+ child = schema.children.dig(:relationships, key)
81
+
72
82
  case rel
73
83
  # FIXME(ezekg) We need https://bugs.ruby-lang.org/issues/18961 to
74
84
  # clean this up (i.e. remove the if guard).
@@ -77,7 +87,7 @@ module TypedParams
77
87
  in data: []
78
88
  res[:"#{key.to_s.singularize}_ids"] = []
79
89
  # FIXME(ezekg) Not sure how to make this cleaner, but this handles polymorphic relationships.
80
- in data: { type:, id:, **nil } if key.to_s.underscore.classify != type.underscore.classify
90
+ in data: { type:, id:, **nil } if key.to_s.underscore.classify != type.underscore.classify && child.polymorphic?
81
91
  res[:"#{key}_type"], res[:"#{key}_id"] = type.underscore.classify, id
82
92
  in data: { type:, id:, **nil }
83
93
  res[:"#{key}_id"] = id
@@ -86,7 +96,7 @@ module TypedParams
86
96
  else
87
97
  # NOTE(ezekg) Embedded relationships are non-standard as per the
88
98
  # JSONAPI spec, but I don't really care. :)
89
- res[:"#{key}_attributes"] = call(rel)
99
+ res[:"#{key}_attributes"] = call(rel, schema: child)
90
100
  end
91
101
  end
92
102
 
@@ -47,7 +47,7 @@ module TypedParams
47
47
  # │ 1 ││ 2 │ │ 5 │
48
48
  # └───┘└───┘ └───┘
49
49
  #
50
- def depth_first_map(param, &)
50
+ def depth_first_map(param, &block)
51
51
  return if param.nil?
52
52
 
53
53
  # Postorder DFS, so we'll visit the children first.
@@ -55,12 +55,12 @@ module TypedParams
55
55
  case param.schema.children
56
56
  in Array if param.array?
57
57
  if param.schema.indexed?
58
- param.schema.children.each_with_index { |v, i| self.class.call(param[i], schema: v, controller:, &) }
58
+ param.schema.children.each_with_index { |v, i| self.class.call(param[i], schema: v, controller:, &block) }
59
59
  else
60
- param.value.each { |v| self.class.call(v, schema: param.schema.children.first, controller:, &) }
60
+ param.value.each { |v| self.class.call(v, schema: param.schema.children.first, controller:, &block) }
61
61
  end
62
62
  in Hash if param.hash?
63
- param.schema.children.each { |k, v| self.class.call(param[k], schema: v, controller:, &) }
63
+ param.schema.children.each { |k, v| self.class.call(param[k], schema: v, controller:, &block) }
64
64
  else
65
65
  end
66
66
  end
@@ -34,7 +34,7 @@ module TypedParams
34
34
  end
35
35
 
36
36
  klass.singleton_class.send(:alias_method, :"unmemoized_#{method_name}", method_name)
37
- klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
37
+ klass.class_eval <<~RUBY, __FILE__, __LINE__ + 1
38
38
  def self.#{method_name}(*args, **kwargs, &block)
39
39
  key = args.hash ^ kwargs.hash ^ block.hash
40
40
 
@@ -72,7 +72,7 @@ module TypedParams
72
72
  end
73
73
 
74
74
  klass.alias_method :"unmemoized_#{method_name}", method_name
75
- klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
75
+ klass.class_eval <<~RUBY, __FILE__, __LINE__ + 1
76
76
  def #{method_name}(*args, **kwargs, &block)
77
77
  key = args.hash ^ kwargs.hash ^ block.hash
78
78
 
@@ -75,7 +75,16 @@ module TypedParams
75
75
  if formatter.present?
76
76
  v = case formatter.arity
77
77
  when 2
78
- formatter.call(v, controller:)
78
+ case formatter.parameters
79
+ in [[:req, *], [:keyreq | :key, :controller], [:keyreq | :key, :schema]]
80
+ formatter.call(v, controller:, schema:)
81
+ in [[:req, *], [:keyreq | :key, :schema], [:keyreq | :key, :controller]]
82
+ formatter.call(v, schema:, controller:)
83
+ in [[:req, *], [:keyreq | :key, :controller]]
84
+ formatter.call(v, controller:)
85
+ in [[:req, *], [:keyreq | :key, :schema]]
86
+ formatter.call(v, schema:)
87
+ end
79
88
  when 1
80
89
  formatter.call(v)
81
90
  end
@@ -38,7 +38,7 @@ module TypedParams
38
38
 
39
39
  value.each_with_index do |v, i|
40
40
  unless schema.children.nil?
41
- child = schema.children.fetch(i) { schema.boundless? ? schema.children.first : nil }
41
+ child = schema.children.fetch(i) { schema.endless? ? schema.children.first : nil }
42
42
  if child.nil?
43
43
  raise UnpermittedParameterError.new('unpermitted parameter', path: Path.new(*param.path.keys, i), source: schema.source) if
44
44
  schema.strict?
@@ -63,7 +63,7 @@ module TypedParams
63
63
 
64
64
  value.each do |k, v|
65
65
  unless schema.children.nil?
66
- child = schema.children.fetch(k) { nil }
66
+ child = schema.children.fetch(k) { schema.children.values.find { _1.alias == k } }
67
67
  if child.nil?
68
68
  raise UnpermittedParameterError.new('unpermitted parameter', path: Path.new(*param.path.keys, k), source: schema.source) if
69
69
  schema.strict?
@@ -71,7 +71,7 @@ module TypedParams
71
71
  next
72
72
  end
73
73
 
74
- param[k] = Parameterizer.new(schema: child, parent: param).call(key: k, value: v)
74
+ param[child.key] = Parameterizer.new(schema: child, parent: param).call(key: child.key, value: v)
75
75
  else
76
76
  param[k] = Parameter.new(key: k, value: v, schema:, parent: param)
77
77
  end
@@ -10,6 +10,7 @@ module TypedParams
10
10
  :source,
11
11
  :type,
12
12
  :key,
13
+ :alias,
13
14
  :if,
14
15
  :unless
15
16
 
@@ -22,6 +23,7 @@ module TypedParams
22
23
  key: nil,
23
24
  optional: false,
24
25
  coerce: false,
26
+ polymorphic: false,
25
27
  allow_blank: false,
26
28
  allow_nil: false,
27
29
  allow_non_scalars: false,
@@ -36,6 +38,7 @@ module TypedParams
36
38
  if: nil,
37
39
  unless: nil,
38
40
  as: nil,
41
+ alias: nil,
39
42
  casing: TypedParams.config.key_transform,
40
43
  &block
41
44
  )
@@ -77,8 +80,10 @@ module TypedParams
77
80
  @strict = strict
78
81
  @parent = parent
79
82
  @key = key
83
+ @alias = binding.local_variable_get(:alias)
80
84
  @optional = optional
81
85
  @coerce = coerce && @type.coercable?
86
+ @polymorphic = polymorphic
82
87
  @allow_blank = key == ROOT || allow_blank
83
88
  @allow_nil = allow_nil
84
89
  @allow_non_scalars = allow_non_scalars
@@ -211,7 +216,7 @@ module TypedParams
211
216
 
212
217
  ##
213
218
  # params defines multiple like-parameters for a hash schema.
214
- def params(*keys, **kwargs, &) = keys.each { param(_1, **kwargs, &) }
219
+ def params(*keys, **kwargs, &block) = keys.each { param(_1, **kwargs, &block) }
215
220
 
216
221
  ##
217
222
  # item defines an indexed parameter for an array schema.
@@ -220,7 +225,7 @@ module TypedParams
220
225
  Types.array?(children)
221
226
 
222
227
  raise ArgumentError, "index #{key} has already been defined" if
223
- children[key].present? || boundless?
228
+ children[key].present? || endless?
224
229
 
225
230
  children << Schema.new(**options, **kwargs, key:, type:, strict:, source:, casing:, parent: self, &block)
226
231
  end
@@ -230,7 +235,7 @@ module TypedParams
230
235
  def items(**kwargs, &)
231
236
  item(0, **kwargs, &)
232
237
 
233
- boundless!
238
+ endless!
234
239
  end
235
240
 
236
241
  def path
@@ -261,12 +266,14 @@ module TypedParams
261
266
  def optional? = !!@optional
262
267
  def required? = !optional?
263
268
  def coerce? = !!@coerce
269
+ def polymorphic? = !!@polymorphic
270
+ def aliased? = !!@alias
264
271
  def allow_blank? = !!@allow_blank
265
272
  def allow_nil? = !!@allow_nil
266
273
  def allow_non_scalars? = !!@allow_non_scalars
267
274
  def nilify_blanks? = !!@nilify_blanks
268
- def boundless? = !!@boundless
269
- def indexed? = !boundless?
275
+ def endless? = !!@endless
276
+ def indexed? = !endless?
270
277
  def if? = !@if.nil?
271
278
  def unless? = !@unless.nil?
272
279
  def array? = Types.array?(type)
@@ -278,6 +285,9 @@ module TypedParams
278
285
  "#<#{self.class.name} key=#{key.inspect} type=#{type.inspect} children=#{children.inspect}>"
279
286
  end
280
287
 
288
+ # @private
289
+ def dig(*keys) = children.dig(*keys)
290
+
281
291
  private
282
292
 
283
293
  attr_reader :controller,
@@ -285,6 +295,6 @@ module TypedParams
285
295
  :strict,
286
296
  :casing
287
297
 
288
- def boundless! = @boundless = true
298
+ def endless! = @endless = true
289
299
  end
290
300
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TypedParams
4
- VERSION = '1.1.0'
4
+ VERSION = '1.2.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typed_params
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
  - Zeke Gabrielse
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-17 00:00:00.000000000 Z
11
+ date: 2024-01-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails