Skip to content

Commit

Permalink
Merge pull request #1279 from mwpastore/full-indifference
Browse files Browse the repository at this point in the history
Rage Against the Params
  • Loading branch information
Zachary Scott authored Apr 1, 2017
2 parents 676cb67 + 44c3cb8 commit 4194577
Show file tree
Hide file tree
Showing 5 changed files with 353 additions and 33 deletions.
30 changes: 2 additions & 28 deletions lib/sinatra/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
require 'uri'

# other files we need
require 'sinatra/indifferent_hash'
require 'sinatra/show_exceptions'
require 'sinatra/version'

Expand Down Expand Up @@ -240,18 +241,6 @@ class NotFound < NameError #:nodoc:
def http_status; 404 end
end

class IndifferentHash < Hash
def [](key)
value = super(key)
return super(key.to_s) if value.nil? && Symbol === key
value
end

def has_key?(key)
super(key) || (Symbol === key && super(key.to_s))
end
end

# Methods available to routes, before/after filters, and views.
module Helpers
# Set or retrieve the response status code.
Expand Down Expand Up @@ -1078,20 +1067,6 @@ def static!(options = {})
send_file path, options.merge(:disposition => nil)
end

# Enable string or symbol key access to the nested params hash.
def indifferent_params(object)
case object
when Hash
new_hash = IndifferentHash.new
object.each { |key, value| new_hash[key] = indifferent_params(value) }
new_hash
when Array
object.map { |item| indifferent_params(item) }
else
object
end
end

# Run the block with 'throw :halt' support and apply result to the response.
def invoke
res = catch(:halt) { yield }
Expand All @@ -1110,8 +1085,7 @@ def invoke

# Dispatch a request with error handling.
def dispatch!
@params = indifferent_params(@request.params)
force_encoding(@params)
force_encoding(@params = IndifferentHash[@request.params])

invoke do
static! if settings.static? && (request.get? || request.head?)
Expand Down
150 changes: 150 additions & 0 deletions lib/sinatra/indifferent_hash.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# frozen_string_literal: true
module Sinatra
# A poor man's ActiveSupport::HashWithIndifferentAccess, with all the Rails-y
# stuff removed.
#
# Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are
# considered to be the same.
#
# rgb = Sinatra::IndifferentHash.new
#
# rgb[:black] = '#000000' # symbol assignment
# rgb[:black] # => '#000000' # symbol retrieval
# rgb['black'] # => '#000000' # string retrieval
#
# rgb['white'] = '#FFFFFF' # string assignment
# rgb[:white] # => '#FFFFFF' # symbol retrieval
# rgb['white'] # => '#FFFFFF' # string retrieval
#
# Internally, symbols are mapped to strings when used as keys in the entire
# writing interface (calling e.g. <tt>[]=</tt>, <tt>merge</tt>). This mapping
# belongs to the public interface. For example, given:
#
# hash = Sinatra::IndifferentHash.new(:a=>1)
#
# You are guaranteed that the key is returned as a string:
#
# hash.keys # => ["a"]
#
# Technically other types of keys are accepted:
#
# hash = Sinatra::IndifferentHash.new(:a=>1)
# hash[0] = 0
# hash # => { "a"=>1, 0=>0 }
#
# But this class is intended for use cases where strings or symbols are the
# expected keys and it is convenient to understand both as the same. For
# example the +params+ hash in Sinatra.
class IndifferentHash < Hash
def self.[](*args)
new.merge!(Hash[*args])
end

def initialize(*args)
super(*args.map(&method(:convert_value)))
end

def default(*args)
super(*args.map(&method(:convert_key)))
end

def default=(value)
super(convert_value(value))
end

def assoc(key)
super(convert_key(key))
end

def rassoc(value)
super(convert_value(value))
end

def fetch(key, *args)
super(convert_key(key), *args.map(&method(:convert_value)))
end

def [](key)
super(convert_key(key))
end

def []=(key, value)
super(convert_key(key), convert_value(value))
end

alias_method :store, :[]=

def key(value)
super(convert_value(value))
end

def key?(key)
super(convert_key(key))
end

alias_method :has_key?, :key?
alias_method :include?, :key?
alias_method :member?, :key?

def value?(value)
super(convert_value(value))
end

alias_method :has_value?, :value?

def delete(key)
super(convert_key(key))
end

def dig(key, *other_keys)
super(convert_key(key), *other_keys)
end if method_defined?(:dig) # Added in Ruby 2.3

def fetch_values(*keys)
super(*keys.map(&method(:convert_key)))
end if method_defined?(:fetch_values) # Added in Ruby 2.3

def values_at(*keys)
super(*keys.map(&method(:convert_key)))
end

def merge!(other_hash)
return super if other_hash.is_a?(self.class)

other_hash.each_pair do |key, value|
key = convert_key(key)
value = yield(key, self[key], value) if block_given? && key?(key)
self[key] = convert_value(value)
end

self
end

alias_method :update, :merge!

def merge(other_hash, &block)
dup.merge!(other_hash, &block)
end

def replace(other_hash)
super(other_hash.is_a?(self.class) ? other_hash : self.class[other_hash])
end

private

def convert_key(key)
key.is_a?(Symbol) ? key.to_s : key
end

def convert_value(value)
case value
when Hash
value.is_a?(self.class) ? value : self.class[value]
when Array
value.map(&method(:convert_value))
else
value
end
end
end
end
7 changes: 3 additions & 4 deletions sinatra-contrib/lib/sinatra/config_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,12 @@ def message
# returned config is a indifferently accessible Hash, which means that you
# can get its values using Strings or Symbols as keys.
def config_for_env(hash)
if hash.respond_to? :keys and hash.keys.all? { |k| environments.include? k.to_s }
if hash.respond_to?(:keys) && hash.keys.all? { |k| environments.include?(k.to_s) }
hash = hash[environment.to_s] || hash[environment.to_sym]
end

if hash.respond_to? :to_hash
indifferent_hash = Hash.new { |hash, key| hash[key.to_s] if Symbol === key }
indifferent_hash.merge hash.to_hash
if hash.respond_to?(:to_hash)
IndifferentHash[hash.to_hash]
else
hash
end
Expand Down
Loading

0 comments on commit 4194577

Please sign in to comment.