Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rage Against the Params #1279

Merged
merged 1 commit into from
Apr 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Add full IndifferentHash implementation
  • Loading branch information
mwpastore committed Mar 31, 2017
commit 44c3cb89b7c9e26ce66e0e9bfe5a481fbef6fc22
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