formeze 4.2.1 → 5.0.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: 39b1043817638b51294f4ca2da81e2e2fa1829f7209eb0026bedffca84c7b430
4
- data.tar.gz: 0e4d59c83e428005f01522430e740795accb24831dcc226cd1ce30eda28afada
3
+ metadata.gz: bd2160c23942cd4a127e7f84993aceb1a416a687060fb4f907181d295d5b26d5
4
+ data.tar.gz: 7406001697348315beeeb47c7400756e4bf8fb8acb60b8d872004f8de2966382
5
5
  SHA512:
6
- metadata.gz: 865f278fd2c8456a1def21e04d2150d6487727eceda9bc6d1ce375cba906f4a177e76cb158eea0cbe40d1cd1c6da785efb512c301647ebbcc8155d2aae8a7061
7
- data.tar.gz: e20a37f37ebbb28932174a148cedd37fd251f97fa05986b0302ddf4e7f7897638d3f722af071060fca0704d0f97265e273478cd01721a1383b72feabb1279896
6
+ metadata.gz: 83a0278250e4a4d9a1e7adae850e34cc4d44bdfa0e71638cf15ec7042abce347889495a4c6466d189ca30a9eb7b35e23472db91237f85f11841ef83fee2e01f8
7
+ data.tar.gz: b4218dd2e97436668378a40863a652ef1e7f7e329c0fa903e44197846b935ba8af21c7d2e34f804e483c9fda385deb39529b8a2049f69e4130b2238ef1ef9c2b
data/CHANGES.md CHANGED
@@ -1,3 +1,39 @@
1
+ # 5.0.0
2
+
3
+ * Added a `fill` field option to specify how to fill a field. For example:
4
+
5
+ class ExampleForm < Formeze::Form
6
+ field :year, required: false, fill: ->{ _1.date&.year }
7
+ end
8
+
9
+ * Added functionality for defining field specific error messages via i18n.
10
+
11
+ Add translations to your locale files like this:
12
+
13
+ ExampleForm:
14
+ errors:
15
+ comments:
16
+ required: 'are required'
17
+
18
+ * Removed support for older rubies. **Required ruby version is now 3.0.0**
19
+
20
+ * Changed from cgi to rack for parsing form data, adding support for parsing
21
+ requests with different methods e.g. PUT and PATCH instead of just POST.
22
+
23
+ Whilst this should largely be backwards compatible there are some differences,
24
+ for example uploaded files will be instances of `Rack::Multipart::UploadedFile`
25
+ instead of `StringIO` instances as they were before.
26
+
27
+ * Changed the default blank value to `nil`
28
+
29
+ # 4.3.0
30
+
31
+ * Added dependency on cgi gem
32
+
33
+ * Added support for lambda conditions without arguments
34
+
35
+ * Fixed custom validation for fields with multiple values
36
+
1
37
  # 4.2.1
2
38
 
3
39
  * Fixed regression in v4.2.0 when mime-types gem is not loaded
data/README.md CHANGED
@@ -3,19 +3,19 @@
3
3
  ![Gem Version](https://badge.fury.io/rb/formeze.svg)
4
4
  ![Test Status](https://github.com/readysteady/formeze/actions/workflows/test.yml/badge.svg)
5
5
 
6
- Ruby gem for validating form data.
6
+ Ruby gem for parsing and validating form data.
7
7
 
8
8
 
9
9
  ## Motivation
10
10
 
11
- Most web apps built for end users will need to process url-encoded form data.
11
+ Most web applications built for end users will need to process form data.
12
12
  Registration forms, profile forms, checkout forms, contact forms, and forms
13
13
  for adding/editing application specific data.
14
14
 
15
- As developers we would like to process this data safely, to minimise the
16
- possibility of security holes within our application that could be exploited.
17
- Formeze adopts the approach of being "strict by default", forcing the application
18
- code to be explicit in what it accepts as input.
15
+ With formeze you can define form objects that explicitly define what your
16
+ application expects as input. This is more secure, and leads to much better
17
+ separation of responsibilities, and also allows for implementing different
18
+ validation rules in different contexts.
19
19
 
20
20
 
21
21
  ## Install
@@ -53,7 +53,8 @@ else
53
53
  end
54
54
  ```
55
55
 
56
- Formeze will automatically ignore the Rails "utf8" and "authenticity_token" parameters.
56
+ Formeze will automatically ignore the Rails "authenticity_token", "commit",
57
+ and "utf8" parameters.
57
58
 
58
59
  If you prefer not to inherit from the `Formeze::Form` class then you can
59
60
  instead call the `Formeze.setup` method on your classes like this:
@@ -89,9 +90,9 @@ messages specific to a single field.
89
90
 
90
91
  ## Field options
91
92
 
92
- By default fields cannot be blank, they are limited to 64 characters,
93
- and they cannot contain newlines. These restrictions can be overridden
94
- by setting various field options.
93
+ By default fields are required (i.e. they cannot be blank), they are limited
94
+ to 64 characters, and they cannot contain newlines. These restrictions can be
95
+ overridden by setting various field options.
95
96
 
96
97
  Defining a field without any options works well for a simple text input.
97
98
  If the default length limit is too big or too small you can override it
@@ -101,11 +102,14 @@ by setting the `maxlength` option. For example:
101
102
  field :title, maxlength: 200
102
103
  ```
103
104
 
104
- Similarly there is a `minlength` option for validating fields that should
105
- have a minimum number of characters (e.g. passwords).
105
+ Similarly there is a `minlength` option for defining a minimum length:
106
+
107
+ ```ruby
108
+ field :password, minlength: 8
109
+ ```
106
110
 
107
111
  Fields are required by default. Specify the `required` option if the field
108
- is not required, i.e. the value of the field can be blank/empty. For example:
112
+ is optional. For example:
109
113
 
110
114
  ```ruby
111
115
  field :title, required: false
@@ -174,7 +178,7 @@ the `defined_if` or `defined_unless` options with a proc. Here's an example
174
178
  of using the defined_if option:
175
179
 
176
180
  ```ruby
177
- field :business_name, defined_if: proc { @account.business? }
181
+ field :business_name, defined_if: ->{ @account.business? }
178
182
  ```
179
183
 
180
184
  In this example the `business_name` field will only be defined and validated
@@ -185,7 +189,7 @@ Here's an example of using the defined_unless option:
185
189
  ```ruby
186
190
  field :same_address, values: %w(true), key_required: false
187
191
 
188
- field :billing_address_line_one, defined_unless: proc { same_address? }
192
+ field :billing_address_line_one, defined_unless: ->{ same_address? }
189
193
 
190
194
  def same_address?
191
195
  same_address == 'true'
@@ -308,11 +312,11 @@ would include the value of the `formeze.errors.does_not_match` I18n key.
308
312
 
309
313
  ## I18n integration
310
314
 
311
- Formeze integrates with [I18n](http://edgeguides.rubyonrails.org/i18n.html)
315
+ Formeze integrates with the [i18n gem](https://rubygems.org/gems/i18n)
312
316
  so that you can define custom error messages and field labels within your
313
317
  locales (useful both for localization, and when working with designers).
314
- For example, here is how you would change the "required" error message
315
- (which defaults to "is required"):
318
+
319
+ Here is an example of how you would change the "required" error message:
316
320
 
317
321
  ```yaml
318
322
  # config/locales/en.yml
@@ -322,8 +326,20 @@ en:
322
326
  required: "cannot be blank"
323
327
  ```
324
328
 
325
- And here is an example of how you would set a custom label for fields named
326
- "first_name" (for which the default label would be "First name"):
329
+ Error messages defined in this way apply globally to all Formeze forms.
330
+
331
+ You can also change error messages on a per field basis, for example:
332
+
333
+ ```yaml
334
+ # config/locales/en.yml
335
+ en:
336
+ ExampleForm:
337
+ errors:
338
+ comments:
339
+ required: 'are required'
340
+ ```
341
+
342
+ Here is an example of how to define a custom label for "first_name" fields:
327
343
 
328
344
  ```yaml
329
345
  # config/locales/en.yml
data/formeze.gemspec CHANGED
@@ -1,15 +1,15 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'formeze'
3
- s.version = '4.2.1'
3
+ s.version = '5.0.0'
4
4
  s.license = 'LGPL-3.0'
5
5
  s.platform = Gem::Platform::RUBY
6
6
  s.authors = ['Tim Craft']
7
7
  s.email = ['mail@timcraft.com']
8
8
  s.homepage = 'https://github.com/readysteady/formeze'
9
- s.description = 'Ruby gem for validating form data'
9
+ s.description = 'Ruby gem for parsing and validating form data'
10
10
  s.summary = 'See description'
11
11
  s.files = Dir.glob('lib/**/*.rb') + %w(CHANGES.md LICENSE.txt README.md formeze.gemspec)
12
- s.required_ruby_version = '>= 2.4.0'
12
+ s.required_ruby_version = '>= 3.0.0'
13
13
  s.require_path = 'lib'
14
14
  s.metadata = {
15
15
  'homepage' => 'https://github.com/readysteady/formeze',
@@ -17,4 +17,5 @@ Gem::Specification.new do |s|
17
17
  'bug_tracker_uri' => 'https://github.com/readysteady/formeze/issues',
18
18
  'changelog_uri' => 'https://github.com/readysteady/formeze/blob/main/CHANGES.md'
19
19
  }
20
+ s.add_dependency 'rack', '~> 3'
20
21
  end
@@ -0,0 +1,11 @@
1
+ module Formeze::Block
2
+ def self.evaluate(instance, block)
3
+ block = block.to_proc
4
+
5
+ if block.arity.zero?
6
+ instance.instance_exec(&block)
7
+ else
8
+ instance.instance_eval(&block)
9
+ end
10
+ end
11
+ end
@@ -1,11 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Formeze::Errors
2
4
  SCOPE = [:formeze, :errors].freeze
3
5
 
4
- def self.translate(error, default)
5
- if defined?(I18n)
6
- return I18n.translate(error, scope: SCOPE, default: default)
7
- end
6
+ DEFAULT = {
7
+ bad_value: 'is invalid',
8
+ not_accepted: 'is not an accepted file type',
9
+ not_multiline: 'cannot contain newlines',
10
+ no_match: 'is invalid',
11
+ required: 'is required',
12
+ too_large: 'is too large',
13
+ too_long: 'is too long',
14
+ too_short: 'is too short',
15
+ }
16
+
17
+ def self.translate(error, scope:)
18
+ default = DEFAULT[error] || 'is invalid'
19
+
20
+ return default unless defined?(I18n)
21
+
22
+ message = I18n.translate(error, scope: scope, default: nil)
8
23
 
9
- default
24
+ message || I18n.translate(error, scope: SCOPE, default: default)
10
25
  end
11
26
  end
data/lib/formeze/field.rb CHANGED
@@ -14,49 +14,41 @@ class Formeze::Field
14
14
 
15
15
  values.each do |value|
16
16
  if String === value
17
- validate(value, form)
17
+ value = validate(value, form)
18
18
  else
19
- validate_file(value, form)
19
+ form.add_error(self, :not_accepted) unless acceptable_file?(value)
20
20
 
21
21
  size += value.size
22
22
  end
23
+
24
+ value = Array(form.send(name)).push(value) if multiple?
25
+
26
+ form.send(:"#{name}=", value)
23
27
  end
24
28
 
25
- form.add_error(self, :too_large, 'is too large') if maxsize? && size > maxsize
29
+ form.add_error(self, :too_large) if maxsize? && size > maxsize
26
30
  end
27
31
 
28
32
  def validate(value, form)
29
33
  value = Formeze.scrub(value, @options[:scrub])
30
34
 
31
35
  if blank?(value)
32
- form.add_error(self, :required, 'is required') if required?
36
+ form.add_error(self, :required) if required?
33
37
 
34
- value = blank_value if blank_value?
38
+ value = blank_value
35
39
  else
36
- form.add_error(self, :not_multiline, 'cannot contain newlines') if !multiline? && value.lines.count > 1
37
-
38
- form.add_error(self, :too_long, 'is too long') if too_long?(value)
39
-
40
- form.add_error(self, :too_short, 'is too short') if too_short?(value)
41
-
42
- form.add_error(self, :no_match, 'is invalid') if no_match?(value)
40
+ form.add_error(self, :not_multiline) if !multiline? && value.lines.count > 1
43
41
 
44
- form.add_error(self, :bad_value, 'is invalid') if values? && !values.include?(value)
45
- end
42
+ form.add_error(self, :too_long) if too_long?(value)
46
43
 
47
- value = Array(form.send(name)).push(value) if multiple?
44
+ form.add_error(self, :too_short) if too_short?(value)
48
45
 
49
- form.send(:"#{name}=", value)
50
- end
46
+ form.add_error(self, :no_match) if no_match?(value)
51
47
 
52
- def validate_file(object, form)
53
- unless acceptable_file?(object)
54
- form.add_error(self, :not_accepted, 'is not an accepted file type')
48
+ form.add_error(self, :bad_value) if values? && !values.include?(value)
55
49
  end
56
50
 
57
- object = Array(form.send(name)).push(object) if multiple?
58
-
59
- form.send(:"#{name}=", object)
51
+ value
60
52
  end
61
53
 
62
54
  def acceptable_file?(object)
@@ -103,6 +95,10 @@ class Formeze::Field
103
95
  @options.fetch(:maxsize)
104
96
  end
105
97
 
98
+ def accept?
99
+ @options.key?(:accept)
100
+ end
101
+
106
102
  def accept
107
103
  @accept ||= @options.fetch(:accept).split(',').flat_map { |type| MIME::Types[type] }
108
104
  end
@@ -119,12 +115,8 @@ class Formeze::Field
119
115
  @options.key?(:pattern) && value !~ @options[:pattern]
120
116
  end
121
117
 
122
- def blank_value?
123
- @options.key?(:blank)
124
- end
125
-
126
118
  def blank_value
127
- @options.fetch(:blank)
119
+ @options[:blank]
128
120
  end
129
121
 
130
122
  def values?
@@ -150,4 +142,22 @@ class Formeze::Field
150
142
  def defined_unless
151
143
  @options.fetch(:defined_unless)
152
144
  end
145
+
146
+ def undefined?(form)
147
+ if defined_if?
148
+ !Formeze::Block.evaluate(form, defined_if)
149
+ elsif defined_unless?
150
+ Formeze::Block.evaluate(form, defined_unless)
151
+ else
152
+ false
153
+ end
154
+ end
155
+
156
+ def fill_proc?
157
+ @options.key?(:fill)
158
+ end
159
+
160
+ def fill_proc
161
+ @options[:fill]
162
+ end
153
163
  end
@@ -1,23 +1,53 @@
1
- require 'cgi'
1
+ # frozen_string_literal: true
2
+ require 'rack'
2
3
 
3
4
  module Formeze::FormData
4
- class CGI < ::CGI
5
- def env_table
6
- @options[:request].env
5
+ def self.parse(input)
6
+ if input.is_a?(String)
7
+ query_parser.parse_query(input)
8
+ elsif input.respond_to?(:env)
9
+ body = input.body
10
+ body.rewind if body.respond_to?(:rewind)
11
+ case input.media_type
12
+ when 'multipart/form-data'
13
+ Rack::Multipart.parse_multipart(input.env, Params)
14
+ when 'application/x-www-form-urlencoded'
15
+ query_parser.parse_query(body.read)
16
+ else
17
+ raise ArgumentError, "can't parse #{input.media_type.inspect} form data"
18
+ end
19
+ else
20
+ raise ArgumentError, "can't parse #{input.class} form data"
21
+ end
22
+ end
23
+
24
+ module Params
25
+ def self.make_params
26
+ ParamsHash.new { |h, k| h[k] = Array.new }
7
27
  end
8
28
 
9
- def stdinput
10
- @options[:request].body.tap do |body|
11
- body.rewind if body.respond_to?(:rewind)
29
+ def self.normalize_params(params, key, value)
30
+ if value.is_a?(Hash)
31
+ value = Rack::Multipart::UploadedFile.new(io: value[:tempfile], filename: value[:filename], content_type: value[:type])
12
32
  end
33
+
34
+ params[key] << value
13
35
  end
14
36
  end
15
37
 
16
- def self.parse(input)
17
- if input.is_a?(String)
18
- CGI.parse(input)
19
- else
20
- CGI.new(request: input).params
38
+ class ParamsHash < ::Hash
39
+ alias_method :to_params_hash, :to_h
40
+ end
41
+
42
+ class QueryParser < Rack::QueryParser
43
+ def make_params
44
+ Hash.new { |h, k| h[k] = Array.new }
21
45
  end
22
46
  end
47
+
48
+ def self.query_parser
49
+ @query_parser ||= QueryParser.new(nil, 0)
50
+ end
51
+
52
+ private_class_method :query_parser
23
53
  end
@@ -1,11 +1,14 @@
1
1
  module Formeze::Presence
2
2
  REGEXP = /\S/
3
3
 
4
- def present?(string)
5
- string =~ REGEXP
4
+ def present?(value)
5
+ return false if value.nil?
6
+ return false if value.respond_to?(:empty?) && value.empty?
7
+ return false if value.is_a?(String) && value !~ REGEXP
8
+ return true
6
9
  end
7
10
 
8
- def blank?(string)
9
- string !~ REGEXP
11
+ def blank?(value)
12
+ !present?(value)
10
13
  end
11
14
  end
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  class Formeze::Validation
4
2
  include Formeze::Presence
5
3
 
@@ -14,7 +12,7 @@ class Formeze::Validation
14
12
  end
15
13
 
16
14
  def validates?(form)
17
- @precondition ? form.instance_eval(&@precondition) : true
15
+ @precondition ? Formeze::Block.evaluate(form, @precondition) : true
18
16
  end
19
17
 
20
18
  def field_value?(form)
@@ -33,7 +31,7 @@ class Formeze::Validation
33
31
  form.instance_eval(&@block)
34
32
  end
35
33
 
36
- form.add_error(@field, @error, 'is invalid') unless return_value
34
+ form.add_error(@field, @error) unless return_value
37
35
  end
38
36
  end
39
37
  end
data/lib/formeze.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Formeze
4
+ autoload :Block, 'formeze/block'
4
5
  autoload :Errors, 'formeze/errors'
5
6
  autoload :Field, 'formeze/field'
6
7
  autoload :Form, 'formeze/form'
@@ -35,16 +36,24 @@ module Formeze
35
36
 
36
37
  class ValueError < StandardError; end
37
38
 
38
- class ValidationError < StandardError; end
39
+ class ValidationError < StandardError
40
+ def initialize(field, message)
41
+ @field = field
39
42
 
40
- RAILS_FORM_KEYS = %w[utf8 authenticity_token commit]
43
+ super("#{field.label} #{message}")
44
+ end
41
45
 
42
- private_constant :RAILS_FORM_KEYS
46
+ def field_name
47
+ @field.name
48
+ end
49
+ end
43
50
 
44
51
  module InstanceMethods
45
52
  def fill(object)
46
53
  self.class.fields.each_value do |field|
47
- if Hash === object && object.key?(field.name)
54
+ if field.fill_proc?
55
+ send(:"#{field.name}=", Formeze::Block.evaluate(object, field.fill_proc))
56
+ elsif Hash === object && object.key?(field.name)
48
57
  send(:"#{field.name}=", object[field.name])
49
58
  elsif object.respond_to?(field.name)
50
59
  send(:"#{field.name}=", object.send(field.name))
@@ -58,27 +67,31 @@ module Formeze
58
67
  form_data = FormData.parse(input)
59
68
 
60
69
  self.class.fields.each_value do |field|
61
- next unless field_defined?(field)
70
+ next if field.undefined?(self)
62
71
 
63
72
  unless form_data.key?(field.key)
64
73
  next if field.multiple? || !field.key_required?
65
74
 
66
- raise KeyError, "missing form key: #{field.key}"
75
+ raise KeyError, "missing form key: #{field.key}" unless field.accept?
67
76
  end
68
77
 
69
78
  values = form_data.delete(field.key)
70
79
 
71
- if values.length > 1
72
- raise ValueError unless field.multiple?
73
- end
80
+ if values.is_a?(Array)
81
+ if values.length > 1
82
+ raise ValueError, "multiple values for #{field.key} field" unless field.multiple?
83
+ end
74
84
 
75
- field.validate_all(values, self)
85
+ field.validate_all(values, self)
86
+ else
87
+ field.validate(values, self)
88
+ end
76
89
  end
77
90
 
78
91
  if defined?(Rails)
79
- RAILS_FORM_KEYS.each do |key|
80
- form_data.delete(key)
81
- end
92
+ form_data.delete('authenticity_token')
93
+ form_data.delete('commit')
94
+ form_data.delete('utf8')
82
95
  end
83
96
 
84
97
  unless form_data.empty?
@@ -92,14 +105,10 @@ module Formeze
92
105
  return self
93
106
  end
94
107
 
95
- def add_error(field, message, default = nil)
96
- message = Formeze::Errors.translate(message, default) unless default.nil?
97
-
98
- error = ValidationError.new("#{field.label} #{message}")
108
+ def add_error(field, error)
109
+ message = Formeze::Errors.translate(error, scope: "#{self.class.name}.errors.#{field.name}")
99
110
 
100
- errors << error
101
-
102
- field_errors[field.name] << error
111
+ errors << ValidationError.new(field, message)
103
112
  end
104
113
 
105
114
  def valid?
@@ -115,11 +124,11 @@ module Formeze
115
124
  end
116
125
 
117
126
  def errors_on?(field_name)
118
- field_errors[field_name].size > 0
127
+ errors.any? { |error| error.field_name == field_name }
119
128
  end
120
129
 
121
130
  def errors_on(field_name)
122
- field_errors[field_name]
131
+ errors.select { |error| error.field_name == field_name }
123
132
  end
124
133
 
125
134
  def to_h
@@ -129,22 +138,6 @@ module Formeze
129
138
  end
130
139
 
131
140
  alias_method :to_hash, :to_h
132
-
133
- private
134
-
135
- def field_defined?(field)
136
- if field.defined_if?
137
- instance_eval(&field.defined_if)
138
- elsif field.defined_unless?
139
- !instance_eval(&field.defined_unless)
140
- else
141
- true
142
- end
143
- end
144
-
145
- def field_errors
146
- @field_errors ||= Hash.new { |h, k| h[k] = [] }
147
- end
148
141
  end
149
142
 
150
143
  def self.label(field_name)
@@ -153,11 +146,11 @@ module Formeze
153
146
 
154
147
  def self.scrub_methods
155
148
  @scrub_methods ||= {
156
- :strip => :strip.to_proc,
157
- :upcase => :upcase.to_proc,
158
- :downcase => :downcase.to_proc,
159
- :squeeze => proc { |string| string.squeeze(' ') },
160
- :squeeze_lines => proc { |string| string.gsub(/(\r?\n)(\r?\n)(\r?\n)+/, '\\1\\2') }
149
+ strip: :strip.to_proc,
150
+ upcase: :upcase.to_proc,
151
+ downcase: :downcase.to_proc,
152
+ squeeze: proc { |string| string.squeeze(' ') },
153
+ squeeze_lines: proc { |string| string.gsub(/(\r?\n)(\r?\n)(\r?\n)+/, '\\1\\2') }
161
154
  }
162
155
  end
163
156
 
metadata CHANGED
@@ -1,16 +1,30 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: formeze
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.2.1
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Craft
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-08 00:00:00.000000000 Z
12
- dependencies: []
13
- description: Ruby gem for validating form data
11
+ date: 2024-07-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3'
27
+ description: Ruby gem for parsing and validating form data
14
28
  email:
15
29
  - mail@timcraft.com
16
30
  executables: []
@@ -22,6 +36,7 @@ files:
22
36
  - README.md
23
37
  - formeze.gemspec
24
38
  - lib/formeze.rb
39
+ - lib/formeze/block.rb
25
40
  - lib/formeze/errors.rb
26
41
  - lib/formeze/field.rb
27
42
  - lib/formeze/form.rb
@@ -45,14 +60,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
45
60
  requirements:
46
61
  - - ">="
47
62
  - !ruby/object:Gem::Version
48
- version: 2.4.0
63
+ version: 3.0.0
49
64
  required_rubygems_version: !ruby/object:Gem::Requirement
50
65
  requirements:
51
66
  - - ">="
52
67
  - !ruby/object:Gem::Version
53
68
  version: '0'
54
69
  requirements: []
55
- rubygems_version: 3.5.3
70
+ rubygems_version: 3.5.11
56
71
  signing_key:
57
72
  specification_version: 4
58
73
  summary: See description