form_input 0.9.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE +19 -0
  4. data/README.md +3160 -0
  5. data/Rakefile +19 -0
  6. data/example/controllers/ramaze/press_release.rb +104 -0
  7. data/example/controllers/ramaze/profile.rb +38 -0
  8. data/example/controllers/sinatra/press_release.rb +114 -0
  9. data/example/controllers/sinatra/profile.rb +39 -0
  10. data/example/forms/change_password_form.rb +17 -0
  11. data/example/forms/login_form.rb +14 -0
  12. data/example/forms/lost_password_form.rb +14 -0
  13. data/example/forms/new_password_form.rb +15 -0
  14. data/example/forms/password_form.rb +18 -0
  15. data/example/forms/press_release_form.rb +153 -0
  16. data/example/forms/profile_form.rb +21 -0
  17. data/example/forms/signup_form.rb +25 -0
  18. data/example/views/press_release.slim +65 -0
  19. data/example/views/profile.slim +28 -0
  20. data/example/views/snippets/form_block.slim +27 -0
  21. data/example/views/snippets/form_chunked.slim +25 -0
  22. data/example/views/snippets/form_hidden.slim +21 -0
  23. data/example/views/snippets/form_panel.slim +89 -0
  24. data/form_input.gemspec +32 -0
  25. data/lib/form_input/core.rb +1165 -0
  26. data/lib/form_input/localize.rb +49 -0
  27. data/lib/form_input/r18n/cs.yml +97 -0
  28. data/lib/form_input/r18n/en.yml +70 -0
  29. data/lib/form_input/r18n/pl.yml +122 -0
  30. data/lib/form_input/r18n/sk.yml +120 -0
  31. data/lib/form_input/r18n.rb +163 -0
  32. data/lib/form_input/steps.rb +365 -0
  33. data/lib/form_input/types.rb +176 -0
  34. data/lib/form_input/version.rb +12 -0
  35. data/lib/form_input.rb +5 -0
  36. data/test/helper.rb +21 -0
  37. data/test/localize/en.yml +63 -0
  38. data/test/r18n/cs.yml +60 -0
  39. data/test/r18n/xx.yml +51 -0
  40. data/test/reference/cs.txt +352 -0
  41. data/test/reference/cs.yml +14 -0
  42. data/test/reference/en.txt +76 -0
  43. data/test/reference/en.yml +8 -0
  44. data/test/reference/pl.txt +440 -0
  45. data/test/reference/pl.yml +16 -0
  46. data/test/reference/sk.txt +352 -0
  47. data/test/reference/sk.yml +14 -0
  48. data/test/test_core.rb +1272 -0
  49. data/test/test_localize.rb +27 -0
  50. data/test/test_r18n.rb +373 -0
  51. data/test/test_steps.rb +482 -0
  52. data/test/test_types.rb +307 -0
  53. metadata +145 -0
@@ -0,0 +1,1165 @@
1
+ # Form input.
2
+
3
+ # Class for easy form input processing and management.
4
+ class FormInput
5
+
6
+ # Constants.
7
+
8
+ # Default size limit applied to all input.
9
+ DEFAULT_SIZE_LIMIT = 255
10
+
11
+ # Default input filter applied to all input.
12
+ DEFAULT_FILTER = ->{ gsub( /\s+/, ' ' ).strip }
13
+
14
+ # Minimum hash key value we allow by default.
15
+ DEFAULT_MIN_KEY = 0
16
+
17
+ # Maximum hash key value we allow by default.
18
+ DEFAULT_MAX_KEY = ( 1 << 64 ) - 1
19
+
20
+ # Encoding we convert all input request parameters into.
21
+ DEFAULT_ENCODING = Encoding::UTF_8
22
+
23
+ # Hash mapping error codes to default error messages.
24
+ DEFAULT_ERROR_MESSAGES = {
25
+ required_scalar: "%p is required",
26
+ required_array: "%p are required",
27
+ not_array: "%p are not an array",
28
+ not_hash: "%p are not a hash",
29
+ not_string: "%p is not a string",
30
+ match_key: "%p contain invalid key",
31
+ invalid_key: "%p contain invalid key",
32
+ min_key: "%p contain too small key",
33
+ max_key: "%p contain too large key",
34
+ min_count: "%p must have at least",
35
+ max_count: "%p may have at most",
36
+ value_type: "%p like this is not valid",
37
+ element_type: "%p contain invalid value",
38
+ min_limit: "%p must be at least",
39
+ max_limit: "%p may be at most",
40
+ inf_limit: "%p must be greater than",
41
+ sup_limit: "%p must be less than",
42
+ invalid_encoding: "%p must use valid encoding",
43
+ invalid_characters: "%p may not contain invalid characters",
44
+ min_size: "%p must have at least",
45
+ max_size: "%p may have at most",
46
+ min_bytesize: "%p must have at least",
47
+ max_bytesize: "%p may have at most",
48
+ reject_msg: "%p like this is not allowed",
49
+ match_msg: "%p like this is not valid",
50
+ }
51
+
52
+ # Form parameter.
53
+ class Parameter
54
+
55
+ # Form this parameter belongs to.
56
+ attr_reader :form
57
+
58
+ # Name of the parameter as we use it internally in our code.
59
+ attr_reader :name
60
+
61
+ # Name of the parameter as we use it in the form fields and url queries.
62
+ attr_reader :code
63
+
64
+ # Additional parameter options.
65
+ attr_reader :opts
66
+
67
+ # Initialize new parameter.
68
+ def initialize( name, code, opts )
69
+ @name = name.freeze
70
+ @code = code.freeze
71
+ @opts = opts.freeze
72
+ end
73
+
74
+ # Allow copies to evaluate tags again.
75
+ def initialize_dup( other )
76
+ super
77
+ @tags = nil
78
+ end
79
+
80
+ # Bind self to given form instance. Can be done only once.
81
+ def bind( form )
82
+ fail "parameter #{name} is already bound" if @form
83
+ @form = form
84
+ self
85
+ end
86
+
87
+ # Get the value of this parameter. Always nil for unbound parameters.
88
+ def value
89
+ form ? form[ name ] : nil
90
+ end
91
+
92
+ # Format given value for form/URL output, applying the formatting filter as necessary.
93
+ def format_value( value )
94
+ if format.nil? or value.nil? or ( value.is_a?( String ) and type = self[ :class ] and type != String )
95
+ value.to_s
96
+ else
97
+ value.instance_exec( &format ).to_s
98
+ end
99
+ end
100
+
101
+ # Get value of this parameter for use in form/URL, with all scalar values converted to strings.
102
+ def form_value
103
+ if array?
104
+ [ *value ].map{ |x| format_value( x ) }
105
+ elsif hash?
106
+ Hash[ [ *value ].map{ |k, v| [ k.to_s, format_value( v ) ] } ]
107
+ else
108
+ format_value( value )
109
+ end
110
+ end
111
+
112
+ # Test if given parameter has value of correct type.
113
+ def correct?
114
+ case v = value
115
+ when nil
116
+ true
117
+ when String
118
+ scalar?
119
+ when Array
120
+ array?
121
+ when Hash
122
+ hash?
123
+ end or ( scalar? && [ *self[ :class ] ].any?{ |x| v.is_a?( x ) } )
124
+ end
125
+
126
+ # Test if given parameter has value of incorrect type.
127
+ def incorrect?
128
+ not correct?
129
+ end
130
+
131
+ # Test if given parameter has blank value.
132
+ def blank?
133
+ case v = value
134
+ when nil
135
+ true
136
+ when String
137
+ v.empty? or !! ( v.valid_encoding? && v =~ /\A\s*\z/ )
138
+ when Array, Hash
139
+ v.empty?
140
+ else
141
+ false
142
+ end
143
+ end
144
+
145
+ # Test if given parameter has empty value.
146
+ def empty?
147
+ case v = value
148
+ when nil
149
+ true
150
+ when String, Array, Hash
151
+ v.empty?
152
+ else
153
+ false
154
+ end
155
+ end
156
+
157
+ # Test if given parameter has non-empty value.
158
+ def filled?
159
+ not empty?
160
+ end
161
+
162
+ # Get the proper name for use in form names, adding [] to array and [key] to hash parameters.
163
+ def form_name( key = nil )
164
+ if array?
165
+ "#{code}[]"
166
+ elsif hash?
167
+ fail( ArgumentError, "missing hash key" ) if key.nil?
168
+ "#{code}[#{key}]"
169
+ else
170
+ code.to_s
171
+ end
172
+ end
173
+
174
+ # Test if given value is the selected value, for use in form selects.
175
+ def selected?( value )
176
+ if empty?
177
+ false
178
+ elsif array?
179
+ self.value.include?( value )
180
+ elsif hash?
181
+ false
182
+ else
183
+ self.value == value
184
+ end
185
+ end
186
+
187
+ # Get the name of the parameter to be displayed to the user, or nil if there is none.
188
+ def title
189
+ self[ :title ]
190
+ end
191
+
192
+ # Get the title for use in form. Fallbacks to normal title and code if no form title was specified.
193
+ def form_title
194
+ self[ :form_title ] || title || code.to_s
195
+ end
196
+
197
+ # Get the title for use in error messages. Fallbacks to normal title and code if no form title was specified.
198
+ def error_title
199
+ self[ :error_title ] || title || code.to_s
200
+ end
201
+
202
+ # Get list of errors reported for this paramater. Always empty for unbound parameters.
203
+ def errors
204
+ form ? form.errors_for( name ) : []
205
+ end
206
+
207
+ # Get first error reported for this parameter. Always nil for unbound parameters.
208
+ def error
209
+ errors.first
210
+ end
211
+
212
+ # Test if this parameter had no errors reported.
213
+ def valid?
214
+ errors.empty?
215
+ end
216
+
217
+ # Test if this parameter had some errors reported.
218
+ def invalid?
219
+ not valid?
220
+ end
221
+
222
+ # Test if the parameter is required.
223
+ def required?
224
+ !! self[ :required ]
225
+ end
226
+
227
+ # Test if the parameter is optional.
228
+ def optional?
229
+ not required?
230
+ end
231
+
232
+ # Test if the parameter is disabled.
233
+ def disabled?
234
+ !! self[ :disabled ]
235
+ end
236
+
237
+ # Test if the parameter is enabled.
238
+ def enabled?
239
+ not disabled?
240
+ end
241
+
242
+ # Get type of the parameter. Defaults to :text.
243
+ def type
244
+ self[ :type ] || :text
245
+ end
246
+
247
+ # Test if the parameter is hidden.
248
+ def hidden?
249
+ type == :hidden
250
+ end
251
+
252
+ # Test if the parameter is to be ignored on output.
253
+ def ignored?
254
+ type == :ignore
255
+ end
256
+
257
+ # Test if the parameter is visible.
258
+ def visible?
259
+ not ( hidden? || ignored? )
260
+ end
261
+
262
+ # Test if this is an array parameter.
263
+ def array?
264
+ !! self[ :array ]
265
+ end
266
+
267
+ # Test if this is a hash parameter.
268
+ def hash?
269
+ !! self[ :hash ]
270
+ end
271
+
272
+ # Test if this is a scalar parameter.
273
+ def scalar?
274
+ not ( array? || hash? )
275
+ end
276
+
277
+ # Get list of tags of this parameter.
278
+ def tags
279
+ @tags ||= [ *self[ :tag ], *self[ :tags ] ]
280
+ end
281
+
282
+ # Test if the parameter is tagged with some of given tags, or any tag if the argument list is empty.
283
+ def tagged?( *tags )
284
+ t = self.tags
285
+ if tags.empty?
286
+ not t.empty?
287
+ else
288
+ tags.flatten.any?{ |x| t.include? x }
289
+ end
290
+ end
291
+
292
+ # Test if the parameter is not tagged with any of given tags, or any tag if the argument list is empty.
293
+ def untagged?( *tags )
294
+ not tagged?( *tags )
295
+ end
296
+
297
+ # Get input filter for this paramater, if any.
298
+ def filter
299
+ opts[ :filter ]
300
+ end
301
+
302
+ # Get input transform for this paramater, if any.
303
+ def transform
304
+ opts[ :transform ]
305
+ end
306
+
307
+ # Get output filter for this paramater, if any.
308
+ def format
309
+ opts[ :format ]
310
+ end
311
+
312
+ # Get data relevant for this parameter, if any. Returns empty array if there are none.
313
+ def data
314
+ self[ :data ] || []
315
+ end
316
+
317
+ # Methods affected by localization, put in separate module for easier overloading.
318
+ module LocaleMethods
319
+
320
+ # Get value of arbitrary option. Automatically resolves call blocks.
321
+ def []( name )
322
+ o = opts[ name ]
323
+ o = instance_exec( &o ) if o.is_a?( Proc )
324
+ o
325
+ end
326
+
327
+ # Format the error report message. Default implementation includes simple pluralizer.
328
+ # String %p in the message is automatically replaced with error title.
329
+ # Can be redefined to provide correctly localized error messages.
330
+ def format_error_message( msg, count = nil, singular = nil, plural = "#{singular}s" )
331
+ msg = DEFAULT_ERROR_MESSAGES[ msg ] || msg.to_s
332
+ msg += " #{count}" if count
333
+ msg += " #{ count == 1 ? singular : plural }" if singular
334
+ msg.gsub( '%p', error_title )
335
+ end
336
+
337
+ # Report an error concerning this parameter.
338
+ # String %p in the message is automatically replaced with error title.
339
+ # Returns self for chaining.
340
+ def report( msg, *args )
341
+ form.report( name, format_error_message( msg, *args ) ) if form
342
+ self
343
+ end
344
+
345
+ end
346
+
347
+ include LocaleMethods
348
+
349
+ # Validation.
350
+
351
+ # Validate this parameter. Does nothing if it was found invalid already.
352
+ def validate
353
+ return if invalid?
354
+
355
+ # First of all, make sure required parameters are present and not empty.
356
+
357
+ if required? && empty?
358
+ report( self[ :required_msg ] || ( scalar? ? :required_scalar : :required_array ) )
359
+ return
360
+ end
361
+
362
+ # Otherwise empty parameters are considered correct, as long as the type is correct.
363
+
364
+ return if empty? && correct?
365
+
366
+ # Make sure the parameter value contains only valid data.
367
+
368
+ return unless if array?
369
+ validate_array( value )
370
+ elsif hash?
371
+ validate_hash( value )
372
+ else
373
+ validate_value( value )
374
+ end
375
+
376
+ # Finally, invoke the custom check callback if there is any.
377
+
378
+ if check = opts[ :check ]
379
+ instance_exec( &check )
380
+ end
381
+ end
382
+
383
+ private
384
+
385
+ # Validate given array.
386
+ # Return true if the entire array validated correctly, nil or false otherwise.
387
+ def validate_array( value )
388
+
389
+ # Make sure it's an array in the first place.
390
+
391
+ unless value.is_a? Array
392
+ report( :not_array )
393
+ return
394
+ end
395
+
396
+ # Enforce array limits.
397
+
398
+ return unless validate_count( value )
399
+
400
+ # Now validate array elements. If we detect problems, don't bother with the rest.
401
+
402
+ value.all?{ |v| validate_value( v ) }
403
+ end
404
+
405
+ # Validate given hash.
406
+ # Return true if the entire hash validated correctly, nil or false otherwise.
407
+ def validate_hash( value )
408
+
409
+ # Make sure it's a hash in the first place.
410
+
411
+ unless value.is_a? Hash
412
+ report( :not_hash )
413
+ return
414
+ end
415
+
416
+ # Enforce hash limits.
417
+
418
+ return unless validate_count( value )
419
+
420
+ # Now validate hash keys and values. If we detect problems, don't bother with the rest.
421
+
422
+ value.all?{ |k, v| validate_key( k ) && validate_value( v ) }
423
+ end
424
+
425
+ # Validate given hash key.
426
+ # Return true if it validated correctly, nil or false otherwise.
427
+ def validate_key( value )
428
+
429
+ # If there is a key pattern specified, make sure the key matches.
430
+
431
+ if patterns = self[ :match_key ]
432
+ unless [ *patterns ].all?{ |x| value.to_s =~ x }
433
+ report( :match_key )
434
+ return
435
+ end
436
+ return true
437
+ end
438
+
439
+ # Otherwise make sure it's an integer.
440
+
441
+ unless value.is_a? Integer
442
+ report( :invalid_key )
443
+ return
444
+ end
445
+
446
+ # Make sure it is within allowed limits.
447
+
448
+ if limit = self[ :min_key ] and value < limit
449
+ report( :min_key )
450
+ return
451
+ end
452
+
453
+ if limit = self[ :max_key ] and value > limit
454
+ report( :max_key )
455
+ return
456
+ end
457
+
458
+ true
459
+ end
460
+
461
+ # Validate container limits.
462
+ # Return true if it validated correctly, nil or false otherwise.
463
+ def validate_count( value )
464
+
465
+ if limit = self[ :min_count ] and value.count < limit
466
+ report( :min_count, limit, 'element' )
467
+ return
468
+ end
469
+
470
+ if limit = self[ :max_count ] and value.count > limit
471
+ report( :max_count, limit, 'element' )
472
+ return
473
+ end
474
+
475
+ true
476
+ end
477
+
478
+ # Validate given scalar value.
479
+ # Return true if it validated correctly, nil or false otherwise.
480
+ def validate_value( value )
481
+
482
+ # First apply the type tests.
483
+
484
+ if type = self[ :class ] and type != String
485
+ unless [ *type ].any?{ |x| value.is_a?( x ) }
486
+ report( scalar? ? :value_type : :element_type )
487
+ return
488
+ end
489
+ else
490
+ return unless validate_string( value )
491
+ end
492
+
493
+ # Then enforce any value limits.
494
+
495
+ if limit = self[ :min ] and value.to_f < limit.to_f
496
+ report( :min_limit, limit )
497
+ return
498
+ end
499
+
500
+ if limit = self[ :max ] and value.to_f > limit.to_f
501
+ report( :max_limit, limit )
502
+ return
503
+ end
504
+
505
+ if limit = self[ :inf ] and value.to_f <= limit.to_f
506
+ report( :inf_limit, limit )
507
+ return
508
+ end
509
+
510
+ if limit = self[ :sup ] and value.to_f >= limit.to_f
511
+ report( :sup_limit, limit )
512
+ return
513
+ end
514
+
515
+ # Finally, invoke the custom callback if there is any.
516
+
517
+ if test = opts[ :test ]
518
+ instance_exec( value, &test )
519
+ return unless valid?
520
+ end
521
+
522
+ true
523
+ end
524
+
525
+ # Validate given string.
526
+ # Return true if it validated correctly, nil or false otherwise.
527
+ def validate_string( value )
528
+
529
+ # Make sure it's a string in the first place.
530
+
531
+ unless value.is_a? String
532
+ report( scalar? ? :not_string : :element_type )
533
+ return
534
+ end
535
+
536
+ # Make sure the string contains only valid data.
537
+
538
+ unless value.valid_encoding? && ( value.encoding == DEFAULT_ENCODING || value.ascii_only? )
539
+ report( :invalid_encoding )
540
+ return
541
+ end
542
+
543
+ unless value =~ /\A(\p{Graph}|[ \t\r\n])*\z/u
544
+ report( :invalid_characters )
545
+ return
546
+ end
547
+
548
+ # Enforce any size limits.
549
+
550
+ if limit = self[ :min_size ] and value.size < limit
551
+ report( :min_size, limit, 'character' )
552
+ return
553
+ end
554
+
555
+ if limit = self[ :min_bytesize ] and value.bytesize < limit
556
+ report( :min_bytesize, limit, 'byte' )
557
+ return
558
+ end
559
+
560
+ if limit = self[ :max_size ] and value.size > limit
561
+ report( :max_size, limit, 'character' )
562
+ return
563
+ end
564
+
565
+ if limit = self[ :max_bytesize ] and value.bytesize > limit
566
+ report( :max_bytesize, limit, 'byte' )
567
+ return
568
+ end
569
+
570
+ # Finally make sure the format is valid.
571
+
572
+ if patterns = self[ :reject ]
573
+ if [ *patterns ].any?{ |x| value =~ x }
574
+ report( self[ :reject_msg ] || self[ :msg ] || :reject_msg )
575
+ return
576
+ end
577
+ end
578
+
579
+ if patterns = self[ :match ]
580
+ unless [ *patterns ].all?{ |x| value =~ x }
581
+ report( self[ :match_msg ] || self[ :msg ] || :match_msg )
582
+ return
583
+ end
584
+ end
585
+
586
+ true
587
+ end
588
+
589
+ end
590
+
591
+ # Class methods.
592
+
593
+ class << self
594
+
595
+ # Create standalone copy of form parameters in case someone inherits an existing form.
596
+ def inherited( into )
597
+ into.instance_variable_set( '@params', form_params.dup )
598
+ end
599
+
600
+ # Get hash mapping parameter names to parameters themselves.
601
+ def form_params
602
+ @params ||= {}
603
+ end
604
+
605
+ # Get given parameter(s), hash style.
606
+ def []( *names )
607
+ if names.count == 1
608
+ form_params[ names.first ]
609
+ else
610
+ form_params.values_at( *names )
611
+ end
612
+ end
613
+
614
+ # Add given parameter to the form, after performing basic validity checks.
615
+ def add( param )
616
+ name = param.name
617
+
618
+ fail ArgumentError, "duplicate parameter #{name}" if form_params[ name ]
619
+ fail ArgumentError, "invalid parameter name #{name}" if method_defined?( name )
620
+
621
+ self.send( :attr_accessor, name )
622
+
623
+ form_params[ name ] = param
624
+ end
625
+ private :add
626
+
627
+ # Copy given/all form parameters.
628
+ # Returns self for chaining.
629
+ def copy( source, opts = {} )
630
+ case source
631
+ when Parameter
632
+ add( Parameter.new(
633
+ opts[ :name ] || source.name,
634
+ opts[ :code ] || opts[ :name ] || source.code,
635
+ source.opts.merge( opts )
636
+ ) )
637
+ when Array
638
+ source.each{ |x| copy( x, opts ) }
639
+ when Class
640
+ fail ArgumentError, "invalid source form #{source.inspect}" unless source < FormInput
641
+ copy( source.form_params.values, opts )
642
+ else
643
+ fail ArgumentError, "invalid source parameter #{source.inspect}"
644
+ end
645
+ self
646
+ end
647
+
648
+ # Define form parameter with given name, code, title, maximum size, options, and filter block.
649
+ # All fields except name are optional. In case the code is missing, name is used instead.
650
+ # If no size limits are specified, 255 characters and bytes limits are applied by default.
651
+ # If no filter is explicitly defined, default filter squeezing and stripping whitespace is applied.
652
+ # Returns self for chaining.
653
+ def param( name, *args, &block )
654
+
655
+ # Fetch arguments.
656
+
657
+ code = name
658
+ code = args.shift if args.first.is_a? Symbol
659
+
660
+ title = args.shift if args.first.is_a? String
661
+
662
+ size = args.shift if args.first.is_a? Numeric
663
+
664
+ opts = {}
665
+ opts.merge!( args.shift ) while args.first.is_a? Hash
666
+
667
+ fail ArgumentError, "invalid arguments #{args}" unless args.empty?
668
+
669
+ # Set the title.
670
+
671
+ opts[ :title ] = title.freeze if title
672
+
673
+ # Set input filter.
674
+
675
+ opts[ :filter ] = block if block
676
+ opts[ :filter ] = DEFAULT_FILTER unless opts.key?( :filter )
677
+
678
+ # Enforce default size limits for any input processed.
679
+
680
+ limit = DEFAULT_SIZE_LIMIT
681
+
682
+ size = ( opts[ :max_size ] ||= size || limit )
683
+ opts[ :max_bytesize ] ||= limit if size.is_a?( Proc ) or size <= limit
684
+
685
+ # Set default key limits for hash parameters.
686
+
687
+ if opts[ :hash ]
688
+ opts[ :min_key ] ||= DEFAULT_MIN_KEY
689
+ opts[ :max_key ] ||= DEFAULT_MAX_KEY
690
+ end
691
+
692
+ # Define parameter.
693
+
694
+ add( Parameter.new( name, code, opts ) )
695
+ self
696
+ end
697
+
698
+ # Like param, except this defines required parameter.
699
+ def param!( name, *args, &block )
700
+ param( name, *args, required: true, &block )
701
+ end
702
+
703
+ # Like param, except that it defines array parameter.
704
+ def array( name, *args, &block )
705
+ param( name, *args, array: true, &block )
706
+ end
707
+
708
+ # Like param!, except that it defines required array parameter.
709
+ def array!( name, *args, &block )
710
+ param!( name, *args, array: true, &block )
711
+ end
712
+
713
+ # Like param, except that it defines hash parameter.
714
+ def hash( name, *args, &block )
715
+ param( name, *args, hash: true, &block )
716
+ end
717
+
718
+ # Like param!, except that it defines required hash parameter.
719
+ def hash!( name, *args, &block )
720
+ param!( name, *args, hash: true, &block )
721
+ end
722
+
723
+ # Create new form from request with external values.
724
+ def from_request( request )
725
+ new.import( request )
726
+ end
727
+
728
+ # Create new form from hash with external values.
729
+ def from_params( params )
730
+ new.import( params )
731
+ end
732
+
733
+ # Create new form from hash with internal values.
734
+ def from_hash( hash )
735
+ new.set( hash )
736
+ end
737
+
738
+ end
739
+
740
+ # Instantiation and access.
741
+
742
+ # Get copy of parameter hash with parameters bound to this form.
743
+ def bound_params
744
+ hash = {}
745
+ self.class.form_params.each{ |name, param| hash[ name ] = param.dup.bind( self ) }
746
+ hash.freeze
747
+ end
748
+ private :bound_params
749
+
750
+ # Create new form info, initializing it from given hash or request, if anything.
751
+ def initialize( *args )
752
+ @params = bound_params
753
+ @errors = nil
754
+ for arg in args
755
+ if arg.is_a? Hash
756
+ set( arg )
757
+ else
758
+ import( arg )
759
+ end
760
+ end
761
+ end
762
+
763
+ # Initialize form clone.
764
+ def initialize_clone( other )
765
+ super
766
+ @params = bound_params
767
+ @errors &&= Hash[ @errors.map{ |k,v| [ k, v.clone ] } ]
768
+ end
769
+
770
+ # Initialize form copy.
771
+ def initialize_dup( other )
772
+ super
773
+ @params = bound_params
774
+ @errors = nil
775
+ end
776
+
777
+ # Freeze the form.
778
+ def freeze
779
+ unless frozen?
780
+ validate?
781
+ @errors.freeze.each{ |k,v| v.freeze }
782
+ end
783
+ super
784
+ end
785
+
786
+ # Import request parameter value.
787
+ def sanitize_value( value, filter = nil )
788
+ case value
789
+ when String
790
+ # Note that Rack does no encoding processing as of now,
791
+ # and even if we know content type charset, the query charset
792
+ # is not well defined and we can't fix the multi-part input here either.
793
+ # So we just hope that all clients will send the data in UTF-8 which we used in the form,
794
+ # and enforce everything to UTF-8. If it is not valid, we keep the binary string instead
795
+ # so the validation can detect it but the user can still process it himself if he wants to.
796
+ value = value.dup.force_encoding( DEFAULT_ENCODING )
797
+ if value.valid_encoding?
798
+ value = value.instance_exec( &filter ) if filter
799
+ else
800
+ value.force_encoding( Encoding::BINARY )
801
+ end
802
+ value
803
+ when Array
804
+ # Arrays are supported, but note that the validation done later only allows flat arrays.
805
+ value.map{ |x| sanitize_value( x, filter ) }
806
+ when Hash
807
+ # To reduce security issues, we prefer integer hash keys only.
808
+ # The validation done later ensures that the keys are valid, within range,
809
+ # and that only flat hashes are allowed.
810
+ Hash[ value.map{ |k, v| [ ( Integer( k, 10 ) rescue k ), sanitize_value( v, filter ) ] } ]
811
+ else
812
+ fail TypeError, "unexpected parameter type"
813
+ end
814
+ end
815
+ private :sanitize_value
816
+
817
+ # Import parameter values from given request or hash. Applies parameter input filters and transforms as well.
818
+ # Returns self for chaining.
819
+ def import( request )
820
+ for name, param in @params
821
+ if value = request[ param.code ]
822
+ value = sanitize_value( value, param.filter )
823
+ if transform = param.transform
824
+ value = value.instance_exec( &transform )
825
+ end
826
+ self[ name ] = value
827
+ end
828
+ end
829
+ self
830
+ end
831
+
832
+ # Set parameter values from given hash.
833
+ # Returns self for chaining.
834
+ def set( hash )
835
+ for name, value in hash
836
+ self[ name ] = value
837
+ end
838
+ self
839
+ end
840
+
841
+ # Clear all/given parameter values. Both names and parameters are accepted.
842
+ # Returns self for chaining.
843
+ def clear( *names )
844
+ names = names.empty? ? params_names : validate_names( names )
845
+ for name in names
846
+ self[ name ] = nil
847
+ end
848
+ self
849
+ end
850
+
851
+ # Get given parameter(s) value(s), hash style.
852
+ def []( *names )
853
+ if names.count == 1
854
+ send( names.first )
855
+ else
856
+ names.map{ |x| send( x ) }
857
+ end
858
+ end
859
+
860
+ # Set given parameter value, hash style.
861
+ # Unlike setting the attribute directly, this triggers a revalidation in the future.
862
+ def []=( name, value )
863
+ @errors = nil
864
+ send( "#{name}=", value )
865
+ end
866
+
867
+ # Return all non-empty parameters as a hash.
868
+ # See also url_params, which creates a hash suitable for url output.
869
+ def to_hash
870
+ result = {}
871
+ filled_params.each{ |x| result[ x.name ] = x.value }
872
+ result
873
+ end
874
+ alias to_h to_hash
875
+
876
+ # Convert parameters to names and fail if we encounter unknown one.
877
+ def validate_names( names )
878
+ names.flatten.map do |name|
879
+ name = name.name if name.is_a? Parameter
880
+ fail( ArgumentError, "unknown parameter #{name}" ) unless @params[ name ]
881
+ name
882
+ end
883
+ end
884
+ private :validate_names
885
+
886
+ # Create copy of itself, with given parameters unset. Both names and parameters are accepted.
887
+ def except( *names )
888
+ result = dup
889
+ for name in validate_names( names )
890
+ result[ name ] = nil
891
+ end
892
+ result
893
+ end
894
+
895
+ # Create copy of itself, with only given parameters set. Both names and parameters are accepted.
896
+ def only( *names )
897
+ # It would be easier to create new instance here and only copy selected values,
898
+ # but we want to use dup instead of new here, as the derived form can use
899
+ # different parameters in its construction.
900
+ result = dup
901
+ for name in params_names - validate_names( names )
902
+ result[ name ] = nil
903
+ end
904
+ result
905
+ end
906
+
907
+ # Parameter lists.
908
+
909
+ # Get given named parameter.
910
+ def param( name )
911
+ @params[ name ]
912
+ end
913
+ alias parameter param
914
+
915
+ # Get list of all parameters.
916
+ def params
917
+ @params.values
918
+ end
919
+ alias parameters params
920
+
921
+ # Get list of all parameter names.
922
+ def params_names
923
+ @params.keys
924
+ end
925
+ alias parameters_names params_names
926
+
927
+ # Get list of given named parameters.
928
+ # Note that nil is returned for unknown names, and duplicate parameters for duplicate names.
929
+ def named_params( *names )
930
+ @params.values_at( *names )
931
+ end
932
+ alias named_parameters named_params
933
+
934
+ # Get list of parameters with correct value types.
935
+ def correct_params
936
+ params.select{ |x| x.correct? }
937
+ end
938
+ alias correct_parameters correct_params
939
+
940
+ # Get list of parameters with incorrect value types.
941
+ def incorrect_params
942
+ params.select{ |x| x.incorrect? }
943
+ end
944
+ alias incorrect_parameters incorrect_params
945
+
946
+ # Get list of parameters with blank values.
947
+ def blank_params
948
+ params.select{ |x| x.blank? }
949
+ end
950
+ alias blank_parameters blank_params
951
+
952
+ # Get list of parameters with empty values.
953
+ def empty_params
954
+ params.select{ |x| x.empty? }
955
+ end
956
+ alias empty_parameters empty_params
957
+
958
+ # Get list of parameters with non-empty values.
959
+ def filled_params
960
+ params.select{ |x| x.filled? }
961
+ end
962
+ alias filled_parameters filled_params
963
+
964
+ # Get list of required parameters.
965
+ def required_params
966
+ params.select{ |x| x.required? }
967
+ end
968
+ alias required_parameters required_params
969
+
970
+ # Get list of optional parameters.
971
+ def optional_params
972
+ params.select{ |x| x.optional? }
973
+ end
974
+ alias optional_parameters optional_params
975
+
976
+ # Get list of disabled parameters.
977
+ def disabled_params
978
+ params.select{ |x| x.disabled? }
979
+ end
980
+ alias disabled_parameters disabled_params
981
+
982
+ # Get list of enabled parameters.
983
+ def enabled_params
984
+ params.select{ |x| x.enabled? }
985
+ end
986
+ alias enabled_parameters enabled_params
987
+
988
+ # Get list of hidden parameters.
989
+ def hidden_params
990
+ params.select{ |x| x.hidden? }
991
+ end
992
+ alias hidden_parameters hidden_params
993
+
994
+ # Get list of ignored parameters.
995
+ def ignored_params
996
+ params.select{ |x| x.ignored? }
997
+ end
998
+ alias ignored_parameters ignored_params
999
+
1000
+ # Get list of visible parameters.
1001
+ def visible_params
1002
+ params.select{ |x| x.visible? }
1003
+ end
1004
+ alias visible_parameters visible_params
1005
+
1006
+ # Get list of array parameters.
1007
+ def array_params
1008
+ params.select{ |x| x.array? }
1009
+ end
1010
+ alias array_parameters array_params
1011
+
1012
+ # Get list of hash parameters.
1013
+ def hash_params
1014
+ params.select{ |x| x.hash? }
1015
+ end
1016
+ alias hash_parameters hash_params
1017
+
1018
+ # Get list of scalar parameters.
1019
+ def scalar_params
1020
+ params.select{ |x| x.scalar? }
1021
+ end
1022
+ alias scalar_parameters scalar_params
1023
+
1024
+ # Get list of parameters tagged with given/any tags.
1025
+ def tagged_params( *tags )
1026
+ params.select{ |x| x.tagged?( *tags ) }
1027
+ end
1028
+ alias tagged_parameters tagged_params
1029
+
1030
+ # Get list of parameters not tagged with given/any tags.
1031
+ def untagged_params( *tags )
1032
+ params.select{ |x| x.untagged?( *tags ) }
1033
+ end
1034
+ alias untagged_parameters untagged_params
1035
+
1036
+ # Get list of parameters with no errors reported.
1037
+ def valid_params
1038
+ params.select{ |x| x.valid? }
1039
+ end
1040
+ alias valid_parameters valid_params
1041
+
1042
+ # Get list of parameters with some errors reported.
1043
+ def invalid_params
1044
+ params.select{ |x| x.invalid? }
1045
+ end
1046
+ alias invalid_parameters invalid_params
1047
+
1048
+ # Get all/given parameters chunked into individual rows for nicer form display.
1049
+ def chunked_params( params = self.params )
1050
+ params.chunk{ |p| p[ :row ] || :_alone }.map{ |x,a| a.count > 1 ? a : a.first }
1051
+ end
1052
+
1053
+ # URL helpers.
1054
+
1055
+ # Return true if all parameters are empty.
1056
+ def empty?
1057
+ filled_params.empty?
1058
+ end
1059
+
1060
+ # Get hash of all non-empty parameters for use in URL.
1061
+ def url_params
1062
+ result = {}
1063
+ filled_params.each{ |x| result[ x.code ] = x.form_value }
1064
+ result
1065
+ end
1066
+ alias url_parameters url_params
1067
+
1068
+ # Create string containing URL query from all current non-empty parameters.
1069
+ def url_query
1070
+ Rack::Utils.build_nested_query( url_params )
1071
+ end
1072
+
1073
+ # Extend given URL with query created from all current non-empty parameters.
1074
+ def extend_url( url )
1075
+ url = url.to_s.dup
1076
+ query = url_query
1077
+ unless query.empty?
1078
+ url << ( url['?'] ? '&' : '?' ) << query
1079
+ end
1080
+ url
1081
+ end
1082
+
1083
+ # Build URL from given URL and combination of current paramaters and provided parameters.
1084
+ def build_url( url, args = {} )
1085
+ dup.set( args ).extend_url( url )
1086
+ end
1087
+
1088
+ # Validation.
1089
+
1090
+ # Get hash of all errors detected for each parameter.
1091
+ def errors
1092
+ validate?
1093
+ @errors.dup
1094
+ end
1095
+
1096
+ # Get list of error messages, but including only the first one reported for each parameter.
1097
+ def error_messages
1098
+ errors.values.map{ |x| x.first }
1099
+ end
1100
+
1101
+ # Remember error concerning given parameter.
1102
+ # Returns self for chaining.
1103
+ def report( name, msg )
1104
+ validate?
1105
+ ( @errors[ name ] ||= [] ) << msg.to_s.dup.freeze
1106
+ self
1107
+ end
1108
+
1109
+ # Get list of errors for given parameter. Returns empty list if there were no errors.
1110
+ def errors_for( name )
1111
+ errors[ name ] || []
1112
+ end
1113
+
1114
+ # Get first error for given parameter. Returns nil if there were no errors.
1115
+ def error_for( name )
1116
+ errors_for( name ).first
1117
+ end
1118
+
1119
+ # Test if there were no errors (overall or for given parameters) reported.
1120
+ def valid?( *names )
1121
+ if names.empty?
1122
+ errors.empty?
1123
+ else
1124
+ validate_names( names ).all?{ |x| errors_for( x ).empty? }
1125
+ end
1126
+ end
1127
+
1128
+ # Test if there were some errors (overall or for given parameters) reported.
1129
+ def invalid?( *names )
1130
+ not valid?( *names )
1131
+ end
1132
+
1133
+ # Return parameter(s) value(s) as long as they are all valid, nil otherwise.
1134
+ def valid( name, *names )
1135
+ self[ name, *names ] if valid?( name, *names )
1136
+ end
1137
+
1138
+ # Validate parameter values and remember any errors detected.
1139
+ # You can override this in your class if you need more specific validation
1140
+ # and :check callback is not good enough. Just make sure to call super first.
1141
+ # Returns self for chaining.
1142
+ def validate
1143
+ @errors ||= {}
1144
+ params.each{ |x| x.validate }
1145
+ self
1146
+ end
1147
+
1148
+ # Like validate, except that it forces revalidation of all parameters.
1149
+ # Returns self for chaining.
1150
+ def validate!
1151
+ @errors = {}
1152
+ validate
1153
+ self
1154
+ end
1155
+
1156
+ # Like validate, except that it does nothing if validation was already done.
1157
+ # Returns self for chaining.
1158
+ def validate?
1159
+ validate unless @errors
1160
+ self
1161
+ end
1162
+
1163
+ end
1164
+
1165
+ # EOF #