form_input 1.1.0 → 1.2.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
  SHA1:
3
- metadata.gz: d7eb22ab0a9fbdbf797fd70548c4caed7201b4e7
4
- data.tar.gz: d8fd5dbc4f0e7a10238aedddb889c37163e419f6
3
+ metadata.gz: 4014e07ca32e3c7a76ee15db0dfe478a4103d123
4
+ data.tar.gz: 83224f22dc199d2a3f1bf4d46011f616195d33ca
5
5
  SHA512:
6
- metadata.gz: 5d8ccd8693068596ca26d4d6b15ddfbd5434b558062de2371a154c1b5e562712792065ba4ec32bc937bcd4870cac1a4f4bab4e428409f02d75303d8707c9e9ad
7
- data.tar.gz: 7df1a5feca9a80307e1f1fbeb6f5a1e840775743cf96136a45e723a7cd434117563b297d57264cb5169dab8534cc125ea5af3a0009a99cb6123d4e7ce4024946
6
+ metadata.gz: 82f8812fbd77215e7a058f1585bc7b2ee2a5082215d4247ae865b8ced6e2c739c39e836741482ebf4cb089fe57b6c330f8b19726d8998ed8cfca48910faf92ba
7
+ data.tar.gz: e2e6853f4eb58193d11aa22696164844130c5c653df6b872372a40bfa1d790a8b61cf45bd287046cf7c375c14326709c49d907ba51e5779f0d3a72ad9ff12152
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ = 1.2.0
2
+
3
+ * 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.
6
+
1
7
  = 1.1.0
2
8
 
3
9
  * Added report! methods for late reporting of the most fundamental errors.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2015-2018 Patrik Rak (patrik@raxoft.cz)
1
+ Copyright (c) 2015-2019 Patrik Rak (patrik@raxoft.cz)
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -86,6 +86,7 @@ Sounds cool enough? Then read on.
86
86
  * [Errors and Validation](#errors-and-validation)
87
87
  * [Using Forms](#using-forms)
88
88
  * [URL Helpers](#url-helpers)
89
+ * [JSON Helpers](#json-helpers)
89
90
  * [Form Helpers](#form-helpers)
90
91
  * [Extending Forms](#extending-forms)
91
92
  * [Parameter Options](#parameter-options)
@@ -798,13 +799,22 @@ to include parts of the URL as the form input:
798
799
  end
799
800
  ```
800
801
 
802
+ Similarly, you want to use `import` when feeding form input with JSON data (see [JSON Helpers](#json-helpers) for details):
803
+
804
+ ``` ruby
805
+ post '/api/v1/contact' do
806
+ form = ContactForm.new.import( json_data )
807
+ end
808
+ ```
809
+
801
810
  If you are worried that you might make a mistake,
802
- you can use one of the three helper shortcuts
811
+ you can use one of the four helper shortcuts
803
812
  which make it easier to remember which one to use when:
804
813
 
805
814
  ``` ruby
806
815
  form = ContactForm.from_request( request ) # Like new.import, for Rack request with external values.
807
816
  form = ContactForm.from_params( params ) # Like new.import, for params hash of external values.
817
+ form = ContactForm.from_data( json_data ) # Like new.import, for JSON hash of external values.
808
818
  form = ContactForm.from_hash( some_hash ) # Like new.set, for hash of internal values.
809
819
  ```
810
820
 
@@ -1254,7 +1264,7 @@ regardless of if it comes from a form post or from the URL query string.
1254
1264
  It is therefore quite natural that the `FormInput` provides helpers for generating
1255
1265
  URL query strings as well in addition to helpers used for form creation.
1256
1266
 
1257
- You can get a hash of filled parameters suitable for use in the URL query by using the `url_params` method,
1267
+ You can get a hash of filled parameters suitable for use in the URL query by using the `url_params` (AKA `to_params`) method,
1258
1268
  or get them combined into the URL query string by using the `url_query` method.
1259
1269
  Note that the `url_params` result differs considerably from the result of the `to_hash` method,
1260
1270
  as it uses parameter code rather than name for keys and their external representation for the values:
@@ -1324,6 +1334,74 @@ Finally, if you do not like the idea of parameter arrays in your URLs, you can u
1324
1334
  Just note that none of the standard array parameter validations apply in this case,
1325
1335
  so make sure to apply your own validations using the `:check` callback if you need to.
1326
1336
 
1337
+ ### JSON Helpers
1338
+
1339
+ The `Form Input` can also help a great deal with validating JSON data commonly used in REST API endpoints.
1340
+ There are two methods which are quite useful in this case, `from_data` and `to_data`.
1341
+ The former imports the data from an external input hash,
1342
+ which may contain data in either internal or external form,
1343
+ while the latter creates the hash containing the data in their canonic internal form.
1344
+
1345
+ When you create a form input from request or params, the external parameter values are always strings.
1346
+ But in case of JSON data, the values can be also numbers, booleans or `nil`.
1347
+ Fortunately, the `import` and `from_data` methods can deal with such input as well.
1348
+ The only difference is that the [input filter](#input-filter) is not run in such case.
1349
+ The [input transform](#input-transform) is run as usual,
1350
+ and so are all the [validation methods](#errors-and-validation).
1351
+
1352
+ Among other things this means that you can declare a parameter as integer using the `INTEGER_ARGS` macro,
1353
+ and pass in the value as either string or integer, and you end up with integer in both cases, which is pretty convenient.
1354
+
1355
+ ``` ruby
1356
+ class NumericInput < FormInput
1357
+ param :int, INTEGER_ARGS
1358
+ param :float, FLOAT_ARGS
1359
+ end
1360
+
1361
+ NumericInput.from_data( int: '10', float: 3.0 ).to_data # { int: 10, float: 3.0 }
1362
+ NumericInput.from_data( int: 10, float: '3.0' ).to_data # { int: 10, float: 3.0 }
1363
+ ```
1364
+
1365
+ 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
1368
+ (unlike the `to_hash` and `to_params` methods).
1369
+ The `nil` values are still filtered out, though.
1370
+
1371
+ ``` ruby
1372
+ class OptionalInput < FormInput
1373
+ param :string
1374
+ array :array
1375
+ hash :hash
1376
+ end
1377
+
1378
+ OptionalInput.from_data( string: '' ).to_data # { string: '' }
1379
+ OptionalInput.from_data( array: [] ).to_data # { array: [] }
1380
+ OptionalInput.from_data( hash: {} ).to_data # { hash: {} }
1381
+ ```
1382
+
1383
+ The whole JSON processing may in the end look something like this:
1384
+
1385
+ ``` ruby
1386
+ require 'oj'
1387
+ def json_data
1388
+ data = Oj.load( request.body, symbolize_keys: true )
1389
+ halt( 422, 'Invalid data' ) unless Hash === data
1390
+ data
1391
+ rescue Oj::Error
1392
+ halt( 422, 'Invalid JSON' )
1393
+ end
1394
+
1395
+ post '/api/v1/contact' do
1396
+ input = ContactForm.from_data( json_data )
1397
+ halt( 422, "Invalid data: #{input.error_messages.first}" ) unless input.valid?
1398
+ # Somehow use the input.
1399
+ Contact.create( input.to_data )
1400
+ end
1401
+ ```
1402
+
1403
+ Not bad for having completely validated and converted input data, is it?
1404
+
1327
1405
  ### Form Helpers
1328
1406
 
1329
1407
  It may come as a surprise, but `FormInput` provides no helpers for creating HTML tags.
@@ -3178,7 +3256,7 @@ Thanks for that.
3178
3256
 
3179
3257
  ## Credits
3180
3258
 
3181
- Copyright &copy; 2015-2016 Patrik Rak
3259
+ Copyright &copy; 2015-2019 Patrik Rak
3182
3260
 
3183
3261
  Translations contributed by
3184
3262
  Maroš Rovňák (Slovak)
@@ -742,6 +742,7 @@ class FormInput
742
742
  def from_params( params )
743
743
  new.import( params )
744
744
  end
745
+ alias from_data from_params
745
746
 
746
747
  # Create new form from hash with internal values.
747
748
  def from_hash( hash )
@@ -821,6 +822,10 @@ class FormInput
821
822
  # The validation done later ensures that the keys are valid, within range,
822
823
  # and that only flat hashes are allowed.
823
824
  Hash[ value.map{ |k, v| [ ( Integer( k, 10 ) rescue k ), sanitize_value( v, filter ) ] } ]
825
+ when Numeric, TrueClass, FalseClass, NilClass
826
+ # For convenience of importing JSON payloads, allow each of these simple scalar types as they are.
827
+ # The validation done later will ensure that the type class matches the parameter.
828
+ value
824
829
  else
825
830
  fail TypeError, "unexpected parameter type"
826
831
  end
@@ -878,7 +883,8 @@ class FormInput
878
883
  end
879
884
 
880
885
  # Return all non-empty parameters as a hash.
881
- # See also url_params, which creates a hash suitable for url output.
886
+ # See also #to_data, which creates a hash of non-nil parameters,
887
+ # and #url_params, which creates a hash suitable for url output.
882
888
  def to_hash
883
889
  result = {}
884
890
  filled_params.each{ |x| result[ x.name ] = x.value }
@@ -886,6 +892,16 @@ class FormInput
886
892
  end
887
893
  alias to_h to_hash
888
894
 
895
+ # Return all non-nil parameters as a hash.
896
+ # Note that the keys are external names of the parameters (should they differ),
897
+ # so the keys created by `from_data(data).to_data` remain consistent.
898
+ # See also #to_hash, which creates a hash of non-empty parameters.
899
+ def to_data
900
+ result = {}
901
+ params.each{ |x| result[ x.code ] = x.value unless x.value.nil? }
902
+ result
903
+ end
904
+
889
905
  # Convert parameters to names and fail if we encounter unknown one.
890
906
  def validate_names( names )
891
907
  names.flatten.map do |name|
@@ -1077,6 +1093,7 @@ class FormInput
1077
1093
  result
1078
1094
  end
1079
1095
  alias url_parameters url_params
1096
+ alias to_params url_params
1080
1097
 
1081
1098
  # Create string containing URL query from all current non-empty parameters.
1082
1099
  def url_query
@@ -3,7 +3,7 @@
3
3
  class FormInput
4
4
  module Version
5
5
  MAJOR = 1
6
- MINOR = 1
6
+ MINOR = 2
7
7
  PATCH = 0
8
8
  STRING = [ MAJOR, MINOR, PATCH ].join( '.' ).freeze
9
9
  end
data/test/test_core.rb CHANGED
@@ -459,6 +459,7 @@ describe FormInput do
459
459
  arr: [ "a", "b" ],
460
460
  hsh: { "1" => "2", "3" => "4" },
461
461
  }
462
+ f.url_params.should == f.to_params
462
463
  f.url_params.should == {
463
464
  str: "1.5",
464
465
  int: "1",
@@ -485,6 +486,7 @@ describe FormInput do
485
486
  time: "e",
486
487
  bool: false,
487
488
  }
489
+ f.url_params.should == f.to_params
488
490
  f.url_params.should == {
489
491
  str: "a",
490
492
  int: "0",
@@ -1184,14 +1186,44 @@ describe FormInput do
1184
1186
  end
1185
1187
 
1186
1188
  should 'reject unexpected values in request input' do
1187
- ->{ TestForm.new.import( q: "", opts: [], on: {} ) }.should.not.raise
1188
- ->{ TestForm.new.import( q: [], opts: {}, on: "" ) }.should.not.raise
1189
- ->{ TestForm.new.import( q: {}, opts: "", on: [] ) }.should.not.raise
1190
- ->{ TestForm.new.import( q: 1 ) }.should.raise TypeError
1189
+ ->{ TestForm.new.import( q: "", opts: [], on: {} ).valid?(:query).should.be.false }.should.not.raise
1190
+ ->{ TestForm.new.import( q: [], opts: {}, on: "" ).valid?(:query).should.be.false }.should.not.raise
1191
+ ->{ TestForm.new.import( q: {}, opts: "", on: [] ).valid?(:query).should.be.false }.should.not.raise
1192
+ ->{ TestForm.new.import( q: 1 ).valid?(:query).should.be.false }.should.not.raise
1193
+ ->{ TestForm.new.import( q: true ).valid?(:query).should.be.false }.should.not.raise
1194
+ ->{ TestForm.new.import( q: false ).valid?(:query).should.be.false }.should.not.raise
1195
+ ->{ TestForm.new.import( q: nil ).valid?(:query).should.be.false }.should.not.raise
1191
1196
  ->{ TestForm.new.import( q: :x ) }.should.raise TypeError
1192
1197
  ->{ TestForm.new.import( q: TestForm ) }.should.raise TypeError
1193
1198
  end
1194
1199
 
1200
+ should 'support importing and exporting JSON payloads without modification' do
1201
+ data = {
1202
+ q: 'x',
1203
+ email: 'foo@bar.com',
1204
+ age: 10,
1205
+ rate: 0.5,
1206
+ text: '',
1207
+ # password: nil,
1208
+ opts: [],
1209
+ on: {},
1210
+ }
1211
+ f = TestForm.from_data(data)
1212
+ f.to_data.should == data
1213
+
1214
+ f = TestForm.from_data(data.merge(password: nil))
1215
+ f.to_data.should == data
1216
+
1217
+ f.to_hash.should == {
1218
+ query: 'x',
1219
+ email: 'foo@bar.com',
1220
+ age: 10,
1221
+ rate: 0.5,
1222
+ }
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
+ end
1226
+
1195
1227
  should 'make it easy to create URLs' do
1196
1228
  f = TestForm.new( query: "x", opts: [ 0, 0, 1 ], on: { 0 => 1 }, age: 10, email: nil, password: "", text: " " )
1197
1229
  f.url_params.should == { q: "x", age: "10", text: " ", opts: [ "0", "0", "1" ], on: { "0" => "1" } }
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.1.0
4
+ version: 1.2.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: 2018-03-06 00:00:00.000000000 Z
11
+ date: 2019-06-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack