class Roda::RodaPlugins::TypecastParams::Params

  1. lib/roda/plugins/typecast_params.rb
Superclass: Object

Class handling conversion of submitted parameters to desired types.

Public Class methods

handle_type(type, opts=OPTS, &block)

Handle conversions for the given type using the given block. For a type named foo, this will create the following methods:

  • foo(key, default=nil)

  • foo!(key)

  • convert_foo(value) # private

  • _convert_array_foo(value) # private

This method is used to define all type conversions, even the built in ones. It can be called in subclasses to setup subclass-specific types.

[show source]
    # File lib/roda/plugins/typecast_params.rb
448 def self.handle_type(type, opts=OPTS, &block)
449   convert_meth = :"convert_#{type}"
450   define_method(convert_meth, &block)
451 
452   max_input_bytesize = opts[:max_input_bytesize]
453   max_input_bytesize_meth = :"_max_input_bytesize_for_#{type}"
454   define_method(max_input_bytesize_meth){max_input_bytesize}
455 
456   convert_array_meth = :"_convert_array_#{type}"
457   define_method(convert_array_meth) do |v|
458     raise Error, "expected array but received #{v.inspect}" unless v.is_a?(Array)
459     v.map! do |val|
460       check_allowed_bytesize(val, send(max_input_bytesize_meth))
461       check_null_byte(val)
462       send(convert_meth, val)
463     end
464   end
465 
466   private convert_meth, convert_array_meth, max_input_bytesize_meth
467   alias_method max_input_bytesize_meth, max_input_bytesize_meth
468 
469   define_method(type) do |key, default=nil|
470     process_arg(convert_meth, key, default, send(max_input_bytesize_meth)) if require_hash!
471   end
472 
473   define_method(:"#{type}!") do |key|
474     send(type, key, CHECK_NIL)
475   end
476 end
max_input_bytesize(type, bytesize)

Override the maximum input bytesize for the given type. This is mostly useful for overriding the sizes for the default input types.

[show source]
    # File lib/roda/plugins/typecast_params.rb
480 def self.max_input_bytesize(type, bytesize)
481   max_input_bytesize_meth = :"_max_input_bytesize_for_#{type}"
482   define_method(max_input_bytesize_meth){bytesize}
483   private max_input_bytesize_meth
484   alias_method max_input_bytesize_meth, max_input_bytesize_meth
485 end
nest(obj, nesting)

Create a new instance with the given object and nesting level. obj should be an array or hash, and nesting should be an array. Designed for internal use, should not be called by external code.

[show source]
    # File lib/roda/plugins/typecast_params.rb
491 def self.nest(obj, nesting)
492   v = allocate
493   v.instance_variable_set(:@nesting, nesting)
494   v.send(:initialize, obj)
495   v
496 end
new(obj)

Set the object used for converting. Conversion methods will convert members of the passed object.

[show source]
    # File lib/roda/plugins/typecast_params.rb
585 def initialize(obj)
586   case @obj = obj
587   when Hash, Array
588     # nothing
589   else
590     if @nesting
591       handle_error(nil, (@obj.nil? ? :missing : :invalid_type), "value of #{param_name(nil)} parameter not an array or hash: #{obj.inspect}", true)
592     else
593       handle_error(nil, :invalid_type, "parameters given not an array or hash: #{obj.inspect}", true)
594     end
595   end
596 end

Public Instance methods

[](key)

Return a new Params instance for the given key. The value of key should be an array if key is an integer, or hash otherwise.

[show source]
    # File lib/roda/plugins/typecast_params.rb
615 def [](key)
616   @subs ||= {}
617   if sub = @subs[key]
618     return sub
619   end
620 
621   if @obj.is_a?(Array)
622     unless key.is_a?(Integer)
623       handle_error(key, :invalid_type, "invalid use of non-integer key for accessing array: #{key.inspect}", true)
624     end
625   else
626     if key.is_a?(Integer)
627       handle_error(key, :invalid_type, "invalid use of integer key for accessing hash: #{key}", true)
628     end
629   end
630 
631   v = @obj[key]
632   v = yield if v.nil? && defined?(yield)
633 
634   begin
635     sub = self.class.nest(v, Array(@nesting) + [key])
636   rescue => e
637     handle_error(key, :invalid_type, e, true)
638   end
639 
640   @subs[key] = sub
641   sub.sub_capture(@capture, @symbolize, @skip_missing)
642   sub
643 end
array(type, key, default=nil)

Convert the value of key to an array of values of the given type. If default is given, any nil values in the array are replaced with default. If key is an array then this returns an array of arrays, one for each respective value of key. If there is no value for key, nil is returned instead of an array.

[show source]
    # File lib/roda/plugins/typecast_params.rb
752 def array(type, key, default=nil)
753   meth = :"_convert_array_#{type}"
754   raise ProgrammerError, "no typecast_params type registered for #{type.inspect}" unless respond_to?(meth, true)
755   process_arg(meth, key, default, send(:"_max_input_bytesize_for_#{type}")) if require_hash!
756 end
array!(type, key, default=nil)

Call array with the type, key, and default, but if the return value is nil or any value in the returned array is nil, raise an Error.

[show source]
    # File lib/roda/plugins/typecast_params.rb
760 def array!(type, key, default=nil)
761   v = array(type, key, default)
762 
763   if key.is_a?(Array)
764     key.zip(v).each do |k, arr|
765       check_array!(k, arr)
766     end
767   else
768     check_array!(key, v)
769   end
770 
771   v
772 end
convert!(keys=nil, opts=OPTS)

Captures conversions inside the given block, and returns a hash of all conversions, including conversions of subkeys. keys should be an array of subkeys to access, or nil to convert the current object. If keys is given as a hash, it is used as the options hash. Options:

:raise

If set to false, do not raise errors for missing keys

:skip_missing

If set to true, does not store values if the key is not present in the params.

:symbolize

Convert any string keys in the resulting hash and for any conversions below

[show source]
    # File lib/roda/plugins/typecast_params.rb
661 def convert!(keys=nil, opts=OPTS)
662   if keys.is_a?(Hash)
663     opts = keys
664     keys = nil
665   end
666 
667   _capture!(:nested_params, opts) do
668     if sub = subkey(Array(keys).dup, opts.fetch(:raise, true))
669       yield sub
670     end
671   end
672 end
convert_each!(opts=OPTS, &block)

Runs conversions similar to convert! for each key specified by the :keys option. If :keys option is not given and the object is an array, runs conversions for all entries in the array. If the :keys option is not given and the object is a Hash with string keys ‘0’, ‘1’, …, ‘N’ (with no skipped keys), runs conversions for all entries in the hash. If :keys option is a Proc or a Method, calls the proc/method with the current object, which should return an array of keys to use. Supports options given to convert!, and this additional option:

:keys

The keys to extract from the object. If a proc or method, calls the value with the current object, which should return the array of keys to use.

[show source]
    # File lib/roda/plugins/typecast_params.rb
685 def convert_each!(opts=OPTS, &block)
686   np = !@capture
687 
688   _capture!(nil, opts) do
689     case keys = opts[:keys]
690     when nil
691       keys = (0...@obj.length)
692 
693       valid = if @obj.is_a?(Array)
694         true
695       else
696         keys = keys.map(&:to_s)
697         keys.all?{|k| @obj.has_key?(k)}
698       end
699 
700       unless valid
701         handle_error(nil, :invalid_type, "convert_each! called on object not an array or hash with keys '0'..'N'")
702         next 
703       end
704     when Array
705       # nothing to do
706     when Proc, Method
707       keys = keys.call(@obj)
708     else
709       raise ProgrammerError, "unsupported convert_each! :keys option: #{keys.inspect}"
710     end
711 
712     keys.map do |i|
713       begin
714         if v = subkey([i], opts.fetch(:raise, true))
715           yield v
716           v.nested_params if np 
717         end
718       rescue => e
719         handle_error(i, :invalid_type, e)
720       end
721     end
722   end
723 end
dig(type, *nest, key)

Convert values nested under the current obj. Traverses the current object using nest, then converts key on that object using type:

tp.dig(:pos_int, 'foo')               # tp.pos_int('foo')
tp.dig(:pos_int, 'foo', 'bar')        # tp['foo'].pos_int('bar')
tp.dig(:pos_int, 'foo', 'bar', 'baz') # tp['foo']['bar'].pos_int('baz')

Returns nil if any of the values are not present or not the expected type. If the nest path results in an object that is not an array or hash, then raises an Error.

You can use dig to get access to nested arrays by using :array or :array! as the first argument and providing the type in the second argument:

tp.dig(:array, :pos_int, 'foo', 'bar', 'baz')  # tp['foo']['bar'].array(:int, 'baz')
[show source]
    # File lib/roda/plugins/typecast_params.rb
739 def dig(type, *nest, key)
740   _dig(false, type, nest, key)
741 end
dig!(type, *nest, key)

Similar to dig, but raises an Error instead of returning nil if no value is found.

[show source]
    # File lib/roda/plugins/typecast_params.rb
744 def dig!(type, *nest, key)
745   _dig(true, type, nest, key)
746 end
fetch(key)

Return the nested value for key. If there is no nested_value for key, calls the block to return the value, or returns nil if there is no block given.

[show source]
    # File lib/roda/plugins/typecast_params.rb
647 def fetch(key)
648   send(:[], key){return(yield if defined?(yield))}
649 end
present?(key)

If key is a String Return whether the key is present in the object,

[show source]
    # File lib/roda/plugins/typecast_params.rb
599 def present?(key)
600   case key
601   when String
602     !any(key).nil?
603   when Array
604     key.all? do |k|
605       raise ProgrammerError, "non-String element in array argument passed to present?: #{k.inspect}" unless k.is_a?(String)
606       !any(k).nil?
607     end
608   else
609     raise ProgrammerError, "unexpected argument passed to present?: #{key.inspect}"
610   end
611 end

Protected Instance methods

nested_params()

Recursively descendent into all known subkeys and get the converted params from each.

[show source]
    # File lib/roda/plugins/typecast_params.rb
777 def nested_params
778   return @nested_params if @nested_params
779 
780   params = @params
781 
782   if @subs
783     @subs.each do |key, v|
784       if key.is_a?(String) && symbolize?
785         key = key.to_sym
786       end
787       params[key] = v.nested_params
788     end
789   end
790   
791   params
792 end
sub_capture(capture, symbolize, skip_missing)

Inherit given capturing and symbolize setting from parent object.

[show source]
    # File lib/roda/plugins/typecast_params.rb
831 def sub_capture(capture, symbolize, skip_missing)
832   if @capture = capture
833     @symbolize = symbolize
834     @skip_missing = skip_missing
835     @params = @obj.class.new
836   end
837 end
subkey(keys, do_raise)

Recursive method to get subkeys.

[show source]
    # File lib/roda/plugins/typecast_params.rb
795 def subkey(keys, do_raise)
796   unless key = keys.shift
797     return self
798   end
799 
800   reason = :invalid_type
801 
802   case key
803   when String
804     unless @obj.is_a?(Hash)
805       raise Error, "parameter #{param_name(nil)} is not a hash" if do_raise
806       return
807     end
808     present = !@obj[key].nil?
809   when Integer
810     unless @obj.is_a?(Array)
811       raise Error, "parameter #{param_name(nil)} is not an array" if do_raise
812       return
813     end
814     present = key < @obj.length
815   else
816     raise ProgrammerError, "invalid argument used to traverse parameters: #{key.inspect}"
817   end
818 
819   unless present
820     reason = :missing
821     raise Error, "parameter #{param_name(key)} is not present" if do_raise
822     return
823   end
824 
825   self[key].subkey(keys, do_raise)
826 rescue => e
827   handle_error(key, reason, e)
828 end