form_input 1.2.0 → 1.4.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
- SHA1:
3
- metadata.gz: 4014e07ca32e3c7a76ee15db0dfe478a4103d123
4
- data.tar.gz: 83224f22dc199d2a3f1bf4d46011f616195d33ca
2
+ SHA256:
3
+ metadata.gz: f5f2db9b4c0442f601294eda78d17eae6fa07658d746123b34904f3b436305ec
4
+ data.tar.gz: 3895b0ddd7f21befbb375d337a485af8c533641faa1171c1f643b14efa306fdf
5
5
  SHA512:
6
- metadata.gz: 82f8812fbd77215e7a058f1585bc7b2ee2a5082215d4247ae865b8ced6e2c739c39e836741482ebf4cb089fe57b6c330f8b19726d8998ed8cfca48910faf92ba
7
- data.tar.gz: e2e6853f4eb58193d11aa22696164844130c5c653df6b872372a40bfa1d790a8b61cf45bd287046cf7c375c14326709c49d907ba51e5779f0d3a72ad9ff12152
6
+ metadata.gz: 6864fff3b9a98fab84caba8c5768fd7a4f56c43792e0b540b9d3a8187c5eea9290cce2d0807512c5e9839515b3b48999f1f3e78d560af81765950215c483c981
7
+ data.tar.gz: 1dd327e0efa4d7c940023a4eae1be1cfd357def6dfd4f432ba9f960e1f42513f5aaeb9ad13afebb6cad2a27625e15dd1abca3c597a9b205fb2bdfb28b250c9ca
data/CHANGELOG.md CHANGED
@@ -1,12 +1,29 @@
1
+ = 1.4.0
2
+
3
+ * Dependencies were updated to support latest gems, in particular both Rack 2.x and 3.x are now supported.
4
+
5
+ * The inputs containing characters from Unicode Cf (Format) and Co (Private Use) categories are rejected as was intended.
6
+
7
+ * More robust handling of inputs with invalid encoding:
8
+ * Strings returned by `form_value` are now scrubbed so they don't cause errors when used in templates.
9
+ * The hash keys with invalid encoding are now handled properly, too.
10
+
11
+ = 1.3.0
12
+
13
+ * Further improvements for JSON payloads:
14
+ * Added set of methods for distinguishing between set and unset parameters.
15
+ * The `import` now properly handles parameters with `nil` and `false` values.
16
+ * The `to_data` method now returns all set parameters, regardless of their value.
17
+
1
18
  = 1.2.0
2
19
 
3
20
  * Few changes for easier import and export of JSON payloads:
4
- * The import and the new from_data methods now support numeric, boolean, and nil values natively.
5
- * Added to_data method which creates a hash of all non-nil parameters.
21
+ * The `import` and the new `from_data` methods now support numeric, boolean, and `nil` values natively.
22
+ * Added `to_data` method which creates a hash of all non-`nil` parameters.
6
23
 
7
24
  = 1.1.0
8
25
 
9
- * Added report! methods for late reporting of the most fundamental errors.
26
+ * Added `report!` methods for late reporting of the most fundamental errors.
10
27
 
11
28
  * Added support for multiple `check` and `test` validation callbacks.
12
29
 
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Form input
2
2
 
3
- [![Gem Version](https://img.shields.io/gem/v/form_input.svg)](http://rubygems.org/gems/form_input) [![Build Status](https://travis-ci.org/raxoft/form_input.svg?branch=master)](http://travis-ci.org/raxoft/form_input) [![Dependency Status](https://img.shields.io/gemnasium/raxoft/form_input.svg)](https://gemnasium.com/raxoft/form_input) [![Code Climate](https://img.shields.io/codeclimate/github/raxoft/form_input.svg)](https://codeclimate.com/github/raxoft/form_input) [![Coverage](https://img.shields.io/codeclimate/coverage/github/raxoft/form_input.svg)](https://codeclimate.com/github/raxoft/form_input)
3
+ [![Gem Version](https://img.shields.io/gem/v/form_input.svg)](http://rubygems.org/gems/form_input) [![Build Status](https://travis-ci.org/raxoft/form_input.svg?branch=master)](http://travis-ci.org/raxoft/form_input) [![Maintainability](https://api.codeclimate.com/v1/badges/ea19d227eb1285d59860/maintainability)](https://codeclimate.com/github/raxoft/form_input/maintainability) [![Coverage](https://api.codeclimate.com/v1/badges/ea19d227eb1285d59860/test_coverage)](https://codeclimate.com/github/raxoft/form_input/test_coverage)
4
4
 
5
5
  Form input is a gem which helps dealing with web request input and with the creation of HTML forms.
6
6
 
@@ -50,7 +50,7 @@ Using them in your templates is as simple as this:
50
50
  .panel-heading
51
51
  = @title = "Contact Form"
52
52
  .panel-body
53
- form *form_attrs
53
+ form method='post' action=request.path
54
54
  fieldset
55
55
  == snippet :form_panel, params: @form.params
56
56
  button.btn.btn-default type='submit' Send
@@ -794,16 +794,19 @@ to include parts of the URL as the form input:
794
794
 
795
795
  ``` ruby
796
796
  get '/contact/:email' do
797
- form = ContactForm.new( params ) # NEVER EVER DO THIS!
798
- form = ContactForm.new.import( params ) # Do this instead if you need to.
797
+ form = ContactForm.new( params ) # NEVER EVER DO THIS!
798
+ form = ContactForm.new.import( params ) # Do this instead (or use from_params).
799
+ ...
799
800
  end
800
801
  ```
801
802
 
802
803
  Similarly, you want to use `import` when feeding form input with JSON data (see [JSON Helpers](#json-helpers) for details):
803
804
 
804
805
  ``` ruby
805
- post '/api/v1/contact' do
806
- form = ContactForm.new.import( json_data )
806
+ post '/api/v1/contacts' do
807
+ form = ContactForm.new( json_data ) # NEVER EVER DO THIS!
808
+ form = ContactForm.new.import( json_data ) # Do this instead (or use from_data).
809
+ ...
807
810
  end
808
811
  ```
809
812
 
@@ -829,6 +832,14 @@ You can either clear the entire form, named parameters, or parameter subsets (wh
829
832
  form.clear( form.disabled_params )
830
833
  ```
831
834
 
835
+ The `unset` method works the same way, except that it always requires an argument:
836
+
837
+ ``` ruby
838
+ form.unset( :message )
839
+ form.unset( :name, :company )
840
+ form.unset( form.invalid_params )
841
+ ```
842
+
832
843
  Alternatively, you can create form copies with just a subset of parameters set:
833
844
 
834
845
  ``` ruby
@@ -1026,7 +1037,7 @@ The `validate!` method on the other hand always invokes the validation,
1026
1037
  wiping any previously reported errors first.
1027
1038
 
1028
1039
  In either case any errors collected will remain stored
1029
- until you change any of the parameter values with `set`, `clear`, or `[]=` methods,
1040
+ until you change any of the parameter values with `set`, `unset`, `clear`, or `[]=` methods,
1030
1041
  or you explicitly call `validate!`.
1031
1042
  Copies created with `dup` (but not `clone`), `only`, and `except` methods
1032
1043
  also have any errors reported before cleared.
@@ -1165,6 +1176,9 @@ In fact, the parameter has a dozen of simple boolean getters like this which you
1165
1176
  p.empty? # Is the value nil or empty?
1166
1177
  p.filled? # Is the value neither nil nor empty?
1167
1178
 
1179
+ p.set? # Was the parameter value set to something?
1180
+ p.unset? # Was the parameter value not set to anything?
1181
+
1168
1182
  p.required? # Is the parameter required?
1169
1183
  p.optional? # Is the parameter not required?
1170
1184
 
@@ -1193,6 +1207,8 @@ The following methods are available:
1193
1207
  form.blank_params # Parameters with nil, empty, or blank value.
1194
1208
  form.empty_params # Parameters with nil or empty value.
1195
1209
  form.filled_params # Parameters with some non-empty value.
1210
+ form.set_params # Parameters whose value was set to something.
1211
+ form.unset_params # Parameters whose value was not set to anything.
1196
1212
  form.required_params # Parameters which are required and have to be filled.
1197
1213
  form.optional_params # Parameters which are not required and can be nil or empty.
1198
1214
  form.disabled_params # Parameters which are disabled and shall be rendered as such.
@@ -1349,8 +1365,9 @@ The only difference is that the [input filter](#input-filter) is not run in such
1349
1365
  The [input transform](#input-transform) is run as usual,
1350
1366
  and so are all the [validation methods](#errors-and-validation).
1351
1367
 
1352
- Among other things this means that you can declare a parameter as integer using the `INTEGER_ARGS` macro,
1368
+ Among other things this means that you can declare a parameter as integer using the `INTEGER_ARGS` macro
1353
1369
  and pass in the value as either string or integer, and you end up with integer in both cases, which is pretty convenient.
1370
+ Of course, this works similarly for `FLOAT_ARGS` and `BOOL_ARGS`, too:
1354
1371
 
1355
1372
  ``` ruby
1356
1373
  class NumericInput < FormInput
@@ -1363,10 +1380,11 @@ and pass in the value as either string or integer, and you end up with integer i
1363
1380
  ```
1364
1381
 
1365
1382
  Another difference when dealing with JSON data is that
1366
- the empty strings have completely different significance than in case of web forms.
1367
- For this reason, the result of the `to_data` method includes even empty strings, arrays, and hashes
1383
+ the empty strings have completely different significance than in the case of the web forms.
1384
+ For this reason, the result of the `to_data` method includes any set parameters,
1385
+ even if the values are empty strings, arrays, or hashes
1368
1386
  (unlike the `to_hash` and `to_params` methods).
1369
- The `nil` values are still filtered out, though.
1387
+ Even the `nil` values are included for parameters which were explicitly set to `nil`:
1370
1388
 
1371
1389
  ``` ruby
1372
1390
  class OptionalInput < FormInput
@@ -1376,27 +1394,67 @@ The `nil` values are still filtered out, though.
1376
1394
  end
1377
1395
 
1378
1396
  OptionalInput.from_data( string: '' ).to_data # { string: '' }
1397
+ OptionalInput.from_data( string: nil ).to_data # { string: nil }
1379
1398
  OptionalInput.from_data( array: [] ).to_data # { array: [] }
1380
1399
  OptionalInput.from_data( hash: {} ).to_data # { hash: {} }
1381
1400
  ```
1382
1401
 
1402
+ With regards to `nil` values, you may want to differentiate
1403
+ between which parameters may be set to `nil` and which not
1404
+ (in addition to parameters being required or not).
1405
+ In such case, you can extend your JSON processing class wrapping `FormInput` like this:
1406
+
1407
+ ``` ruby
1408
+ # Like param, but the parameter may be nil (and other parameters now can't).
1409
+ def self.param?( name, *args, &block )
1410
+ param( name, *args, allow_nil: true, &block )
1411
+ end
1412
+
1413
+ # Like normal validate, but makes sure the parameters are not nil unless allowed.
1414
+ def validate
1415
+ super
1416
+ set_params.each do |p|
1417
+ unless p[ :allow_nil ]
1418
+ p.report( "%p is nil" ) if p.value.nil?
1419
+ end
1420
+ end
1421
+ self
1422
+ end
1423
+ ```
1424
+
1425
+ This allows you to explicitely distinguish the three most commonly used cases with ease,
1426
+ especially when combined with further parameter types and checks:
1427
+
1428
+ ``` ruby
1429
+ param :a, INTEGER_ARGS # When set, this must be an integer.
1430
+ param? :b, INTEGER_ARGS # When set, this must be nil or an integer.
1431
+ param! :c, INTEGER_ARGS # This must be set to an integer.
1432
+
1433
+ param :s # When set, this must be a string, albeit perhaps empty.
1434
+ param? :t # When set, this must be nil or a string, including empty.
1435
+ param! :n # This must be set to a non-empty string.
1436
+ ```
1437
+
1383
1438
  The whole JSON processing may in the end look something like this:
1384
1439
 
1385
1440
  ``` ruby
1386
1441
  require 'oj'
1442
+
1387
1443
  def json_data
1388
- data = Oj.load( request.body, symbolize_keys: true )
1444
+ data = Oj.load( request.body, mode: :strict, symbolize_keys: true )
1389
1445
  halt( 422, 'Invalid data' ) unless Hash === data
1390
1446
  data
1391
1447
  rescue Oj::Error
1392
1448
  halt( 422, 'Invalid JSON' )
1393
1449
  end
1394
1450
 
1395
- post '/api/v1/contact' do
1451
+ post '/api/v1/contacts' do
1396
1452
  input = ContactForm.from_data( json_data )
1397
1453
  halt( 422, "Invalid data: #{input.error_messages.first}" ) unless input.valid?
1398
1454
  # Somehow use the input.
1399
- Contact.create( input.to_data )
1455
+ entry = Contact.create( input.to_data )
1456
+ content_type :json
1457
+ [ 201, Oj.dump( entry.to_hash ) ]
1400
1458
  end
1401
1459
  ```
1402
1460
 
@@ -1445,6 +1503,19 @@ and keys are passed to `form_name` to create the actual name:
1445
1503
  input type=p.type name=p.form_name( key ) value=value
1446
1504
  ```
1447
1505
 
1506
+ Note that for your convenience,
1507
+ any strings returned by `form_value` are scrubbed of characters in invalid encoding,
1508
+ should there be any,
1509
+ which makes them guaranteed to coalesce with the form template without triggering encoding errors.
1510
+ Under normal circumstances you would never encounter such invalid inputs,
1511
+ but sometimes hackers use them to try to break the applications on purpose.
1512
+ The scrubbing thus takes care that you don't have to deal with this in templates specially if it ever happens.
1513
+ However,
1514
+ if you would for some reason ever need the `form_value` without the scrubbing applied,
1515
+ note that you can use the `url_value` method
1516
+ (used internally by `url_params` and `url_query`, see [URL Helpers](#url-helpers))
1517
+ instead.
1518
+
1448
1519
  For parameters which require additional data,
1449
1520
  like select, multi-select, or multi-checkbox parameters,
1450
1521
  you can ask for the data using the `data` method.
@@ -1699,11 +1770,8 @@ For [Sinatra], the helper may look like this:
1699
1770
 
1700
1771
  ``` ruby
1701
1772
  # Get hash with default form attributes, optionally overriding them as needed.
1702
- def form_attrs( *args )
1703
- opts = args.last.is_a?( Hash ) ? args.pop : {}
1704
- url = args.shift.to_s unless args.empty?
1705
- fail( ArgumentError, "Invalid arguments #{args.inspect}" ) unless args.empty?
1706
- { action: url || request.path, method: :post }.merge( opts )
1773
+ def form_attrs( url = request.path, **opts )
1774
+ { action: url.to_s, method: :post }.merge( opts )
1707
1775
  end
1708
1776
  ```
1709
1777
 
@@ -0,0 +1,97 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ # Simple but functional example FormInput application for Sinatra.
4
+ #
5
+ # You will need the following gems:
6
+ # gem install sinatra sinatra-partial slim form_input
7
+ #
8
+ # Then just run it like this:
9
+ # ruby example.rb
10
+
11
+ require 'sinatra'
12
+ require 'sinatra/partial'
13
+ require 'slim/smart'
14
+ require 'form_input'
15
+
16
+ # The example form. Feel free to experiment with changing this section to whatever you want to test.
17
+
18
+ class ExampleForm < FormInput
19
+ param! :email, 'Email address', EMAIL_ARGS
20
+ param! :name, 'Name'
21
+ param :company, 'Company'
22
+ param! :message, 'Message', 1000, type: :textarea, size: 6, filter: ->{ rstrip }
23
+ end
24
+
25
+ # Support for snippets.
26
+
27
+ set :partial_template_engine, :slim
28
+ helpers do
29
+ def snippet( name, opts = {}, **locals )
30
+ partial( "snippets/#{name}", opts.merge( locals: locals ) )
31
+ end
32
+ end
33
+
34
+ # The whole application itself.
35
+
36
+ get '/' do
37
+ @form = ExampleForm.new( request )
38
+ slim :form
39
+ end
40
+
41
+ post '/' do
42
+ @form = ExampleForm.new( request )
43
+ return slim :form unless @form.valid? and params[:action] == 'post'
44
+ logger.info "These data were successfully posted: #{@form.to_hash.inspect}"
45
+ slim :post
46
+ end
47
+
48
+ # Inline templates follow.
49
+
50
+ __END__
51
+
52
+ @@ layout
53
+ doctype html
54
+ html
55
+ head
56
+ title = @title
57
+ meta charset='utf-8'
58
+ meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'
59
+ link rel='stylesheet' href='https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css' integrity='sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu' crossorigin='anonymous'
60
+ css:
61
+ label { width: 100%; }
62
+ body.container == yield
63
+
64
+ @@ form
65
+ .panel.panel-default
66
+ .panel-heading
67
+ = @title = "Example Form"
68
+ .panel-body
69
+ form method='post' action=request.path
70
+ fieldset
71
+ == snippet :form_panel, params: @form.params
72
+ button.btn.btn-default type='submit' name='action' value='post' Submit
73
+ - unless @form.empty?
74
+ .panel-footer
75
+ == partial :dump
76
+
77
+ @@ post
78
+ .panel.panel-default
79
+ .panel-heading
80
+ = @title = "Congratulations!"
81
+ All inputs are valid.
82
+ .panel-body
83
+ == partial :dump
84
+ form method='post' action=request.path
85
+ == snippet :form_hidden, params: @form.params
86
+ button.btn.btn-default type='submit' Go back
87
+ a.btn.btn-default< href=request.path Start over
88
+
89
+ @@ dump
90
+ p These are the filled form values:
91
+ dl.dl-horizontal
92
+ - for p in @form.filled_params
93
+ dt = p.form_title
94
+ dd
95
+ code = p.value.inspect
96
+
97
+ // EOF //
data/form_input.gemspec CHANGED
@@ -23,11 +23,13 @@ EOT
23
23
 
24
24
  s.files = %w[ LICENSE README.md CHANGELOG.md Rakefile .yardopts form_input.gemspec ] + Dir[ '{lib,test,example}/**/*.{rb,yml,txt,slim}' ]
25
25
 
26
- s.required_ruby_version = '>= 2.0.0'
27
- s.add_runtime_dependency 'rack', '>= 1.5', '< 3.0'
26
+ s.required_ruby_version = '>= 2.1.0'
27
+ s.add_runtime_dependency 'rack', '>= 1.5', '< 4.0'
28
+ s.add_development_dependency 'rake', '~> 13.0'
28
29
  s.add_development_dependency 'bacon', '~> 1.2'
29
- s.add_development_dependency 'rack-test', '~> 0.6'
30
- s.add_development_dependency 'r18n-core', '~> 2.0'
30
+ s.add_development_dependency 'simplecov', '~> 0.21'
31
+ s.add_development_dependency 'rack-test', '>= 1.0', '< 3.0'
32
+ s.add_development_dependency 'r18n-core', '~> 5.0'
31
33
  end
32
34
 
33
35
  # EOF #
@@ -8,9 +8,20 @@ class FormInput
8
8
  # Default size limit applied to all input.
9
9
  DEFAULT_SIZE_LIMIT = 255
10
10
 
11
- # Default input filter applied to all input.
11
+ # Default input filter applied to all input (unless replaced by user's filter).
12
12
  DEFAULT_FILTER = ->{ gsub( /\s+/, ' ' ).strip }
13
13
 
14
+ # Default match applied to all input (in addition to user's match(es)).
15
+ # Note that the Graph property, despite being defined as "Non-blank characters",
16
+ # currently includes Cf (Other: Format) and Co (Other: Private Use) categories,
17
+ # which include stuff like BOM or BIDI formatting and other invisible characters,
18
+ # which you may or may not want. To protect the innocent and prevent nasty surprises,
19
+ # DEFAULT_REJECT was introduced to avoid these by default, but make it possible to override.
20
+ DEFAULT_MATCH = /\A(\p{Graph}|[ \t\r\n])*\z/u
21
+
22
+ # Default reject applied to all input (in addition to user's reject(s)).
23
+ DEFAULT_REJECT = /\p{Cf}|\p{Co}/u
24
+
14
25
  # Minimum hash key value we allow by default.
15
26
  DEFAULT_MIN_KEY = 0
16
27
 
@@ -49,6 +60,9 @@ class FormInput
49
60
  match_msg: "%p like this is not valid",
50
61
  }
51
62
 
63
+ # Character used as default replacement for invalid characters when formatting values.
64
+ DEFAULT_REPLACEMENT_CHARACTER = '?'
65
+
52
66
  # Parameter options which can be merged together into an array when multiple option hashes are merged.
53
67
  MERGEABLE_OPTIONS = [ :check, :test ]
54
68
 
@@ -92,8 +106,17 @@ class FormInput
92
106
  form ? form[ name ] : nil
93
107
  end
94
108
 
109
+ # Make sure given string is in our default encoding and contains only valid characters.
110
+ def cleanup_string( string, replacement )
111
+ unless string.valid_encoding? && ( string.encoding == DEFAULT_ENCODING || string.ascii_only? )
112
+ string = string.dup.force_encoding( DEFAULT_ENCODING ).scrub( replacement )
113
+ end
114
+ string
115
+ end
116
+
95
117
  # Format given value for form/URL output, applying the formatting filter as necessary.
96
- def format_value( value )
118
+ def format_value( value, replacement = DEFAULT_REPLACEMENT_CHARACTER )
119
+ value = cleanup_string( value, replacement ) if replacement and value.is_a?( String )
97
120
  if format.nil? or value.nil? or ( value.is_a?( String ) and type = self[ :class ] and type != String )
98
121
  value.to_s
99
122
  else
@@ -102,16 +125,26 @@ class FormInput
102
125
  end
103
126
 
104
127
  # Get value of this parameter for use in form/URL, with all scalar values converted to strings.
105
- def form_value
128
+ def formatted_value( replacement = DEFAULT_REPLACEMENT_CHARACTER )
106
129
  if array?
107
- [ *value ].map{ |x| format_value( x ) }
130
+ [ *value ].map{ |x| format_value( x, replacement ) }
108
131
  elsif hash?
109
- Hash[ [ *value ].map{ |k, v| [ k.to_s, format_value( v ) ] } ]
132
+ Hash[ [ *value ].map{ |k, v| [ replacement ? cleanup_string( k.to_s, replacement ) : k.to_s, format_value( v, replacement ) ] } ]
110
133
  else
111
- format_value( value )
134
+ format_value( value, replacement )
112
135
  end
113
136
  end
114
137
 
138
+ # Get value of this parameter for use in form, with all scalar values converted to strings.
139
+ def form_value
140
+ formatted_value
141
+ end
142
+
143
+ # Get value of this parameter for use in URL, with all scalar values converted to strings.
144
+ def url_value
145
+ formatted_value( nil )
146
+ end
147
+
115
148
  # Test if given parameter has value of correct type.
116
149
  def correct?
117
150
  case v = value
@@ -162,6 +195,16 @@ class FormInput
162
195
  not empty?
163
196
  end
164
197
 
198
+ # Test if value of given parameter was set.
199
+ def set?
200
+ form ? form.instance_variable_defined?( "@#{name}" ) : false
201
+ end
202
+
203
+ # Test if value of given parameter is not set.
204
+ def unset?
205
+ not set?
206
+ end
207
+
165
208
  # Get the proper name for use in form names, adding [] to array and [key] to hash parameters.
166
209
  def form_name( key = nil )
167
210
  if array?
@@ -442,7 +485,7 @@ class FormInput
442
485
  # If there is a key pattern specified, make sure the key matches.
443
486
 
444
487
  if patterns = self[ :match_key ]
445
- unless [ *patterns ].all?{ |x| value.to_s =~ x }
488
+ unless value.to_s.valid_encoding? and [ *patterns ].all?{ |x| value.to_s =~ x }
446
489
  report( :match_key )
447
490
  return
448
491
  end
@@ -553,7 +596,7 @@ class FormInput
553
596
  return
554
597
  end
555
598
 
556
- unless value =~ /\A(\p{Graph}|[ \t\r\n])*\z/u
599
+ unless value =~ DEFAULT_MATCH && value !~ DEFAULT_REJECT
557
600
  report( :invalid_characters )
558
601
  return
559
602
  end
@@ -835,8 +878,10 @@ class FormInput
835
878
  # Import parameter values from given request or hash. Applies parameter input filters and transforms as well.
836
879
  # Returns self for chaining.
837
880
  def import( request )
881
+ hash = request.respond_to?( :params ) ? request.params : request.to_hash
838
882
  for name, param in @params
839
- if value = request[ param.code ]
883
+ value = hash.fetch( param.code ) { hash.fetch( param.code.to_s, self ) }
884
+ unless value == self
840
885
  value = sanitize_value( value, param.filter )
841
886
  if transform = param.transform
842
887
  value = value.instance_exec( &transform )
@@ -856,12 +901,20 @@ class FormInput
856
901
  self
857
902
  end
858
903
 
904
+ # Unset values of given parameters. Both names and parameters are accepted.
905
+ # Returns self for chaining.
906
+ def unset( name, *names )
907
+ clear( name, *names )
908
+ end
909
+
859
910
  # Clear all/given parameter values. Both names and parameters are accepted.
860
911
  # Returns self for chaining.
861
912
  def clear( *names )
862
913
  names = names.empty? ? params_names : validate_names( names )
863
914
  for name in names
915
+ # Set the value to nil first so it triggers anything necessary.
864
916
  self[ name ] = nil
917
+ remove_instance_variable( "@#{name}" )
865
918
  end
866
919
  self
867
920
  end
@@ -883,7 +936,7 @@ class FormInput
883
936
  end
884
937
 
885
938
  # Return all non-empty parameters as a hash.
886
- # See also #to_data, which creates a hash of non-nil parameters,
939
+ # See also #to_data, which creates a hash of set parameters,
887
940
  # and #url_params, which creates a hash suitable for url output.
888
941
  def to_hash
889
942
  result = {}
@@ -892,13 +945,13 @@ class FormInput
892
945
  end
893
946
  alias to_h to_hash
894
947
 
895
- # Return all non-nil parameters as a hash.
948
+ # Return all set parameters as a hash.
896
949
  # Note that the keys are external names of the parameters (should they differ),
897
950
  # so the keys created by `from_data(data).to_data` remain consistent.
898
951
  # See also #to_hash, which creates a hash of non-empty parameters.
899
952
  def to_data
900
953
  result = {}
901
- params.each{ |x| result[ x.code ] = x.value unless x.value.nil? }
954
+ set_params.each{ |x| result[ x.code ] = x.value }
902
955
  result
903
956
  end
904
957
 
@@ -914,11 +967,7 @@ class FormInput
914
967
 
915
968
  # Create copy of itself, with given parameters unset. Both names and parameters are accepted.
916
969
  def except( *names )
917
- result = dup
918
- for name in validate_names( names )
919
- result[ name ] = nil
920
- end
921
- result
970
+ dup.clear( names )
922
971
  end
923
972
 
924
973
  # Create copy of itself, with only given parameters set. Both names and parameters are accepted.
@@ -926,11 +975,7 @@ class FormInput
926
975
  # It would be easier to create new instance here and only copy selected values,
927
976
  # but we want to use dup instead of new here, as the derived form can use
928
977
  # different parameters in its construction.
929
- result = dup
930
- for name in params_names - validate_names( names )
931
- result[ name ] = nil
932
- end
933
- result
978
+ dup.clear( params_names - validate_names( names ) )
934
979
  end
935
980
 
936
981
  # Parameter lists.
@@ -990,6 +1035,18 @@ class FormInput
990
1035
  end
991
1036
  alias filled_parameters filled_params
992
1037
 
1038
+ # Get list of parameters whose values were set.
1039
+ def set_params
1040
+ params.select{ |x| x.set? }
1041
+ end
1042
+ alias set_parameters set_params
1043
+
1044
+ # Get list of parameters whose values were not set.
1045
+ def unset_params
1046
+ params.select{ |x| x.unset? }
1047
+ end
1048
+ alias unset_parameters unset_params
1049
+
993
1050
  # Get list of required parameters.
994
1051
  def required_params
995
1052
  params.select{ |x| x.required? }
@@ -1089,15 +1146,32 @@ class FormInput
1089
1146
  # Get hash of all non-empty parameters for use in URL.
1090
1147
  def url_params
1091
1148
  result = {}
1092
- filled_params.each{ |x| result[ x.code ] = x.form_value }
1149
+ filled_params.each{ |x| result[ x.code ] = x.url_value }
1093
1150
  result
1094
1151
  end
1095
1152
  alias url_parameters url_params
1096
1153
  alias to_params url_params
1097
1154
 
1155
+ # Escape given string for use in URL query.
1156
+ def url_escape( string )
1157
+ URI.encode_www_form_component( string )
1158
+ end
1159
+
1160
+ # Build URL query from given URL parameters.
1161
+ def build_url_query( value, prefix = nil )
1162
+ case value
1163
+ when Hash
1164
+ value.map{ |k, v| k = url_escape( k ) ; build_url_query( v, prefix ? "#{prefix}[#{k}]" : k ) }.join( '&' )
1165
+ when Array
1166
+ value.map{ |v| build_url_query( v, "#{prefix}[]" ) }.join( '&' )
1167
+ else
1168
+ "#{prefix}=#{url_escape( value )}"
1169
+ end
1170
+ end
1171
+
1098
1172
  # Create string containing URL query from all current non-empty parameters.
1099
1173
  def url_query
1100
- Rack::Utils.build_nested_query( url_params )
1174
+ build_url_query( url_params )
1101
1175
  end
1102
1176
 
1103
1177
  # Extend given URL with query created from all current non-empty parameters.
@@ -3,7 +3,7 @@
3
3
  class FormInput
4
4
  module Version
5
5
  MAJOR = 1
6
- MINOR = 2
6
+ MINOR = 4
7
7
  PATCH = 0
8
8
  STRING = [ MAJOR, MINOR, PATCH ].join( '.' ).freeze
9
9
  end
data/test/helper.rb CHANGED
@@ -14,7 +14,9 @@ end unless jruby?
14
14
 
15
15
  if ENV[ 'COVERAGE' ]
16
16
  require 'simplecov'
17
- SimpleCov.start
17
+ SimpleCov.start do
18
+ add_filter 'bundler'
19
+ end
18
20
  end
19
21
 
20
22
  # EOF #
data/test/test_core.rb CHANGED
@@ -115,7 +115,15 @@ describe FormInput do
115
115
  [ 'q is required', q: "\u{0000}" ], # Because strip strips \0 as well.
116
116
  [ 'q may not contain invalid characters', q: "a\u{0000}b" ],
117
117
  [ 'q may not contain invalid characters', q: "\u{0001}" ],
118
- [ 'q may not contain invalid characters', q: "\u{2029}" ],
118
+ [ 'q may not contain invalid characters', q: "\u{2029}" ], # NBSP
119
+ [ 'q may not contain invalid characters', q: "\u{00AD}" ], # Soft-hyphen
120
+ [ 'q may not contain invalid characters', q: "\u{FEFF}" ], # BOM, ZWNBSP
121
+ [ 'q may not contain invalid characters', q: "\u{E000}" ], # Private use
122
+ [ 'q may not contain invalid characters', q: "\u{F8FF}" ], # Private use
123
+ [ 'q may not contain invalid characters', q: "\u{F0000}" ], # Private use plane 15
124
+ [ 'q may not contain invalid characters', q: "\u{FFFFD}" ], # Private use plane 15
125
+ [ 'q may not contain invalid characters', q: "\u{100000}" ], # Private use plane 16
126
+ [ 'q may not contain invalid characters', q: "\u{10FFFD}" ], # Private use plane 16
119
127
  [ 'email address like this is not valid', email: 'abc' ],
120
128
  [ 'email address may have at most 255 characters', email: 'a@' + 'a' * 254 ],
121
129
  [ 'email address may have at most 255 bytes', email: 'á@' + 'a' * 253 ],
@@ -782,6 +790,9 @@ describe FormInput do
782
790
  names( f.empty_params ).should == [ :age, :rate, :password, :opts, :on ]
783
791
  names( f.blank_params ).should == [ :email, :age, :rate, :password, :opts, :on ]
784
792
 
793
+ names( f.set_params ).should == [ :query, :email, :text, :password ]
794
+ names( f.unset_params ).should == [ :age, :rate, :opts, :on ]
795
+
785
796
  names( f.tagged_params ).should == [ :age, :rate ]
786
797
  names( f.untagged_params ).should == [ :query, :email, :text, :password, :opts, :on ]
787
798
 
@@ -857,6 +868,8 @@ describe FormInput do
857
868
  p.should.not.be.blank
858
869
  p.should.not.be.empty
859
870
  p.should.be.filled
871
+ p.should.be.set
872
+ p.should.not.be.unset
860
873
  p.should.be.valid
861
874
  p.should.not.be.invalid
862
875
  p.should.be.required
@@ -890,6 +903,8 @@ describe FormInput do
890
903
  p.should.be.blank
891
904
  p.should.not.be.empty
892
905
  p.should.be.filled
906
+ p.should.be.set
907
+ p.should.not.be.unset
893
908
  p.should.not.be.valid
894
909
  p.should.be.invalid
895
910
  p.should.not.be.required
@@ -914,6 +929,8 @@ describe FormInput do
914
929
  p.should.be.blank
915
930
  p.should.be.empty
916
931
  p.should.not.be.filled
932
+ p.should.not.be.set
933
+ p.should.be.unset
917
934
  p.should.be.disabled
918
935
  p.should.not.be.enabled
919
936
  p[ :tag ].should == :mix
@@ -942,6 +959,8 @@ describe FormInput do
942
959
  p.should.be.blank
943
960
  p.should.be.empty
944
961
  p.should.not.be.filled
962
+ p.should.not.be.set
963
+ p.should.be.unset
945
964
  p.should.be.array
946
965
  p.should.not.be.hash
947
966
  p.should.not.be.scalar
@@ -953,6 +972,8 @@ describe FormInput do
953
972
  p.should.be.blank
954
973
  p.should.be.empty
955
974
  p.should.not.be.filled
975
+ p.should.not.be.set
976
+ p.should.be.unset
956
977
  p.should.not.be.array
957
978
  p.should.be.hash
958
979
  p.should.not.be.scalar
@@ -990,6 +1011,8 @@ describe FormInput do
990
1011
  f.except( [ :email, :query ] ).url_query.should == "text=foo"
991
1012
  f.except( [] ).url_query.should == "q=x&email=a%40b&text=foo"
992
1013
 
1014
+ f.except( :email ).to_data.should == { q: 'x', text: 'foo' }
1015
+
993
1016
  f.only( :email ).url_query.should == "email=a%40b"
994
1017
  f.only( :email, :query ).url_query.should == "q=x&email=a%40b"
995
1018
  f.only().url_query.should == ""
@@ -997,15 +1020,34 @@ describe FormInput do
997
1020
  f.only( [ :email, :query ] ).url_query.should == "q=x&email=a%40b"
998
1021
  f.only( [] ).url_query.should == ""
999
1022
 
1023
+ f.only( :email ).to_data.should == { email: 'a@b' }
1024
+
1025
+ d = f.dup
1026
+ d.unset( :text )
1027
+ d.should.not.be.empty
1028
+ d.url_query.should == "q=x&email=a%40b"
1029
+ d.to_data.should == { q: 'x', email: 'a@b' }
1030
+ d.unset( d.required_params )
1031
+ d.should.not.be.empty
1032
+ d.url_query.should == "email=a%40b"
1033
+ d.to_data.should == { email: 'a@b' }
1034
+ d.unset( [] )
1035
+ d.should.not.be.empty
1036
+ d.url_query.should == "email=a%40b"
1037
+ d.to_data.should == { email: 'a@b' }
1038
+
1000
1039
  f.clear( :text )
1001
1040
  f.should.not.be.empty
1002
1041
  f.url_query.should == "q=x&email=a%40b"
1042
+ f.to_data.should == { q: 'x', email: 'a@b' }
1003
1043
  f.clear( f.required_params )
1004
1044
  f.should.not.be.empty
1005
1045
  f.url_query.should == "email=a%40b"
1046
+ f.to_data.should == { email: 'a@b' }
1006
1047
  f.clear
1007
1048
  f.should.be.empty
1008
1049
  f.url_query.should == ""
1050
+ f.to_data.should == {}
1009
1051
 
1010
1052
  f = TestForm.new( { age: 2, query: "x" }, { rate: 1, query: "y" } )
1011
1053
  f.url_query.should == "q=y&age=2&rate=1"
@@ -1052,6 +1094,8 @@ describe FormInput do
1052
1094
  f.named_params( :typo, :missing ).should == [ nil, nil ]
1053
1095
 
1054
1096
  ->{ f.set( typo: 10 ) }.should.raise( NoMethodError )
1097
+ ->{ f.unset() }.should.raise( ArgumentError )
1098
+ ->{ f.unset( :typo ) }.should.raise( ArgumentError )
1055
1099
  ->{ f.clear( :typo ) }.should.raise( ArgumentError )
1056
1100
  ->{ f.except( :typo ) }.should.raise( ArgumentError )
1057
1101
  ->{ f.except( [ :typo ] ) }.should.raise( ArgumentError )
@@ -1109,6 +1153,7 @@ describe FormInput do
1109
1153
  f = TestForm.new( query: true, age: 3, rate: 0.35, text: false, opts: [], on: {} )
1110
1154
  ->{ f.validate }.should.not.raise
1111
1155
  f.to_hash.should == { query: true, age: 3, rate: 0.35, text: false }
1156
+ f.to_data.should == { q: true, age: 3, rate: 0.35, text: false, opts: [], on: {} }
1112
1157
  f.url_params.should == { q: "true", age: "3", rate: "0.35", text: "false" }
1113
1158
  f.url_query.should == "q=true&age=3&rate=0.35&text=false"
1114
1159
  names( f.incorrect_params ).should == [ :query, :rate, :text ]
@@ -1116,6 +1161,7 @@ describe FormInput do
1116
1161
  f = TestForm.new( opts: 1 )
1117
1162
  ->{ f.validate }.should.not.raise
1118
1163
  f.to_hash.should == { opts: 1 }
1164
+ f.to_data.should == { opts: 1 }
1119
1165
  f.url_params.should == { opts: [ "1" ] }
1120
1166
  f.url_query.should == "opts[]=1"
1121
1167
  names( f.incorrect_params ).should == [ :opts ]
@@ -1123,6 +1169,7 @@ describe FormInput do
1123
1169
  f = TestForm.new( opts: [ 2.5, true ] )
1124
1170
  ->{ f.validate }.should.not.raise
1125
1171
  f.to_hash.should == { opts: [ 2.5, true ] }
1172
+ f.to_data.should == { opts: [ 2.5, true ] }
1126
1173
  f.url_params.should == { opts: [ "2.5", "true" ] }
1127
1174
  f.url_query.should == "opts[]=2.5&opts[]=true"
1128
1175
  names( f.incorrect_params ).should == []
@@ -1130,6 +1177,7 @@ describe FormInput do
1130
1177
  f = TestForm.new( opts: { "foo" => 10, true => false } )
1131
1178
  ->{ f.validate }.should.not.raise
1132
1179
  f.to_hash.should == { opts: { "foo" => 10, true => false } }
1180
+ f.to_data.should == { opts: { "foo" => 10, true => false } }
1133
1181
  f.url_params.should == { opts: [ '["foo", 10]', '[true, false]' ] }
1134
1182
  f.url_query.should == "opts[]=%5B%22foo%22%2C+10%5D&opts[]=%5Btrue%2C+false%5D"
1135
1183
  names( f.incorrect_params ).should == [ :opts ]
@@ -1137,6 +1185,7 @@ describe FormInput do
1137
1185
  f = TestForm.new( on: 1 )
1138
1186
  ->{ f.validate }.should.not.raise
1139
1187
  f.to_hash.should == { on: 1 }
1188
+ f.to_data.should == { on: 1 }
1140
1189
  f.url_params.should == { on: { "1" => "" } }
1141
1190
  f.url_query.should == "on[1]="
1142
1191
  names( f.incorrect_params ).should == [ :on ]
@@ -1144,6 +1193,7 @@ describe FormInput do
1144
1193
  f = TestForm.new( on: { 0 => 1, 2 => 3.4 } )
1145
1194
  ->{ f.validate }.should.not.raise
1146
1195
  f.to_hash.should == { on: { 0 => 1, 2 => 3.4 } }
1196
+ f.to_data.should == { on: { 0 => 1, 2 => 3.4 } }
1147
1197
  f.url_params.should == { on: { "0" => "1", "2" => "3.4" } }
1148
1198
  f.url_query.should == "on[0]=1&on[2]=3.4"
1149
1199
  names( f.incorrect_params ).should == []
@@ -1151,6 +1201,7 @@ describe FormInput do
1151
1201
  f = TestForm.new( on: [ [ 10, 20 ], [ true, false ] ] )
1152
1202
  ->{ f.validate }.should.not.raise
1153
1203
  f.to_hash.should == { on: [ [ 10, 20 ], [ true, false ] ] }
1204
+ f.to_data.should == { on: [ [ 10, 20 ], [ true, false ] ] }
1154
1205
  f.url_params.should == { on: { "10" => "20", "true" => "false" } }
1155
1206
  f.url_query.should == "on[10]=20&on[true]=false"
1156
1207
  names( f.incorrect_params ).should == [ :on ]
@@ -1158,6 +1209,7 @@ describe FormInput do
1158
1209
  f = TestForm.new( on: [ 1, true, false ] )
1159
1210
  ->{ f.validate }.should.not.raise
1160
1211
  f.to_hash.should == { on: [ 1, true, false ] }
1212
+ f.to_data.should == { on: [ 1, true, false ] }
1161
1213
  f.url_params.should == { on: { "1" => "", "true" => "", "false" => "" } }
1162
1214
  f.url_query.should == "on[1]=&on[true]=&on[false]="
1163
1215
  names( f.incorrect_params ).should == [ :on ]
@@ -1165,13 +1217,19 @@ describe FormInput do
1165
1217
 
1166
1218
  should 'handle invalid encoding gracefully' do
1167
1219
  s = 255.chr.force_encoding( 'UTF-8' )
1220
+ b = s.dup.force_encoding( 'BINARY' )
1168
1221
 
1169
1222
  f = TestForm.new( query: s )
1170
1223
  ->{ f.validate }.should.not.raise
1171
1224
  f.should.not.be.valid
1172
1225
  f.error_messages.should == [ "q must use valid encoding" ]
1173
- f.param( :query ).should.not.be.blank
1226
+ p = f.param( :query )
1227
+ p.should.not.be.blank
1228
+ p.should.be.invalid
1229
+ p.form_value.should == '?'
1230
+ p.url_value.should == s
1174
1231
  f.to_hash.should == { query: s }
1232
+ f.to_data.should == { q: s }
1175
1233
  f.url_params.should == { q: s }
1176
1234
  f.url_query.should == "q=%FF"
1177
1235
 
@@ -1179,10 +1237,46 @@ describe FormInput do
1179
1237
  ->{ f.validate }.should.not.raise
1180
1238
  f.should.not.be.valid
1181
1239
  f.error_messages.should == [ "q must use valid encoding" ]
1182
- f.param( :query ).should.not.be.blank
1183
- f.to_hash.should == { query: s.dup.force_encoding( 'BINARY' ) }
1184
- f.url_params.should == { q: s.dup.force_encoding( 'BINARY' ) }
1240
+ p = f.param( :query )
1241
+ p.should.not.be.blank
1242
+ p.should.be.invalid
1243
+ p.form_value.should == '?'
1244
+ p.url_value.should == b
1245
+ f.to_hash.should == { query: b }
1246
+ f.to_data.should == { q: b }
1247
+ f.url_params.should == { q: b }
1185
1248
  f.url_query.should == "q=%FF"
1249
+
1250
+ c = Class.new( FormInput )
1251
+ c.hash :hsh, :h, match_key: /\A\w+\z/
1252
+
1253
+ f = c.new( hsh: { s => 1, 2 => s } )
1254
+ ->{ f.validate }.should.not.raise
1255
+ f.should.not.be.valid
1256
+ f.error_messages.should == [ "h contain invalid key" ]
1257
+ p = f.param( :hsh )
1258
+ p.should.not.be.blank
1259
+ p.should.be.invalid
1260
+ p.form_value.should == { '?' => '1', '2' => '?' }
1261
+ p.url_value.should == { s => '1', '2' => s }
1262
+ f.to_hash.should == { hsh: { s => 1, 2 => s } }
1263
+ f.to_data.should == { h: { s => 1, 2 => s } }
1264
+ f.url_params.should == { h: { s => '1', '2' => s } }
1265
+ f.url_query.should == "h[%FF]=1&h[2]=%FF"
1266
+
1267
+ f = c.new( request( "?h[%ff]=1&h[2]=%ff" ) )
1268
+ ->{ f.validate }.should.not.raise
1269
+ f.should.not.be.valid
1270
+ f.error_messages.should == [ "h contain invalid key" ]
1271
+ p = f.param( :hsh )
1272
+ p.should.not.be.blank
1273
+ p.should.be.invalid
1274
+ p.form_value.should == { '?' => '1', '2' => '?' }
1275
+ p.url_value.should == { s => '1', '2' => b }
1276
+ f.to_hash.should == { hsh: { s => '1', 2 => b } }
1277
+ f.to_data.should == { h: { s => '1', 2 => b } }
1278
+ f.url_params.should == { h: { s => '1', '2' => b } }
1279
+ f.url_query.should == "h[%FF]=1&h[2]=%FF"
1186
1280
  end
1187
1281
 
1188
1282
  should 'reject unexpected values in request input' do
@@ -1208,10 +1302,11 @@ describe FormInput do
1208
1302
  opts: [],
1209
1303
  on: {},
1210
1304
  }
1211
- f = TestForm.from_data(data)
1305
+ f = TestForm.from_data( data )
1212
1306
  f.to_data.should == data
1213
1307
 
1214
- f = TestForm.from_data(data.merge(password: nil))
1308
+ data[ :password ] = nil
1309
+ f = TestForm.from_data( data )
1215
1310
  f.to_data.should == data
1216
1311
 
1217
1312
  f.to_hash.should == {
@@ -1220,8 +1315,6 @@ describe FormInput do
1220
1315
  age: 10,
1221
1316
  rate: 0.5,
1222
1317
  }
1223
- f.valid?(:age).should.be.true # has filter and correct class
1224
- f.valid?(:rate).should.be.false # has no filter and class
1225
1318
  end
1226
1319
 
1227
1320
  should 'make it easy to create URLs' do
@@ -1370,6 +1463,13 @@ describe FormInput do
1370
1463
  f.dup.validate?.should.be.valid
1371
1464
  f.validate.should.be.valid
1372
1465
  f.validate!.should.be.valid
1466
+
1467
+ f.unset( :query ).should.equal f
1468
+ f.should.be.invalid
1469
+ f.validate?.should.be.invalid
1470
+ f.dup.validate?.should.be.invalid
1471
+ f.validate.should.be.invalid
1472
+ f.validate!.should.be.invalid
1373
1473
  end
1374
1474
 
1375
1475
  should 'support some custom error messages' do
data/test/test_types.rb CHANGED
@@ -7,41 +7,40 @@ require 'form_input/types'
7
7
  require 'rack/test'
8
8
 
9
9
  class TestBasicTypesForm < FormInput
10
-
11
10
  param :int, INTEGER_ARGS
12
11
  param :float, FLOAT_ARGS
13
12
  param :bool, BOOL_ARGS
14
13
  param :checkbox, CHECKBOX_ARGS
14
+ end
15
15
 
16
+ class TestJSONTypesForm < FormInput
17
+ param :str
18
+ param :int, INTEGER_ARGS
19
+ param :float, FLOAT_ARGS
20
+ param :bool, BOOL_ARGS
16
21
  end
17
22
 
18
23
  class TestAddressTypesForm < FormInput
19
-
20
24
  param :email, EMAIL_ARGS
21
25
  param :zip, ZIP_ARGS
22
26
  param :phone, PHONE_ARGS
23
-
24
27
  end
25
28
 
26
29
  class TestTimeTypesForm < FormInput
27
-
28
30
  param :time, TIME_ARGS
29
31
  param :us_date, US_DATE_ARGS
30
32
  param :uk_date, UK_DATE_ARGS
31
33
  param :eu_date, EU_DATE_ARGS
32
34
  param :hours, HOURS_ARGS
33
-
34
35
  end
35
36
 
36
37
  class TestPrunedTypesForm < FormInput
37
-
38
38
  param :str, PRUNED_ARGS
39
39
  param :int, INTEGER_ARGS, PRUNED_ARGS
40
40
  array :arr, PRUNED_ARGS
41
41
  array :int_arr, INTEGER_ARGS, PRUNED_ARGS
42
42
  hash :hsh, PRUNED_ARGS
43
43
  hash :int_hsh, INTEGER_ARGS, PRUNED_ARGS
44
-
45
44
  end
46
45
 
47
46
  class Bacon::Context
@@ -131,6 +130,33 @@ describe FormInput do
131
130
  f.url_query.should == "int=a&float=b&bool=false&checkbox=true"
132
131
  end
133
132
 
133
+ should 'provide presets for standard JSON types' do
134
+ f = TestJSONTypesForm.from_data( {} )
135
+ f.should.be.valid
136
+ f.to_data.should == {}
137
+
138
+ f = TestJSONTypesForm.from_data( str: "foo", int: "0123", float: "0123.456", bool: "true" )
139
+ f.should.be.valid
140
+ f.to_data.should == { str: 'foo', int: 123, float: 123.456, bool: true }
141
+
142
+ f = TestJSONTypesForm.from_data( str: nil, int: 123, float: 123.456, bool: true )
143
+ f.should.be.valid
144
+ f.to_data.should == { str: nil, int: 123, float: 123.456, bool: true }
145
+
146
+ f = TestJSONTypesForm.from_data( bool: "false" )
147
+ f.should.be.valid
148
+ f.to_data.should == { bool: false }
149
+
150
+ f = TestJSONTypesForm.from_data( bool: false )
151
+ f.should.be.valid
152
+ f.to_data.should == { bool: false }
153
+
154
+ f = TestJSONTypesForm.from_data( int: "a", float: "b", bool: "c" )
155
+ f.should.be.invalid
156
+ names( f.invalid_params ).should == [ :int, :float ]
157
+ f.to_data.should == { int: "a", float: "b", bool: false }
158
+ end
159
+
134
160
  should 'provide presets for address parameter types' do
135
161
  f = TestAddressTypesForm.new( request( "?email=me@1.com&zip=12345&phone=123%20456%20789" ) )
136
162
  f.should.be.valid
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: form_input
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Rak
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-21 00:00:00.000000000 Z
11
+ date: 2025-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: '1.5'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '3.0'
22
+ version: '4.0'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,21 @@ dependencies:
29
29
  version: '1.5'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '3.0'
32
+ version: '4.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: rake
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '13.0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '13.0'
33
47
  - !ruby/object:Gem::Dependency
34
48
  name: bacon
35
49
  requirement: !ruby/object:Gem::Requirement
@@ -45,33 +59,53 @@ dependencies:
45
59
  - !ruby/object:Gem::Version
46
60
  version: '1.2'
47
61
  - !ruby/object:Gem::Dependency
48
- name: rack-test
62
+ name: simplecov
49
63
  requirement: !ruby/object:Gem::Requirement
50
64
  requirements:
51
65
  - - "~>"
52
66
  - !ruby/object:Gem::Version
53
- version: '0.6'
67
+ version: '0.21'
54
68
  type: :development
55
69
  prerelease: false
56
70
  version_requirements: !ruby/object:Gem::Requirement
57
71
  requirements:
58
72
  - - "~>"
59
73
  - !ruby/object:Gem::Version
60
- version: '0.6'
74
+ version: '0.21'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rack-test
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '1.0'
82
+ - - "<"
83
+ - !ruby/object:Gem::Version
84
+ version: '3.0'
85
+ type: :development
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '1.0'
92
+ - - "<"
93
+ - !ruby/object:Gem::Version
94
+ version: '3.0'
61
95
  - !ruby/object:Gem::Dependency
62
96
  name: r18n-core
63
97
  requirement: !ruby/object:Gem::Requirement
64
98
  requirements:
65
99
  - - "~>"
66
100
  - !ruby/object:Gem::Version
67
- version: '2.0'
101
+ version: '5.0'
68
102
  type: :development
69
103
  prerelease: false
70
104
  version_requirements: !ruby/object:Gem::Requirement
71
105
  requirements:
72
106
  - - "~>"
73
107
  - !ruby/object:Gem::Version
74
- version: '2.0'
108
+ version: '5.0'
75
109
  description: |
76
110
  This gem allows you to describe your forms using a simple DSL
77
111
  and then takes care of sanitizing, transforming, and validating the input for you,
@@ -94,6 +128,7 @@ files:
94
128
  - example/controllers/ramaze/profile.rb
95
129
  - example/controllers/sinatra/press_release.rb
96
130
  - example/controllers/sinatra/profile.rb
131
+ - example/example.rb
97
132
  - example/forms/change_password_form.rb
98
133
  - example/forms/login_form.rb
99
134
  - example/forms/lost_password_form.rb
@@ -149,18 +184,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
149
184
  requirements:
150
185
  - - ">="
151
186
  - !ruby/object:Gem::Version
152
- version: 2.0.0
187
+ version: 2.1.0
153
188
  required_rubygems_version: !ruby/object:Gem::Requirement
154
189
  requirements:
155
190
  - - ">="
156
191
  - !ruby/object:Gem::Version
157
192
  version: '0'
158
193
  requirements: []
159
- rubyforge_project:
160
- rubygems_version: 2.4.5.1
194
+ rubygems_version: 3.0.3.1
161
195
  signing_key:
162
196
  specification_version: 4
163
197
  summary: Form helper which sanitizes, transforms, validates and encapsulates web request
164
198
  input.
165
199
  test_files: []
166
- has_rdoc: