form_input 0.9.0.pre1

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.
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 #