miscellany 0.1.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 +7 -0
 - data/README.md +3 -0
 - data/config/initializers/01_custom_preloaders.rb +2 -0
 - data/config/initializers/arbitrary_prefetch.rb +1 -0
 - data/config/initializers/cancancan.rb +34 -0
 - data/lib/miscellany.rb +6 -0
 - data/lib/miscellany/active_record/arbitrary_prefetch.rb +163 -0
 - data/lib/miscellany/active_record/batch_matcher.rb +199 -0
 - data/lib/miscellany/active_record/batched_destruction.rb +96 -0
 - data/lib/miscellany/active_record/custom_preloaders.rb +59 -0
 - data/lib/miscellany/batch_processor.rb +41 -0
 - data/lib/miscellany/batching_csv_processor.rb +86 -0
 - data/lib/miscellany/controller/http_error_handling.rb +62 -0
 - data/lib/miscellany/controller/json_uploads.rb +41 -0
 - data/lib/miscellany/controller/sliced_response.rb +271 -0
 - data/lib/miscellany/param_validator.rb +442 -0
 - data/lib/miscellany/version.rb +3 -0
 - data/miscellany.gemspec +43 -0
 - metadata +297 -0
 
| 
         @@ -0,0 +1,442 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Miscellany
         
     | 
| 
      
 2 
     | 
    
         
            +
              class ParamValidator
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_accessor :context, :options, :errors
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                delegate_missing_to :context
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                TIME_TYPES = [Date, DateTime, Time].freeze
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                CHECKS = %i[type specified present default transform in block items pattern].freeze
         
     | 
| 
      
 10 
     | 
    
         
            +
                NON_PREFIXED = %i[default transform type message timezone].freeze
         
     | 
| 
      
 11 
     | 
    
         
            +
                PREFIXES = %i[all onem onep one none].freeze
         
     | 
| 
      
 12 
     | 
    
         
            +
                PREFIX_ALIASES = { any: :onep, not: :none }.freeze
         
     | 
| 
      
 13 
     | 
    
         
            +
                ALL_PREFIXES = (PREFIXES + PREFIX_ALIASES.keys).freeze
         
     | 
| 
      
 14 
     | 
    
         
            +
                VALID_FLAGS = %i[present specified].freeze
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def self.record_type(model, key: :id)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  Proc.new do |param, *args|
         
     | 
| 
      
 18 
     | 
    
         
            +
                    model.find_by!(key => param)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  rescue ActiveRecord::RecordNotFound
         
     | 
| 
      
 20 
     | 
    
         
            +
                    raise ArgumentError
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def initialize(block, context, parameters = nil, options = nil)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @block = block
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @context = context
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @params = parameters || context.params
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @subkeys = []
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @options = options || {}
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @errors = {}
         
     | 
| 
      
 31 
     | 
    
         
            +
                  @explicit_parameters = []
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                def self.check(params, context: nil, &blk)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  pv = new(blk, context, params)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  pv.apply_checks
         
     | 
| 
      
 37 
     | 
    
         
            +
                  pv.errors
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                def self.assert(params, context: nil, handle:, &blk)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  errors = check(params, context: context, &blk)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  if errors.present?
         
     | 
| 
      
 43 
     | 
    
         
            +
                    handle.call(errors)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  else
         
     | 
| 
      
 45 
     | 
    
         
            +
                    params
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                def apply_checks(&blk)
         
     | 
| 
      
 50 
     | 
    
         
            +
                  blk ||= @block
         
     | 
| 
      
 51 
     | 
    
         
            +
                  args = trim_arguments(blk, [params, @subkeys[-1]])
         
     | 
| 
      
 52 
     | 
    
         
            +
                  instance_exec(*args, &blk)
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                def parameter(param_keys, *args, **kwargs, &blk)
         
     | 
| 
      
 56 
     | 
    
         
            +
                  param_keys = Array(param_keys)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  opts = normalize_opts(*args, **kwargs, &blk)
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  checks = {}
         
     | 
| 
      
 60 
     | 
    
         
            +
                  PREFIXES.each do |pfx|
         
     | 
| 
      
 61 
     | 
    
         
            +
                    pfx_keys = opts[pfx]&.keys&.select { |k| opts[pfx][k] } || []
         
     | 
| 
      
 62 
     | 
    
         
            +
                    pfx_keys.each do |k|
         
     | 
| 
      
 63 
     | 
    
         
            +
                      checks[k] = pfx # TODO: Support filters connected to multiple prefixes
         
     | 
| 
      
 64 
     | 
    
         
            +
                    end
         
     | 
| 
      
 65 
     | 
    
         
            +
                    # TODO: warn if pfx != :all && param_keys.length == 1
         
     | 
| 
      
 66 
     | 
    
         
            +
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
                  NON_PREFIXED.each do |k|
         
     | 
| 
      
 68 
     | 
    
         
            +
                    checks[k] = nil
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  all_results = {}
         
     | 
| 
      
 72 
     | 
    
         
            +
                  param_keys.each do |pk|
         
     | 
| 
      
 73 
     | 
    
         
            +
                    check_results = all_results[pk] = {}
         
     | 
| 
      
 74 
     | 
    
         
            +
                    run_check = ->(check, &blk) { exec_check(check_results, check, checks, options: opts, &blk) }
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                    exec_check(check_results, :type) { coerce_type(params, pk, opts) } || next
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                    run_check[:specified] { params.key?(pk) || 'must be specified' } || next
         
     | 
| 
      
 79 
     | 
    
         
            +
                    run_check[:present] { params[pk].present? || 'must be present' } || next
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                    # Set Default
         
     | 
| 
      
 82 
     | 
    
         
            +
                    if params[pk].nil? && !opts[:default].nil?
         
     | 
| 
      
 83 
     | 
    
         
            +
                      params[pk] ||= opts[:default].respond_to?(:call) ? opts[:default].call : opts[:default]
         
     | 
| 
      
 84 
     | 
    
         
            +
                      next # We can assume that the default value is allowed
         
     | 
| 
      
 85 
     | 
    
         
            +
                    end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                    # Apply Transform
         
     | 
| 
      
 88 
     | 
    
         
            +
                    params[pk] = opts[:transform].to_proc.call(params[pk]) if params.include?(pk) && opts[:transform]
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                    next if params[pk].nil?
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                    run_check[:pattern] do |pattern|
         
     | 
| 
      
 93 
     | 
    
         
            +
                      return true if params[pk].to_s.match?(pattern)
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                      "must match pattern: #{pattern.inspect}"
         
     | 
| 
      
 96 
     | 
    
         
            +
                    end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                    run_check[:in] do |one_of|
         
     | 
| 
      
 99 
     | 
    
         
            +
                      next true if one_of.include?(params[pk])
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                      if one_of.is_a?(Range)
         
     | 
| 
      
 102 
     | 
    
         
            +
                        "must be between: #{one_of.begin}..#{one_of.end}"
         
     | 
| 
      
 103 
     | 
    
         
            +
                      else
         
     | 
| 
      
 104 
     | 
    
         
            +
                        "must be one of: #{one_of.to_a.join(', ')}"
         
     | 
| 
      
 105 
     | 
    
         
            +
                      end
         
     | 
| 
      
 106 
     | 
    
         
            +
                    end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                    # Nested check
         
     | 
| 
      
 109 
     | 
    
         
            +
                    run_check[:block] do |blk|
         
     | 
| 
      
 110 
     | 
    
         
            +
                      iterate_array = false # TODO
         
     | 
| 
      
 111 
     | 
    
         
            +
                      sub_parameter(pk) do
         
     | 
| 
      
 112 
     | 
    
         
            +
                        if params.is_a?(Array) && iterate_array
         
     | 
| 
      
 113 
     | 
    
         
            +
                          params.each_with_index do |v, i|
         
     | 
| 
      
 114 
     | 
    
         
            +
                            sub_parameter(i) { apply_checks(&blk) }
         
     | 
| 
      
 115 
     | 
    
         
            +
                          end
         
     | 
| 
      
 116 
     | 
    
         
            +
                        else
         
     | 
| 
      
 117 
     | 
    
         
            +
                          apply_checks(&blk)
         
     | 
| 
      
 118 
     | 
    
         
            +
                        end
         
     | 
| 
      
 119 
     | 
    
         
            +
                      end
         
     | 
| 
      
 120 
     | 
    
         
            +
                    end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                    # Nested check
         
     | 
| 
      
 123 
     | 
    
         
            +
                    run_check[:items] do |blk|
         
     | 
| 
      
 124 
     | 
    
         
            +
                      sub_parameter(pk) do
         
     | 
| 
      
 125 
     | 
    
         
            +
                        if params.is_a?(Array)
         
     | 
| 
      
 126 
     | 
    
         
            +
                          params.each_with_index do |v, i|
         
     | 
| 
      
 127 
     | 
    
         
            +
                            sub_parameter(i) { apply_checks(&blk) }
         
     | 
| 
      
 128 
     | 
    
         
            +
                          end
         
     | 
| 
      
 129 
     | 
    
         
            +
                        else
         
     | 
| 
      
 130 
     | 
    
         
            +
                          raise "items: validator can only be used with Arrays"
         
     | 
| 
      
 131 
     | 
    
         
            +
                        end
         
     | 
| 
      
 132 
     | 
    
         
            +
                      end
         
     | 
| 
      
 133 
     | 
    
         
            +
                    end
         
     | 
| 
      
 134 
     | 
    
         
            +
                  end
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
                  final_errors = {}
         
     | 
| 
      
 137 
     | 
    
         
            +
                  checks.each do |check, check_prefix|
         
     | 
| 
      
 138 
     | 
    
         
            +
                    if check_prefix == :all || check_prefix == nil
         
     | 
| 
      
 139 
     | 
    
         
            +
                      all_results.each do |field, err_map|
         
     | 
| 
      
 140 
     | 
    
         
            +
                        errs = err_map[check]
         
     | 
| 
      
 141 
     | 
    
         
            +
                        next unless errs.present?
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                        final_errors[field] = merge_error_hashes(final_errors[field], errs)
         
     | 
| 
      
 144 
     | 
    
         
            +
                      end
         
     | 
| 
      
 145 
     | 
    
         
            +
                    elsif check_prefix == :none
         
     | 
| 
      
 146 
     | 
    
         
            +
                      all_results.each do |field, err_map|
         
     | 
| 
      
 147 
     | 
    
         
            +
                        errs = err_map[check]
         
     | 
| 
      
 148 
     | 
    
         
            +
                        final_errors[field] = merge_error_hashes(final_errors[field], "must NOT be #{check}") unless errs.present?
         
     | 
| 
      
 149 
     | 
    
         
            +
                      end
         
     | 
| 
      
 150 
     | 
    
         
            +
                    else
         
     | 
| 
      
 151 
     | 
    
         
            +
                      counts = check_pass_count(check, all_results)
         
     | 
| 
      
 152 
     | 
    
         
            +
                      field_key = param_keys.join(', ')
         
     | 
| 
      
 153 
     | 
    
         
            +
                      string_prefixes = {
         
     | 
| 
      
 154 
     | 
    
         
            +
                        onep: 'One or more of',
         
     | 
| 
      
 155 
     | 
    
         
            +
                        onem: 'At most one of',
         
     | 
| 
      
 156 
     | 
    
         
            +
                        one: 'Exactly one of',
         
     | 
| 
      
 157 
     | 
    
         
            +
                      }
         
     | 
| 
      
 158 
     | 
    
         
            +
             
     | 
| 
      
 159 
     | 
    
         
            +
                      if (counts[:passed] != 1 && check_prefix == :one) ||
         
     | 
| 
      
 160 
     | 
    
         
            +
                        (counts[:passed] > 1 && check_prefix == :onem) ||
         
     | 
| 
      
 161 
     | 
    
         
            +
                        (counts[:passed] < 1 && check_prefix == :onep)
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
                        final_errors = merge_error_hashes(final_errors, "#{string_prefixes[check_prefix]} #{field_key} #{check}")
         
     | 
| 
      
 164 
     | 
    
         
            +
                      end
         
     | 
| 
      
 165 
     | 
    
         
            +
                    end
         
     | 
| 
      
 166 
     | 
    
         
            +
                  end
         
     | 
| 
      
 167 
     | 
    
         
            +
             
     | 
| 
      
 168 
     | 
    
         
            +
                  @errors = merge_error_hashes(@errors, final_errors)
         
     | 
| 
      
 169 
     | 
    
         
            +
                  final_errors
         
     | 
| 
      
 170 
     | 
    
         
            +
                end
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
                alias p parameter
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
                protected
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
                def check_pass_count(check, all_results)
         
     | 
| 
      
 177 
     | 
    
         
            +
                  counts = { passed: 0, failed: 0, skipped: 0 }
         
     | 
| 
      
 178 
     | 
    
         
            +
                  all_results.each do |_field, field_results|
         
     | 
| 
      
 179 
     | 
    
         
            +
                    result = field_results[check]
         
     | 
| 
      
 180 
     | 
    
         
            +
                    key = if result.nil?
         
     | 
| 
      
 181 
     | 
    
         
            +
                            :skipped
         
     | 
| 
      
 182 
     | 
    
         
            +
                          elsif result.present?
         
     | 
| 
      
 183 
     | 
    
         
            +
                            :failed
         
     | 
| 
      
 184 
     | 
    
         
            +
                          else
         
     | 
| 
      
 185 
     | 
    
         
            +
                            :passed
         
     | 
| 
      
 186 
     | 
    
         
            +
                          end
         
     | 
| 
      
 187 
     | 
    
         
            +
                    counts[key] += 1
         
     | 
| 
      
 188 
     | 
    
         
            +
                  end
         
     | 
| 
      
 189 
     | 
    
         
            +
                  counts
         
     | 
| 
      
 190 
     | 
    
         
            +
                end
         
     | 
| 
      
 191 
     | 
    
         
            +
             
     | 
| 
      
 192 
     | 
    
         
            +
                def exec_check(state, check, checks_to_run = nil, options: nil, &blk)
         
     | 
| 
      
 193 
     | 
    
         
            +
                  return true if checks_to_run && !checks_to_run[check] && !NON_PREFIXED.include?(check)
         
     | 
| 
      
 194 
     | 
    
         
            +
             
     | 
| 
      
 195 
     | 
    
         
            +
                  # TODO: Support Running checks of the same type for different prefixes
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
                  check_prefixes = NON_PREFIXED.include?(check) ? [nil] : Array(checks_to_run&.[](check))
         
     | 
| 
      
 198 
     | 
    
         
            +
                  return true unless check_prefixes.present?
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
                  check_prefixes.each do |check_prefix|
         
     | 
| 
      
 201 
     | 
    
         
            +
                    initial_errors = @errors
         
     | 
| 
      
 202 
     | 
    
         
            +
                    @errors = []
         
     | 
| 
      
 203 
     | 
    
         
            +
                    prefix_options = (check_prefix.nil? ? options : options&.[](check_prefix)) || {}
         
     | 
| 
      
 204 
     | 
    
         
            +
                    args = trim_arguments(blk, [prefix_options[check]])
         
     | 
| 
      
 205 
     | 
    
         
            +
             
     | 
| 
      
 206 
     | 
    
         
            +
                    result = yield(*args)
         
     | 
| 
      
 207 
     | 
    
         
            +
                    result = "failed validation #{check}" if result == false
         
     | 
| 
      
 208 
     | 
    
         
            +
             
     | 
| 
      
 209 
     | 
    
         
            +
                    if result.present? && result != true
         
     | 
| 
      
 210 
     | 
    
         
            +
                      result = options[:message] if options&.[](:message).present?
         
     | 
| 
      
 211 
     | 
    
         
            +
                      Array(result).each do |e|
         
     | 
| 
      
 212 
     | 
    
         
            +
                        @errors << e
         
     | 
| 
      
 213 
     | 
    
         
            +
                      end
         
     | 
| 
      
 214 
     | 
    
         
            +
                    end
         
     | 
| 
      
 215 
     | 
    
         
            +
             
     | 
| 
      
 216 
     | 
    
         
            +
                    state[check] = merge_error_hashes(state[check], @errors)
         
     | 
| 
      
 217 
     | 
    
         
            +
                    @errors = initial_errors
         
     | 
| 
      
 218 
     | 
    
         
            +
                  end
         
     | 
| 
      
 219 
     | 
    
         
            +
             
     | 
| 
      
 220 
     | 
    
         
            +
                  !state[check].present?
         
     | 
| 
      
 221 
     | 
    
         
            +
                end
         
     | 
| 
      
 222 
     | 
    
         
            +
             
     | 
| 
      
 223 
     | 
    
         
            +
                def coerce_type(params, key, opts)
         
     | 
| 
      
 224 
     | 
    
         
            +
                  value = params[key]
         
     | 
| 
      
 225 
     | 
    
         
            +
                  return nil if value.nil? || !opts[:type].present?
         
     | 
| 
      
 226 
     | 
    
         
            +
             
     | 
| 
      
 227 
     | 
    
         
            +
                  types = Array(opts[:type])
         
     | 
| 
      
 228 
     | 
    
         
            +
                  types.each do |t|
         
     | 
| 
      
 229 
     | 
    
         
            +
                    params[key] = coerce_single_type(value, t, opts)
         
     | 
| 
      
 230 
     | 
    
         
            +
                    return true
         
     | 
| 
      
 231 
     | 
    
         
            +
                  rescue ArgumentError, TypeError => err
         
     | 
| 
      
 232 
     | 
    
         
            +
                  end
         
     | 
| 
      
 233 
     | 
    
         
            +
             
     | 
| 
      
 234 
     | 
    
         
            +
                  "'#{value}' could not be cast to a #{types.join(' or a ')}"
         
     | 
| 
      
 235 
     | 
    
         
            +
                end
         
     | 
| 
      
 236 
     | 
    
         
            +
             
     | 
| 
      
 237 
     | 
    
         
            +
                def coerce_single_type(param, type, options)
         
     | 
| 
      
 238 
     | 
    
         
            +
                  return param if (param.is_a?(type) rescue false)
         
     | 
| 
      
 239 
     | 
    
         
            +
             
     | 
| 
      
 240 
     | 
    
         
            +
                  if type.is_a?(Class) && type <= ActiveRecord::Base
         
     | 
| 
      
 241 
     | 
    
         
            +
                    type = self.class.record_type(type)
         
     | 
| 
      
 242 
     | 
    
         
            +
                  end
         
     | 
| 
      
 243 
     | 
    
         
            +
             
     | 
| 
      
 244 
     | 
    
         
            +
                  return type.call(param, options) if type.is_a?(Proc)
         
     | 
| 
      
 245 
     | 
    
         
            +
             
     | 
| 
      
 246 
     | 
    
         
            +
                  if (param.is_a?(Array) && type != Array) || ((param.is_a?(Hash) || param.is_a?(ActionController::Parameters)) && type != Hash)
         
     | 
| 
      
 247 
     | 
    
         
            +
                    raise ArgumentError
         
     | 
| 
      
 248 
     | 
    
         
            +
                  end
         
     | 
| 
      
 249 
     | 
    
         
            +
                  return param if (param.is_a?(ActionController::Parameters) && type == Hash rescue false)
         
     | 
| 
      
 250 
     | 
    
         
            +
             
     | 
| 
      
 251 
     | 
    
         
            +
                  # Primitives
         
     | 
| 
      
 252 
     | 
    
         
            +
                  return Integer(param) if type == Integer
         
     | 
| 
      
 253 
     | 
    
         
            +
                  return Float(param) if type == Float
         
     | 
| 
      
 254 
     | 
    
         
            +
                  return Float(param) if type == Numeric
         
     | 
| 
      
 255 
     | 
    
         
            +
                  return String(param) if type == String
         
     | 
| 
      
 256 
     | 
    
         
            +
             
     | 
| 
      
 257 
     | 
    
         
            +
                  # Date/Time
         
     | 
| 
      
 258 
     | 
    
         
            +
                  if TIME_TYPES.include? type
         
     | 
| 
      
 259 
     | 
    
         
            +
                    if tz = options[:timezone]
         
     | 
| 
      
 260 
     | 
    
         
            +
                      tz = ActiveSupport::TimeZone[tz] if tz.is_a?(String)
         
     | 
| 
      
 261 
     | 
    
         
            +
                      dt = options[:format].present? ? tz.strptime(param, options[:format]) : tz.parse(param)
         
     | 
| 
      
 262 
     | 
    
         
            +
                      dt = dt.to_date if type == Date
         
     | 
| 
      
 263 
     | 
    
         
            +
                      return dt
         
     | 
| 
      
 264 
     | 
    
         
            +
                    else
         
     | 
| 
      
 265 
     | 
    
         
            +
                      if options[:format].present?
         
     | 
| 
      
 266 
     | 
    
         
            +
                        return type.strptime(param, options[:format])
         
     | 
| 
      
 267 
     | 
    
         
            +
                      else
         
     | 
| 
      
 268 
     | 
    
         
            +
                        return type.parse(param)
         
     | 
| 
      
 269 
     | 
    
         
            +
                      end
         
     | 
| 
      
 270 
     | 
    
         
            +
                    end
         
     | 
| 
      
 271 
     | 
    
         
            +
                  end
         
     | 
| 
      
 272 
     | 
    
         
            +
             
     | 
| 
      
 273 
     | 
    
         
            +
                  # Arrays/Hashes
         
     | 
| 
      
 274 
     | 
    
         
            +
                  raise ArgumentError if (type == Array || type == Hash) && !param.respond_to?(:split)
         
     | 
| 
      
 275 
     | 
    
         
            +
                  return Array(param.split(options[:delimiter] || ',')) if type == Array
         
     | 
| 
      
 276 
     | 
    
         
            +
                  return Hash[param.split(options[:delimiter] || ',').map { |c| c.split(options[:separator] || ':') }] if type == Hash
         
     | 
| 
      
 277 
     | 
    
         
            +
             
     | 
| 
      
 278 
     | 
    
         
            +
                  # Booleans
         
     | 
| 
      
 279 
     | 
    
         
            +
                  if [TrueClass, FalseClass, :boolean, :bool].include?(type)
         
     | 
| 
      
 280 
     | 
    
         
            +
                    return false if /^(false|f|no|n|0)$/i === param.to_s
         
     | 
| 
      
 281 
     | 
    
         
            +
                    return true if /^(true|t|yes|y|1)$/i === param.to_s
         
     | 
| 
      
 282 
     | 
    
         
            +
             
     | 
| 
      
 283 
     | 
    
         
            +
                    raise ArgumentError
         
     | 
| 
      
 284 
     | 
    
         
            +
                  end
         
     | 
| 
      
 285 
     | 
    
         
            +
             
     | 
| 
      
 286 
     | 
    
         
            +
                  # BigDecimals
         
     | 
| 
      
 287 
     | 
    
         
            +
                  if type == BigDecimal
         
     | 
| 
      
 288 
     | 
    
         
            +
                    param = param.delete('$,').strip.to_f if param.is_a?(String)
         
     | 
| 
      
 289 
     | 
    
         
            +
                    return BigDecimal(param, (options[:precision] || DEFAULT_PRECISION))
         
     | 
| 
      
 290 
     | 
    
         
            +
                  end
         
     | 
| 
      
 291 
     | 
    
         
            +
                  nil
         
     | 
| 
      
 292 
     | 
    
         
            +
                end
         
     | 
| 
      
 293 
     | 
    
         
            +
             
     | 
| 
      
 294 
     | 
    
         
            +
                def normalize_opts(*args, **kwargs, &blk)
         
     | 
| 
      
 295 
     | 
    
         
            +
                  # Stage 1 - Convert args to kwargs
         
     | 
| 
      
 296 
     | 
    
         
            +
                  args, norm = convert_positional_args(args, kwargs)
         
     | 
| 
      
 297 
     | 
    
         
            +
                  if blk.present?
         
     | 
| 
      
 298 
     | 
    
         
            +
                    type = args.delete(:items) ? :all_items : :all_block
         
     | 
| 
      
 299 
     | 
    
         
            +
                    set_hash_key(norm, type, blk)
         
     | 
| 
      
 300 
     | 
    
         
            +
                  end
         
     | 
| 
      
 301 
     | 
    
         
            +
                  set_hash_key(norm, :type, args.pop(0)) if args.present?
         
     | 
| 
      
 302 
     | 
    
         
            +
             
     | 
| 
      
 303 
     | 
    
         
            +
                  # Stage 2
         
     | 
| 
      
 304 
     | 
    
         
            +
                  norm = convert_flags(norm)
         
     | 
| 
      
 305 
     | 
    
         
            +
             
     | 
| 
      
 306 
     | 
    
         
            +
                  # Stage 3
         
     | 
| 
      
 307 
     | 
    
         
            +
                  norm = convert_prefixed_keys(norm)
         
     | 
| 
      
 308 
     | 
    
         
            +
             
     | 
| 
      
 309 
     | 
    
         
            +
                  extra_kwargs = norm.keys - PREFIXES - NON_PREFIXED
         
     | 
| 
      
 310 
     | 
    
         
            +
                  raise ArgumentError, "Unrecognized postitional arguments: #{args.inspect}" if args.present?
         
     | 
| 
      
 311 
     | 
    
         
            +
                  raise ArgumentError, "Unrecognized keyword arguments: #{extra_kwargs.inspect}" if extra_kwargs.present?
         
     | 
| 
      
 312 
     | 
    
         
            +
             
     | 
| 
      
 313 
     | 
    
         
            +
                  norm
         
     | 
| 
      
 314 
     | 
    
         
            +
                end
         
     | 
| 
      
 315 
     | 
    
         
            +
             
     | 
| 
      
 316 
     | 
    
         
            +
                def convert_positional_args(args, kwargs)
         
     | 
| 
      
 317 
     | 
    
         
            +
                  dest_hash = { **kwargs }
         
     | 
| 
      
 318 
     | 
    
         
            +
                  args = args.reject do |arg|
         
     | 
| 
      
 319 
     | 
    
         
            +
                    next false unless arg.is_a?(Symbol)
         
     | 
| 
      
 320 
     | 
    
         
            +
             
     | 
| 
      
 321 
     | 
    
         
            +
                    flag, pfx = split_key(arg)
         
     | 
| 
      
 322 
     | 
    
         
            +
                    next false unless VALID_FLAGS.include?(flag) && (pfx.nil? || PREFIXES.include?(pfx))
         
     | 
| 
      
 323 
     | 
    
         
            +
             
     | 
| 
      
 324 
     | 
    
         
            +
                    set_hash_key(dest_hash, :"#{pfx || 'all'}_#{flag}", true)
         
     | 
| 
      
 325 
     | 
    
         
            +
                    true
         
     | 
| 
      
 326 
     | 
    
         
            +
                  end
         
     | 
| 
      
 327 
     | 
    
         
            +
                  [args, dest_hash]
         
     | 
| 
      
 328 
     | 
    
         
            +
                end
         
     | 
| 
      
 329 
     | 
    
         
            +
             
     | 
| 
      
 330 
     | 
    
         
            +
                def convert_flags(h)
         
     | 
| 
      
 331 
     | 
    
         
            +
                  dest_hash = {}
         
     | 
| 
      
 332 
     | 
    
         
            +
                  h.each do |k, v|
         
     | 
| 
      
 333 
     | 
    
         
            +
                    if VALID_FLAGS.include?(k) && normalize_prefix(v)
         
     | 
| 
      
 334 
     | 
    
         
            +
                      set_hash_key(dest_hash, :"#{normalize_prefix(v)}_#{k}", true)
         
     | 
| 
      
 335 
     | 
    
         
            +
                    else
         
     | 
| 
      
 336 
     | 
    
         
            +
                      dest_hash[k] = v
         
     | 
| 
      
 337 
     | 
    
         
            +
                    end
         
     | 
| 
      
 338 
     | 
    
         
            +
                  end
         
     | 
| 
      
 339 
     | 
    
         
            +
                  dest_hash
         
     | 
| 
      
 340 
     | 
    
         
            +
                end
         
     | 
| 
      
 341 
     | 
    
         
            +
             
     | 
| 
      
 342 
     | 
    
         
            +
                def convert_prefixed_keys(h)
         
     | 
| 
      
 343 
     | 
    
         
            +
                  dest_hash = {}
         
     | 
| 
      
 344 
     | 
    
         
            +
             
     | 
| 
      
 345 
     | 
    
         
            +
                  h.each do |k, v|
         
     | 
| 
      
 346 
     | 
    
         
            +
                    flag, pfx = split_key(k)
         
     | 
| 
      
 347 
     | 
    
         
            +
                    if !CHECKS.include?(flag)
         
     | 
| 
      
 348 
     | 
    
         
            +
                      dest_hash[k] = v
         
     | 
| 
      
 349 
     | 
    
         
            +
                    elsif NON_PREFIXED.include?(flag)
         
     | 
| 
      
 350 
     | 
    
         
            +
                      # TODO: Raise warning if pfx
         
     | 
| 
      
 351 
     | 
    
         
            +
                      dest_hash[k] = v
         
     | 
| 
      
 352 
     | 
    
         
            +
                    elsif normalize_prefix(flag)
         
     | 
| 
      
 353 
     | 
    
         
            +
                      dest_hash[flag] = merge_hashes(dest_hash[normalize_prefix(flag)] || {}, v)
         
     | 
| 
      
 354 
     | 
    
         
            +
                    elsif pfx.nil? || PREFIXES.include?(pfx)
         
     | 
| 
      
 355 
     | 
    
         
            +
                      pfx ||= :all
         
     | 
| 
      
 356 
     | 
    
         
            +
                      dest_hash[pfx] ||= {}
         
     | 
| 
      
 357 
     | 
    
         
            +
                      set_hash_key(dest_hash[pfx], flag, v)
         
     | 
| 
      
 358 
     | 
    
         
            +
                    else
         
     | 
| 
      
 359 
     | 
    
         
            +
                      dest_hash[k] = v
         
     | 
| 
      
 360 
     | 
    
         
            +
                    end
         
     | 
| 
      
 361 
     | 
    
         
            +
                  end
         
     | 
| 
      
 362 
     | 
    
         
            +
             
     | 
| 
      
 363 
     | 
    
         
            +
                  dest_hash
         
     | 
| 
      
 364 
     | 
    
         
            +
                end
         
     | 
| 
      
 365 
     | 
    
         
            +
             
     | 
| 
      
 366 
     | 
    
         
            +
                def normalize_prefix(prefix)
         
     | 
| 
      
 367 
     | 
    
         
            +
                  prefix = PREFIX_ALIASES[prefix] if PREFIX_ALIASES.include?(prefix)
         
     | 
| 
      
 368 
     | 
    
         
            +
                  return nil unless PREFIXES.include?(prefix)
         
     | 
| 
      
 369 
     | 
    
         
            +
             
     | 
| 
      
 370 
     | 
    
         
            +
                  prefix
         
     | 
| 
      
 371 
     | 
    
         
            +
                end
         
     | 
| 
      
 372 
     | 
    
         
            +
             
     | 
| 
      
 373 
     | 
    
         
            +
                def split_key(key)
         
     | 
| 
      
 374 
     | 
    
         
            +
                  skey = key.to_s
         
     | 
| 
      
 375 
     | 
    
         
            +
                  ALL_PREFIXES.each do |pfx|
         
     | 
| 
      
 376 
     | 
    
         
            +
                    spfx = pfx.to_s
         
     | 
| 
      
 377 
     | 
    
         
            +
                    next unless skey.starts_with?("#{spfx}_")
         
     | 
| 
      
 378 
     | 
    
         
            +
             
     | 
| 
      
 379 
     | 
    
         
            +
                    return [skey[(spfx.length + 1)..-1].to_sym, PREFIX_ALIASES[pfx] || pfx]
         
     | 
| 
      
 380 
     | 
    
         
            +
                  end
         
     | 
| 
      
 381 
     | 
    
         
            +
                  [key, nil]
         
     | 
| 
      
 382 
     | 
    
         
            +
                end
         
     | 
| 
      
 383 
     | 
    
         
            +
             
     | 
| 
      
 384 
     | 
    
         
            +
                def sub_parameter(k)
         
     | 
| 
      
 385 
     | 
    
         
            +
                  @subkeys.push(k)
         
     | 
| 
      
 386 
     | 
    
         
            +
                  yield
         
     | 
| 
      
 387 
     | 
    
         
            +
                ensure
         
     | 
| 
      
 388 
     | 
    
         
            +
                  @subkeys.pop
         
     | 
| 
      
 389 
     | 
    
         
            +
                end
         
     | 
| 
      
 390 
     | 
    
         
            +
             
     | 
| 
      
 391 
     | 
    
         
            +
                def params
         
     | 
| 
      
 392 
     | 
    
         
            +
                  p = @params
         
     | 
| 
      
 393 
     | 
    
         
            +
                  @subkeys.each { |k| p = p[k] }
         
     | 
| 
      
 394 
     | 
    
         
            +
                  p
         
     | 
| 
      
 395 
     | 
    
         
            +
                end
         
     | 
| 
      
 396 
     | 
    
         
            +
             
     | 
| 
      
 397 
     | 
    
         
            +
                def merge_error_hashes(target, from)
         
     | 
| 
      
 398 
     | 
    
         
            +
                  target ||= []
         
     | 
| 
      
 399 
     | 
    
         
            +
                  if target.is_a?(Hash)
         
     | 
| 
      
 400 
     | 
    
         
            +
                    ta = []
         
     | 
| 
      
 401 
     | 
    
         
            +
                    th = target
         
     | 
| 
      
 402 
     | 
    
         
            +
                  else
         
     | 
| 
      
 403 
     | 
    
         
            +
                    ta = target
         
     | 
| 
      
 404 
     | 
    
         
            +
                    th = target[-1].is_a?(Hash) ? ta.pop : {}
         
     | 
| 
      
 405 
     | 
    
         
            +
                  end
         
     | 
| 
      
 406 
     | 
    
         
            +
             
     | 
| 
      
 407 
     | 
    
         
            +
                  if from.is_a?(Hash)
         
     | 
| 
      
 408 
     | 
    
         
            +
                    from.each_pair do |k, v|
         
     | 
| 
      
 409 
     | 
    
         
            +
                      th[k] = merge_error_hashes(th[k], v)
         
     | 
| 
      
 410 
     | 
    
         
            +
                    end
         
     | 
| 
      
 411 
     | 
    
         
            +
                  elsif from.is_a?(Array)
         
     | 
| 
      
 412 
     | 
    
         
            +
                    merge_error_hashes(th, from.pop) if from[-1].is_a?(Hash)
         
     | 
| 
      
 413 
     | 
    
         
            +
                    from.each { |f| ta << f }
         
     | 
| 
      
 414 
     | 
    
         
            +
                  else
         
     | 
| 
      
 415 
     | 
    
         
            +
                    ta << from
         
     | 
| 
      
 416 
     | 
    
         
            +
                  end
         
     | 
| 
      
 417 
     | 
    
         
            +
             
     | 
| 
      
 418 
     | 
    
         
            +
                  return th if !ta.present? && th.present?
         
     | 
| 
      
 419 
     | 
    
         
            +
             
     | 
| 
      
 420 
     | 
    
         
            +
                  ta << th if th.present?
         
     | 
| 
      
 421 
     | 
    
         
            +
                  ta
         
     | 
| 
      
 422 
     | 
    
         
            +
                end
         
     | 
| 
      
 423 
     | 
    
         
            +
             
     | 
| 
      
 424 
     | 
    
         
            +
                def merge_hashes(h1, h2)
         
     | 
| 
      
 425 
     | 
    
         
            +
                  h2.each do |k, v|
         
     | 
| 
      
 426 
     | 
    
         
            +
                    set_hash_key(h1, k, v)
         
     | 
| 
      
 427 
     | 
    
         
            +
                  end
         
     | 
| 
      
 428 
     | 
    
         
            +
                  h1
         
     | 
| 
      
 429 
     | 
    
         
            +
                end
         
     | 
| 
      
 430 
     | 
    
         
            +
             
     | 
| 
      
 431 
     | 
    
         
            +
                def set_hash_key(h, k, v)
         
     | 
| 
      
 432 
     | 
    
         
            +
                  k = k.to_sym
         
     | 
| 
      
 433 
     | 
    
         
            +
                  # TODO: warn if h[k].present?
         
     | 
| 
      
 434 
     | 
    
         
            +
                  h[k] = v
         
     | 
| 
      
 435 
     | 
    
         
            +
                end
         
     | 
| 
      
 436 
     | 
    
         
            +
             
     | 
| 
      
 437 
     | 
    
         
            +
                def trim_arguments(blk, args)
         
     | 
| 
      
 438 
     | 
    
         
            +
                  return args if blk.arity.negative?
         
     | 
| 
      
 439 
     | 
    
         
            +
                  args[0..(blk.arity.abs - 1)]
         
     | 
| 
      
 440 
     | 
    
         
            +
                end
         
     | 
| 
      
 441 
     | 
    
         
            +
              end
         
     | 
| 
      
 442 
     | 
    
         
            +
            end
         
     | 
    
        data/miscellany.gemspec
    ADDED
    
    | 
         @@ -0,0 +1,43 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # coding: utf-8
         
     | 
| 
      
 2 
     | 
    
         
            +
            lib = File.expand_path("../lib", __FILE__)
         
     | 
| 
      
 3 
     | 
    
         
            +
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            begin
         
     | 
| 
      
 6 
     | 
    
         
            +
              require "miscellany/version"
         
     | 
| 
      
 7 
     | 
    
         
            +
              version = Miscellany::VERSION
         
     | 
| 
      
 8 
     | 
    
         
            +
            rescue LoadError
         
     | 
| 
      
 9 
     | 
    
         
            +
              version = "0.0.0.docker"
         
     | 
| 
      
 10 
     | 
    
         
            +
            end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            Gem::Specification.new do |spec|
         
     | 
| 
      
 13 
     | 
    
         
            +
              spec.name          = "miscellany"
         
     | 
| 
      
 14 
     | 
    
         
            +
              spec.version       = version
         
     | 
| 
      
 15 
     | 
    
         
            +
              spec.authors       = ["Ethan Knapp"]
         
     | 
| 
      
 16 
     | 
    
         
            +
              spec.email         = ["eknapp@instructure.com"]
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              spec.summary       = "Gem for a bunch of random, re-usable Rails Concerns & Helpers"
         
     | 
| 
      
 19 
     | 
    
         
            +
              spec.homepage      = "https://instructure.com"
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              spec.files = Dir["{app,config,db,lib}/**/*", "README.md", "*.gemspec"]
         
     | 
| 
      
 22 
     | 
    
         
            +
              spec.test_files = Dir["spec/**/*"]
         
     | 
| 
      
 23 
     | 
    
         
            +
              spec.require_paths = ['lib']
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              spec.add_development_dependency "bundler", "~> 1.15"
         
     | 
| 
      
 26 
     | 
    
         
            +
              spec.add_development_dependency "rake", "~> 10.0"
         
     | 
| 
      
 27 
     | 
    
         
            +
              spec.add_development_dependency "rspec", "~> 3.0"
         
     | 
| 
      
 28 
     | 
    
         
            +
              spec.add_development_dependency "rspec-rails"
         
     | 
| 
      
 29 
     | 
    
         
            +
              spec.add_development_dependency "pg"
         
     | 
| 
      
 30 
     | 
    
         
            +
              spec.add_development_dependency "factory"
         
     | 
| 
      
 31 
     | 
    
         
            +
              spec.add_development_dependency "factory_bot"
         
     | 
| 
      
 32 
     | 
    
         
            +
              spec.add_development_dependency "timecop"
         
     | 
| 
      
 33 
     | 
    
         
            +
              spec.add_development_dependency "webmock"
         
     | 
| 
      
 34 
     | 
    
         
            +
              spec.add_development_dependency "sinatra", ">= 0"
         
     | 
| 
      
 35 
     | 
    
         
            +
              spec.add_development_dependency "shoulda-matchers"
         
     | 
| 
      
 36 
     | 
    
         
            +
              spec.add_development_dependency "yard"
         
     | 
| 
      
 37 
     | 
    
         
            +
              spec.add_development_dependency "pry"
         
     | 
| 
      
 38 
     | 
    
         
            +
              spec.add_development_dependency "pry-nav"
         
     | 
| 
      
 39 
     | 
    
         
            +
              spec.add_development_dependency "rubocop"
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
              spec.add_dependency "rails", ">= 5"
         
     | 
| 
      
 42 
     | 
    
         
            +
              spec.add_dependency "activerecord-import"
         
     | 
| 
      
 43 
     | 
    
         
            +
            end
         
     |