easy_params 0.4.1 → 0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6c3709594b8141aa9bc0d851a2f1eabcf40b7528637ad3d055c76e3ad7db1ac8
4
- data.tar.gz: da9839e5b6f8242fe05ad074868fb23a3181840b723333b7fa66f9717efca4d7
3
+ metadata.gz: c59e34205e7543bb5d58358d8e93b92ff911cacac58b4dbd55069b1398d10691
4
+ data.tar.gz: 7ba6653bb5b7ecd3e10d3e23be396f9482b0cf336f8807d5cc09a8d9fa1b9750
5
5
  SHA512:
6
- metadata.gz: 784db4e629c9800db787546346c9dd555ec277f578ecebfa9d59d5d76bb0532612d04a9e711b0f76a5040c9257673bf9e2c97ca85cb47bb1addeb67b7549310d
7
- data.tar.gz: 20ced440392006961a46feab7eea99b2a71dc752dbf4f04559041f5fb9f7aa861524faec9d50cab56b3c13ca29dff7ec9dd192012289ed84061485a7ad1f094a
6
+ metadata.gz: 6b6b302946b9eb92cbcedf61a6eae529030154ec251ce4a98928d0cd31cffb069e43ad2fd98f4768ca208ea1f7f65e5cf0be89fa16bce1d3fd7f2c02d8d837c1
7
+ data.tar.gz: ee6bdb5edaf5a37230c631036936a3dc8a520aa0ed5a344ad69cdf03ff9d77d817f2c346d0b8a27ca0a421f6c25f16a80190262ead23877af6a7a4f7a706a8ad
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.1
data/Gemfile CHANGED
@@ -5,6 +5,7 @@ source 'https://rubygems.org'
5
5
  # Specify your gem's dependencies in easy_params.gemspec
6
6
  gemspec
7
7
 
8
+ gem 'ostruct'
8
9
  gem 'pry'
9
10
  gem 'rake', '~> 12.0'
10
11
  gem 'rspec', '~> 3.0'
data/Gemfile.lock CHANGED
@@ -1,120 +1,111 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- easy_params (0.4.1)
5
- activemodel (>= 3.2, < 8)
6
- dry-struct (~> 1.4)
7
- dry-types (~> 1.5)
4
+ easy_params (0.6.0)
5
+ activemodel (>= 3.2)
8
6
 
9
7
  GEM
10
8
  remote: https://rubygems.org/
11
9
  specs:
12
- activemodel (7.1.2)
13
- activesupport (= 7.1.2)
14
- activesupport (7.1.2)
10
+ activemodel (8.0.2)
11
+ activesupport (= 8.0.2)
12
+ activesupport (8.0.2)
15
13
  base64
14
+ benchmark (>= 0.3)
16
15
  bigdecimal
17
- concurrent-ruby (~> 1.0, >= 1.0.2)
16
+ concurrent-ruby (~> 1.0, >= 1.3.1)
18
17
  connection_pool (>= 2.2.5)
19
18
  drb
20
19
  i18n (>= 1.6, < 2)
20
+ logger (>= 1.4.2)
21
21
  minitest (>= 5.1)
22
- mutex_m
23
- tzinfo (~> 2.0)
24
- ast (2.4.2)
25
- base64 (0.2.0)
26
- bigdecimal (3.1.5)
22
+ securerandom (>= 0.3)
23
+ tzinfo (~> 2.0, >= 2.0.5)
24
+ uri (>= 0.13.1)
25
+ ast (2.4.3)
26
+ base64 (0.3.0)
27
+ benchmark (0.4.1)
28
+ bigdecimal (3.2.2)
27
29
  coderay (1.1.3)
28
- concurrent-ruby (1.2.2)
29
- connection_pool (2.4.1)
30
- diff-lcs (1.4.4)
31
- docile (1.3.5)
32
- drb (2.2.0)
33
- ruby2_keywords
34
- dry-core (1.0.1)
30
+ concurrent-ruby (1.3.5)
31
+ connection_pool (2.5.3)
32
+ diff-lcs (1.6.2)
33
+ docile (1.4.1)
34
+ drb (2.2.3)
35
+ i18n (1.14.7)
35
36
  concurrent-ruby (~> 1.0)
36
- zeitwerk (~> 2.6)
37
- dry-inflector (1.0.0)
38
- dry-logic (1.5.0)
39
- concurrent-ruby (~> 1.0)
40
- dry-core (~> 1.0, < 2)
41
- zeitwerk (~> 2.6)
42
- dry-struct (1.6.0)
43
- dry-core (~> 1.0, < 2)
44
- dry-types (>= 1.7, < 2)
45
- ice_nine (~> 0.11)
46
- zeitwerk (~> 2.6)
47
- dry-types (1.7.2)
48
- bigdecimal (~> 3.0)
49
- concurrent-ruby (~> 1.0)
50
- dry-core (~> 1.0)
51
- dry-inflector (~> 1.0)
52
- dry-logic (~> 1.4)
53
- zeitwerk (~> 2.6)
54
- i18n (1.14.1)
55
- concurrent-ruby (~> 1.0)
56
- ice_nine (0.11.2)
57
- json (2.6.3)
58
- method_source (1.0.0)
59
- minitest (5.21.1)
60
- mutex_m (0.2.0)
61
- parallel (1.23.0)
62
- parser (3.2.2.3)
37
+ json (2.13.2)
38
+ language_server-protocol (3.17.0.5)
39
+ lint_roller (1.1.0)
40
+ logger (1.7.0)
41
+ method_source (1.1.0)
42
+ minitest (5.25.5)
43
+ ostruct (0.6.2)
44
+ parallel (1.27.0)
45
+ parser (3.3.9.0)
63
46
  ast (~> 2.4.1)
64
47
  racc
65
- pry (0.14.2)
48
+ prism (1.4.0)
49
+ pry (0.15.2)
66
50
  coderay (~> 1.1)
67
51
  method_source (~> 1.0)
68
- racc (1.7.1)
52
+ racc (1.8.1)
69
53
  rainbow (3.1.1)
70
54
  rake (12.3.3)
71
- regexp_parser (2.8.1)
72
- rexml (3.2.5)
73
- rspec (3.10.0)
74
- rspec-core (~> 3.10.0)
75
- rspec-expectations (~> 3.10.0)
76
- rspec-mocks (~> 3.10.0)
77
- rspec-core (3.10.0)
78
- rspec-support (~> 3.10.0)
79
- rspec-expectations (3.10.0)
55
+ regexp_parser (2.10.0)
56
+ rspec (3.13.1)
57
+ rspec-core (~> 3.13.0)
58
+ rspec-expectations (~> 3.13.0)
59
+ rspec-mocks (~> 3.13.0)
60
+ rspec-core (3.13.5)
61
+ rspec-support (~> 3.13.0)
62
+ rspec-expectations (3.13.5)
80
63
  diff-lcs (>= 1.2.0, < 2.0)
81
- rspec-support (~> 3.10.0)
82
- rspec-mocks (3.10.0)
64
+ rspec-support (~> 3.13.0)
65
+ rspec-mocks (3.13.5)
83
66
  diff-lcs (>= 1.2.0, < 2.0)
84
- rspec-support (~> 3.10.0)
85
- rspec-support (3.10.0)
67
+ rspec-support (~> 3.13.0)
68
+ rspec-support (3.13.4)
86
69
  rspec_vars_helper (0.1.0)
87
70
  rspec (>= 2.4)
88
- rubocop (1.52.1)
71
+ rubocop (1.79.0)
89
72
  json (~> 2.3)
73
+ language_server-protocol (~> 3.17.0.2)
74
+ lint_roller (~> 1.1.0)
90
75
  parallel (~> 1.10)
91
- parser (>= 3.2.2.3)
76
+ parser (>= 3.3.0.2)
92
77
  rainbow (>= 2.2.2, < 4.0)
93
- regexp_parser (>= 1.8, < 3.0)
94
- rexml (>= 3.2.5, < 4.0)
95
- rubocop-ast (>= 1.28.0, < 2.0)
78
+ regexp_parser (>= 2.9.3, < 3.0)
79
+ rubocop-ast (>= 1.46.0, < 2.0)
96
80
  ruby-progressbar (~> 1.7)
97
- unicode-display_width (>= 2.4.0, < 3.0)
98
- rubocop-ast (1.29.0)
99
- parser (>= 3.2.1.0)
81
+ tsort (>= 0.2.0)
82
+ unicode-display_width (>= 2.4.0, < 4.0)
83
+ rubocop-ast (1.46.0)
84
+ parser (>= 3.3.7.2)
85
+ prism (~> 1.4)
100
86
  ruby-progressbar (1.13.0)
101
- ruby2_keywords (0.0.5)
102
- simplecov (0.21.2)
87
+ securerandom (0.4.1)
88
+ simplecov (0.22.0)
103
89
  docile (~> 1.1)
104
90
  simplecov-html (~> 0.11)
105
91
  simplecov_json_formatter (~> 0.1)
106
- simplecov-html (0.12.3)
107
- simplecov_json_formatter (0.1.2)
92
+ simplecov-html (0.13.2)
93
+ simplecov_json_formatter (0.1.4)
94
+ tsort (0.2.0)
108
95
  tzinfo (2.0.6)
109
96
  concurrent-ruby (~> 1.0)
110
- unicode-display_width (2.4.2)
111
- zeitwerk (2.6.12)
97
+ unicode-display_width (3.1.4)
98
+ unicode-emoji (~> 4.0, >= 4.0.4)
99
+ unicode-emoji (4.0.4)
100
+ uri (1.0.3)
112
101
 
113
102
  PLATFORMS
103
+ arm64-darwin-24
114
104
  ruby
115
105
 
116
106
  DEPENDENCIES
117
107
  easy_params!
108
+ ostruct
118
109
  pry
119
110
  rake (~> 12.0)
120
111
  rspec (~> 3.0)
@@ -123,4 +114,4 @@ DEPENDENCIES
123
114
  simplecov (~> 0.17)
124
115
 
125
116
  BUNDLED WITH
126
- 2.2.3
117
+ 2.7.1
data/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # EasyParams
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/nina.svg)](https://badge.fury.io/rb/nina)
3
+ [![Gem Version](https://badge.fury.io/rb/easy_params.svg)](https://badge.fury.io/rb/easy_params)
4
4
  [![Maintainability](https://api.codeclimate.com/v1/badges/17872804ce576b8b0df2/maintainability)](https://codeclimate.com/github/andriy-baran/easy_params/maintainability)
5
5
  [![Test Coverage](https://api.codeclimate.com/v1/badges/17872804ce576b8b0df2/test_coverage)](https://codeclimate.com/github/andriy-baran/easy_params/test_coverage)
6
6
 
7
- Provides an easy way define structure, validation rules, type coercion and set default values for any hash-like structure. It's built on top of `dry-types`, `dry-structure` and `active_model/validations`.
7
+ Provides an easy way to define structure, validation rules, type coercion, and default values for any hash-like structure. It's built on top of `ActiveModel`.
8
8
 
9
9
  ## Types
10
10
 
11
- Dry types are wrapped by class methods. Avaliable types: `integer`, `decimal`, `float`, `bool`, `string`, `array`, `date`, `datetime`, `time`, `struct`
11
+ Available types: `integer`, `decimal`, `float`, `bool`, `string`, `array`, `date`, `datetime`, `time`
12
12
 
13
13
  ## Installation
14
14
 
@@ -28,24 +28,73 @@ Or install it yourself as:
28
28
 
29
29
  ## Usage
30
30
 
31
+ To define attribute we have a set of methods which match types list. Ex.
31
32
  ```ruby
32
- # app/params/api/v2/icqa/move_params.rb
33
- class Api::V1::Carts::MoveParams < EasyParams::Base
34
- attribute :receive_cart_id, integer
35
- attribute :i_am_sure, bool
36
- attribute :location_code, string.default('')
37
- attribute :sections, struct do
38
- attribute :from, string
39
- attribute :to, string
33
+ integer(param_name, default: nil, normalize: nil, **validations)
34
+ ```
35
+ * `:default` provides a value to return if we get `nil` as input or there were errors during coercion.
36
+ * `:normalize` is a Proc or lambda that accepts a single argument and transforms it. It gets called before coercion.
37
+ * `validations` mimic ActiveModel validations; can be any supported validation, e.g., `presence: true, numericality: { only_integer: true, greater_than: 0 }`
38
+
39
+ In addition, there is a special option for the `array` type:
40
+ * `:of` accepts `:integer`, `:decimal`, `:float`, `:bool`, `:string`, `:date`, `:datetime`, `:time` (`:array` is not supported)
41
+
42
+ There are two special types:
43
+
44
+ | type | method to define | default |
45
+ |-------------------|------------------|---------|
46
+ | :struct | has | nil |
47
+ | :array_of_structs | each | [] |
48
+
49
+ ### Defaults for nested types
50
+
51
+ - **has (struct)**: `default:` must be a Hash. When the input is `nil`, the nested struct is instantiated with that hash; otherwise the provided input is used. If no `default:` is given and the input is `nil`, the value will be `nil`.
52
+
53
+ ```ruby
54
+ has :shipping_address, default: { country: 'US' } do
55
+ string :country, default: 'US'
56
+ string :city
57
+ end
58
+ ```
59
+
60
+ - **each (array_of_structs)**: `default:` should be an Array (typically an array of hashes). When the input is `nil`, the collection defaults to an empty array `[]`. If you provide a default array, each element will be coerced into the nested struct.
61
+
62
+ ```ruby
63
+ each :items, default: [{ qty: 1 }] do
64
+ integer :qty, default: 1
65
+ end
66
+ ```
67
+
68
+ - **Override precedence**: Container-level defaults override attribute-level defaults for the same keys. Attribute defaults apply only when the key is absent (or `nil`) in the provided default/input.
69
+
70
+ ```ruby
71
+ has :user, default: { role: 'admin' } do
72
+ string :role, default: 'guest'
73
+ string :name, default: 'Anonymous'
40
74
  end
41
- attribute :options, array.of(struct) do
42
- attribute :option_type_count, integer
43
- attribute :option_type_value, integer
75
+ # input: nil => role: 'admin', name: 'Anonymous'
44
76
 
45
- validates :option_type_count, :option_type_value, presence: { message: "can't be blank" }
77
+ each :items, default: [{ qty: 2 }, {}] do
78
+ integer :qty, default: 1
46
79
  end
80
+ # input: nil => items.map(&:qty) == [2, 1]
81
+ ```
47
82
 
48
- validates :receive_cart_id, :location_code, presence: { message: "can't be blank" }
83
+ ```ruby
84
+ # app/params/api/v2/icqa/move_params.rb
85
+ class Api::V1::Carts::MoveParams < EasyParams::Base
86
+ integer :receive_cart_id, presence: { message: "can't be blank" }
87
+ bool :i_am_sure
88
+ string :location_code, default: '', presence: { message: "can't be blank" }
89
+ has :section do
90
+ string :from
91
+ string :to
92
+ end
93
+ array :variant_ids, of: :integer
94
+ each :options do
95
+ integer :option_type_count, presence: { message: "can't be blank" }
96
+ integer :option_type_value, presence: { message: "can't be blank" }
97
+ end
49
98
  end
50
99
  ```
51
100
  Validation messages for nested attributes will look like this.
@@ -57,21 +106,6 @@ Validation messages for nested attributes will look like this.
57
106
  :"post.sections[0].id"=>an_instance_of(Array)
58
107
  }
59
108
  ```
60
- Optionally you can use more compact form
61
- ```ruby
62
- class MyParams < EasyParams::Base
63
- extend EasyParams::DSL
64
-
65
- quantity integer.default(1)
66
- posts each do
67
- content string.default('')
68
- end
69
- user has do
70
- role string
71
- end
72
- end
73
- ```
74
- This hovewer has some limitations: for attributes have name like `integer`, `decimal`, `float`, `bool`, `string`, `array`, `date`, `datetime`, `time`, `struct` it won't work as expected.
75
109
 
76
110
  More examples here [params_spec.rb](https://github.com/andriy-baran/easy_params/blob/master/spec/easy_params_spec.rb)
77
111
 
@@ -83,7 +117,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
83
117
 
84
118
  ## Contributing
85
119
 
86
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/easy_params. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/easy_params/blob/master/CODE_OF_CONDUCT.md).
120
+ Bug reports and pull requests are welcome on GitHub at [github.com/andriy-baran/easy_params](https://github.com/andriy-baran/easy_params). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/andriy-baran/easy_params/blob/master/CODE_OF_CONDUCT.md).
87
121
 
88
122
 
89
123
  ## License
@@ -92,4 +126,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
92
126
 
93
127
  ## Code of Conduct
94
128
 
95
- Everyone interacting in the EasyParams project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/easy_params/blob/master/CODE_OF_CONDUCT.md).
129
+ Everyone interacting in the EasyParams project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/andriy-baran/easy_params/blob/master/CODE_OF_CONDUCT.md).
data/easy_params.gemspec CHANGED
@@ -29,11 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
30
  spec.require_paths = ['lib']
31
31
 
32
- version_string = ['>= 3.2', '< 8']
32
+ version_string = ['>= 3.2']
33
33
 
34
- spec.add_runtime_dependency 'activemodel', version_string
35
-
36
- # spec.add_dependency 'dry-logic', '~> 0.4.2'
37
- spec.add_dependency 'dry-struct', '~> 1.4'
38
- spec.add_dependency 'dry-types', '~> 1.5'
34
+ spec.add_dependency 'activemodel', version_string
39
35
  end
@@ -2,99 +2,92 @@
2
2
 
3
3
  module EasyParams
4
4
  # Implements validations logic and nesting structures
5
- class Base < Dry::Struct
6
- include ActiveModel::Validations
5
+ class Base
6
+ include ActiveModel::Model
7
+ include EasyParams::Types::Struct
8
+ include EasyParams::Validation
7
9
 
8
- transform_keys(&:to_sym)
10
+ attr_writer :default
9
11
 
10
- def self.name
11
- 'EasyParams::Base'
12
+ def initialize(params = {})
13
+ self.class.schema.each do |attr, type|
14
+ public_send("#{attr}=", type.coerce(params.to_h[attr]))
15
+ end
12
16
  end
13
17
 
14
- validate do
15
- validate_nested
16
- end
18
+ class << self
19
+ def name
20
+ 'EasyParams::Base'
21
+ end
17
22
 
18
- # %w[Integer Decimal Float Bool String Date DateTime Time Array Struct StructDSL].each do |type|
19
- %w[Integer Decimal Float Bool String Date DateTime Time].each do |type_name|
20
- send(:define_singleton_method,
21
- type_name.underscore) do |param_name, default: nil, normalize: nil, optional: nil, **validations|
22
- type = EasyParams::Types.const_get(type_name)
23
- type = type.default(default) if default
24
- type = type.meta(omittable: true) if optional
25
- type = type.constructor { |value| value == Dry::Types::Undefined ? value : normalize.call(value) } if normalize
26
- validates param_name, **validations if validations.any?
27
- public_send(:attribute, param_name, type)
23
+ def attribute(param_name, type)
24
+ attr_accessor param_name
25
+
26
+ schema[param_name] = type
28
27
  end
29
- end
30
28
 
31
- def self.each(param_name, normalize: nil, optional: nil, **validations, &block)
32
- validates param_name, **validations if validations.any?
33
- type = EasyParams::Types::Array.of(EasyParams::Types::Struct)
34
- type = type.meta(omittable: true) if optional
35
- type = type.constructor { |value| value == Dry::Types::Undefined ? value : normalize.call(value) } if normalize
36
- public_send(:attribute, param_name, type, &block)
37
- end
29
+ def schema
30
+ @schema ||= {}
31
+ end
38
32
 
39
- def self.has(param_name, normalize: nil, optional: nil, **validations, &block)
40
- validates param_name, **validations if validations.any?
41
- type = EasyParams::Types::Struct
42
- type = type.meta(omittable: true) if optional
43
- type = type.constructor { |value| value == Dry::Types::Undefined ? value : normalize.call(value) } if normalize
44
- public_send(:attribute, param_name, type, &block)
45
- end
33
+ def each(param_name, default: nil, normalize: nil, **validations, &block)
34
+ validates param_name, **validations if validations.any?
35
+ type = EasyParams::Types::Each.with_type(&block)
36
+ type = customize_type(type, default, &normalize)
37
+ attribute(param_name, type)
38
+ end
46
39
 
47
- def self.array(param_name, of:, normalize: nil, optional: nil, **validations, &block)
48
- validates param_name, **validations if validations.any?
49
- of_type = EasyParams::Types.const_get(of.to_s.camelcase)
50
- type = EasyParams::Types::Array
51
- type = type.meta(omittable: true) if optional
52
- type = type.constructor { |value| value == Dry::Types::Undefined ? value : normalize.call(value) } if normalize
53
- public_send(:attribute, param_name, type.of(of_type), &block)
54
- end
40
+ def has(param_name, default: nil, normalize: nil, **validations, &block)
41
+ validates param_name, **validations if validations.any?
42
+ type = Class.new(EasyParams::Types::Struct.class).tap { |c| c.class_eval(&block) }.new
43
+ type = customize_type(type, default, &normalize)
44
+ attribute(param_name, type)
45
+ end
55
46
 
56
- private
47
+ def array(param_name, of:, default: nil, normalize: nil, **validations)
48
+ validates param_name, **validations if validations.any?
49
+ type = EasyParams::Types::Array.of(EasyParams::Types.const_get(of.to_s.camelcase))
50
+ type = customize_type(type, default, &normalize)
51
+ attribute(param_name, type)
52
+ end
57
53
 
58
- def validate_nested
59
- attributes.each do |_, value|
60
- case value
61
- when *EasyParams::Types::ARRAY_OF_STRUCTS_TYPES_LIST
62
- value.each(&:valid?)
63
- when *EasyParams::Types::STRUCT_TYPES_LIST
64
- value.valid?
65
- end
54
+ private
55
+
56
+ def customize_type(type, default, &normalize)
57
+ type = type.default(default) if default
58
+ type = type.normalize(&normalize) if normalize
59
+ type
66
60
  end
67
- attributes.each(&aggregate_nested_errors)
68
61
  end
69
62
 
70
- def aggregate_nested_errors
71
- proc do |attr_name, value, array_index, error_key_prefix|
72
- case value
73
- when *EasyParams::Types::ARRAY_OF_STRUCTS_TYPES_LIST
74
- value.each.with_index do |element, i|
75
- aggregate_nested_errors[attr_name, element, "[#{i}]", error_key_prefix]
76
- end
77
- when *EasyParams::Types::STRUCT_TYPES_LIST
78
- handle_nested_errors(value, error_key_prefix, attr_name, array_index)
79
- end
63
+ %w[Integer Decimal Float Bool String Date DateTime Time].each do |type_name|
64
+ send(:define_singleton_method,
65
+ type_name.underscore) do |param_name, default: nil, normalize: nil, **validations|
66
+ validates param_name, **validations if validations.any?
67
+ type = EasyParams::Types.const_get(type_name)
68
+ type = customize_type(type, default, &normalize)
69
+ attribute(param_name, type)
80
70
  end
81
71
  end
82
72
 
83
- def handle_nested_errors(value, error_key_prefix, attr_name, array_index)
84
- return if value.errors.blank?
73
+ def attributes
74
+ self.class.schema.to_h { |k, type| [k, type.array? ? send(k).to_a : send(k)] }
75
+ end
85
76
 
86
- error_key_components = [error_key_prefix, attr_name, array_index]
87
- attr_error_key_prefix = error_key_components.compact.join('.').gsub(/\.\[(\d+)\]/, '[\1]')
88
- add_errors_on_top_level(value, attr_error_key_prefix)
77
+ validate do
78
+ validate_nested
89
79
  end
90
80
 
91
- if defined? ActiveModel::Error
92
- def add_errors_on_top_level(value, attr_error_key_prefix)
93
- value.errors.each { |error| errors.add("#{attr_error_key_prefix}.#{error.attribute}", error.message) }
94
- end
95
- else
96
- def add_errors_on_top_level(value, attr_error_key_prefix)
97
- value.errors.each { |key, message| errors.add("#{attr_error_key_prefix}.#{key}", message) }
81
+ def to_h
82
+ attributes.each.with_object({}) do |(key, value), result|
83
+ result[key] = case value
84
+ when EasyParams::Types::StructsCollection
85
+ value.map(&:to_h)
86
+ when EasyParams::Types::Struct.class
87
+ value.to_h
88
+ else
89
+ value
90
+ end
98
91
  end
99
92
  end
100
93
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EasyParams
4
+ module Types
5
+ # base interface for array type
6
+ class Collection < Generic
7
+ include Enumerable
8
+
9
+ def initialize(*attrs, of: nil)
10
+ super(*attrs)
11
+ @of_type = of
12
+ end
13
+
14
+ def of(of_type)
15
+ self.class.new(@title, @default, @normalize_proc, of: of_type, &@coerce_proc)
16
+ end
17
+
18
+ def coerce(value)
19
+ input = value || @default
20
+ input = @normalize_proc.call(Array(input)) if @normalize_proc
21
+ coerced = Array(input).map { |v| @of_type.coerce(v) }
22
+ self.class.new(@title, coerced, @normalize_proc, of: @of_type, &@coerce_proc)
23
+ end
24
+
25
+ def normalize(&block)
26
+ self.class.new(@title, @default, block, of: @of_type, &@coerce_proc)
27
+ end
28
+
29
+ def self.optional
30
+ self.class.new(@title, @default, @normalize_proc, of: @of_type, &@coerce_proc)
31
+ end
32
+
33
+ def default(value)
34
+ self.class.new(@title, value, @normalize_proc, of: @of_type, &@coerce_proc)
35
+ end
36
+
37
+ def each(&block)
38
+ @default.each(&block)
39
+ end
40
+ end
41
+
42
+ # base interface for array of structs type
43
+ class StructsCollection < Collection
44
+ def with_type(&block)
45
+ of_type = Class.new(EasyParams::Types::Struct.class).tap { |c| c.class_eval(&block) }.new
46
+ self.class.new(@title, @default, @normalize_proc, of: of_type)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EasyParams
4
+ module Types
5
+ # base interface for simple types
6
+ class Generic
7
+ def array?
8
+ @title == :array
9
+ end
10
+
11
+ def initialize(title, default = nil, normalize_proc = nil, &coerce_proc)
12
+ @title = title
13
+ @default = default
14
+ @coerce_proc = coerce_proc
15
+ @normalize_proc = normalize_proc
16
+ end
17
+
18
+ def default(value)
19
+ self.class.new(@title, value, @normalize_proc, &@coerce_proc)
20
+ end
21
+
22
+ def normalize(&block)
23
+ self.class.new(@title, @default, block, &@coerce_proc)
24
+ end
25
+
26
+ def coerce(value)
27
+ value = @normalize_proc.call(value) if @normalize_proc
28
+ return @default if value.nil?
29
+
30
+ @coerce_proc.call(value)
31
+ rescue StandardError
32
+ @default
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EasyParams
4
+ module Types
5
+ # base interface for struct type
6
+ module Struct
7
+ def array?
8
+ false
9
+ end
10
+
11
+ def default(value)
12
+ self.default = value
13
+ self
14
+ end
15
+
16
+ def coerce(input)
17
+ return if input.nil? && @default.nil?
18
+ return self.class.new(@default) if input.nil? && @default.is_a?(Hash)
19
+
20
+ self.class.new(input)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -2,18 +2,37 @@
2
2
 
3
3
  module EasyParams
4
4
  module Types
5
- Struct = EasyParams::Base
6
- Integer = Dry::Types['params.integer'].optional.default(nil)
7
- Decimal = Dry::Types['params.decimal'].optional.default(nil)
8
- Float = Dry::Types['params.float'].optional.default(nil)
9
- Bool = Dry::Types['params.bool'].optional.default(nil)
10
- String = Dry::Types['coercible.string'].optional.default(nil)
11
- Array = Dry::Types['array'].default([].freeze)
12
- Date = Dry::Types['params.date'].optional.default(nil)
13
- DateTime = Dry::Types['params.date_time'].optional.default(nil)
14
- Time = Dry::Types['params.time'].optional.default(nil)
5
+ class CoercionError < StandardError; end
15
6
 
16
- STRUCT_TYPES_LIST = [Struct].freeze
17
- ARRAY_OF_STRUCTS_TYPES_LIST = [Array.of(Struct)].freeze
7
+ BOOLEAN_MAP =
8
+ { '1' => true, 't' => true, 'true' => true, 'True' => true, 'TRUE' => true, 'T' => true }.merge(
9
+ { '0' => false, 'f' => false, 'false' => false, 'False' => false, 'FALSE' => false, 'F' => false }
10
+ ).freeze
11
+
12
+ Struct = Class.new(EasyParams::Base).new
13
+ Array = Collection.new(:array)
14
+ Each = StructsCollection.new(:array_of_structs)
15
+ Integer = Generic.new(:integer, &:to_i)
16
+ Float = Generic.new(:float, &:to_f)
17
+ String = Generic.new(:string, &:to_s)
18
+ Decimal = Generic.new(:decimal) { |v| v.to_f.to_d }
19
+ Bool = Generic.new(:bool) do |v|
20
+ BOOLEAN_MAP.fetch(v.to_s) { raise CoercionError }
21
+ end
22
+ Date = Generic.new(:date) do |v|
23
+ ::Date.parse(v)
24
+ rescue ArgumentError, RangeError
25
+ raise CoercionError, 'cannot be coerced'
26
+ end
27
+ DateTime = Generic.new(:datetime) do |v|
28
+ ::DateTime.parse(v)
29
+ rescue ArgumentError
30
+ raise CoercionError, 'cannot be coerced'
31
+ end
32
+ Time = Generic.new(:time) do |v|
33
+ ::Time.parse(v)
34
+ rescue ArgumentError
35
+ raise CoercionError, 'cannot be coerced'
36
+ end
18
37
  end
19
38
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EasyParams
4
+ # helpers for recursive validation
5
+ module Validation
6
+ private
7
+
8
+ def validate_nested
9
+ attributes.each_value do |value|
10
+ case value
11
+ when EasyParams::Types::StructsCollection
12
+ value.each(&:valid?)
13
+ when EasyParams::Types::Struct.class
14
+ value.valid?
15
+ end
16
+ end
17
+ attributes.each(&aggregate_nested_errors)
18
+ end
19
+
20
+ def aggregate_nested_errors
21
+ proc do |attr_name, value, array_index, error_key_prefix|
22
+ case value
23
+ when EasyParams::Types::StructsCollection
24
+ value.each.with_index do |element, i|
25
+ aggregate_nested_errors[attr_name, element, "[#{i}]", error_key_prefix]
26
+ end
27
+ when EasyParams::Types::Struct.class
28
+ handle_nested_errors(value, error_key_prefix, attr_name, array_index)
29
+ end
30
+ end
31
+ end
32
+
33
+ def handle_nested_errors(value, error_key_prefix, attr_name, array_index)
34
+ return if value.errors.blank?
35
+
36
+ error_key_components = [error_key_prefix, attr_name, array_index]
37
+ attr_error_key_prefix = error_key_components.compact.join('.').gsub(/\.\[(\d+)\]/, '[\1]')
38
+ add_errors_on_top_level(value, attr_error_key_prefix)
39
+ end
40
+
41
+ if defined? ActiveModel::Error
42
+ def add_errors_on_top_level(value, attr_error_key_prefix)
43
+ value.errors.each { |error| errors.add("#{attr_error_key_prefix}.#{error.attribute}", error.message) }
44
+ end
45
+ else
46
+ def add_errors_on_top_level(value, attr_error_key_prefix)
47
+ value.errors.each { |key, message| errors.add("#{attr_error_key_prefix}.#{key}", message) }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EasyParams
4
- VERSION = '0.4.1'
4
+ VERSION = '0.6.0'
5
5
  end
data/lib/easy_params.rb CHANGED
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry-struct'
4
- require 'dry-types'
5
3
  require 'active_model'
4
+ require 'bigdecimal/util'
5
+ require 'easy_params/types/generic'
6
+ require 'easy_params/types/collection'
7
+ require 'easy_params/types/struct'
8
+ require 'easy_params/validation'
6
9
  require 'easy_params/base'
7
10
  require 'easy_params/types'
8
11
  require 'easy_params/version'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: easy_params
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrii Baran
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-01-15 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activemodel
@@ -17,9 +16,6 @@ dependencies:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
18
  version: '3.2'
20
- - - "<"
21
- - !ruby/object:Gem::Version
22
- version: '8'
23
19
  type: :runtime
24
20
  prerelease: false
25
21
  version_requirements: !ruby/object:Gem::Requirement
@@ -27,37 +23,6 @@ dependencies:
27
23
  - - ">="
28
24
  - !ruby/object:Gem::Version
29
25
  version: '3.2'
30
- - - "<"
31
- - !ruby/object:Gem::Version
32
- version: '8'
33
- - !ruby/object:Gem::Dependency
34
- name: dry-struct
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '1.4'
40
- type: :runtime
41
- prerelease: false
42
- version_requirements: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: '1.4'
47
- - !ruby/object:Gem::Dependency
48
- name: dry-types
49
- requirement: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '1.5'
54
- type: :runtime
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: '1.5'
61
26
  description: Dessribe structure, validate and coerce values. Powered by dry-types
62
27
  and dry-struct
63
28
  email:
@@ -70,6 +35,7 @@ files:
70
35
  - ".gitignore"
71
36
  - ".rspec"
72
37
  - ".rubocop.yml"
38
+ - ".ruby-version"
73
39
  - ".travis.yml"
74
40
  - CODE_OF_CONDUCT.md
75
41
  - Gemfile
@@ -83,6 +49,10 @@ files:
83
49
  - lib/easy_params.rb
84
50
  - lib/easy_params/base.rb
85
51
  - lib/easy_params/types.rb
52
+ - lib/easy_params/types/collection.rb
53
+ - lib/easy_params/types/generic.rb
54
+ - lib/easy_params/types/struct.rb
55
+ - lib/easy_params/validation.rb
86
56
  - lib/easy_params/version.rb
87
57
  homepage: https://github.com/andriy-baran/easy_params
88
58
  licenses:
@@ -91,7 +61,6 @@ metadata:
91
61
  allowed_push_host: https://rubygems.org
92
62
  homepage_uri: https://github.com/andriy-baran/easy_params
93
63
  source_code_uri: https://github.com/andriy-baran/easy_params
94
- post_install_message:
95
64
  rdoc_options: []
96
65
  require_paths:
97
66
  - lib
@@ -106,8 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
75
  - !ruby/object:Gem::Version
107
76
  version: '0'
108
77
  requirements: []
109
- rubygems_version: 3.3.26
110
- signing_key:
78
+ rubygems_version: 3.7.1
111
79
  specification_version: 4
112
80
  summary: A tool that handles common tasks needed when working with params in Rails
113
81
  test_files: []