summary refs log tree commit
diff options
context:
space:
mode:
authorTed Johansson <drenmi@gmail.com>2019-08-01 19:50:12 +0800
committerTed Johansson <drenmi@gmail.com>2019-08-03 10:09:50 +0800
commitcb2ab0baf6d5bec8fcdb1a45c215d521ff0f471c (patch)
tree13f1bb0bf0e23df8a03cadf0b8e38d75ceb72a05
parentbbd2a93f422508c615ae23d007c34b8bfb405b91 (diff)
downloadrack-cb2ab0baf6d5bec8fcdb1a45c215d521ff0f471c.tar.gz
Refactor QueryParser::Params#to_params_hash for readability and performance
-rw-r--r--lib/rack/query_parser.rb50
1 files changed, 35 insertions, 15 deletions
diff --git a/lib/rack/query_parser.rb b/lib/rack/query_parser.rb
index fce1ce91..2a4eb244 100644
--- a/lib/rack/query_parser.rb
+++ b/lib/rack/query_parser.rb
@@ -55,7 +55,7 @@ module Rack
         end
       end
 
-      return params.to_params_hash
+      return params.to_h
     end
 
     # parse_nested_query expands a query string into structural types. Supported
@@ -73,7 +73,7 @@ module Rack
         normalize_params(params, k, v, param_depth_limit)
       end
 
-      return params.to_params_hash
+      return params.to_h
     rescue ArgumentError => e
       raise InvalidParameterError, e.message
     end
@@ -177,22 +177,42 @@ module Rack
         @params.key?(key)
       end
 
-      def to_params_hash
-        hash = @params
-        hash.keys.each do |key|
-          value = hash[key]
-          if value.kind_of?(self.class)
-            if value.object_id == self.object_id
-              hash[key] = hash
-            else
-              hash[key] = value.to_params_hash
-            end
-          elsif value.kind_of?(Array)
-            value.map! {|x| x.kind_of?(self.class) ? x.to_params_hash : x}
+      # Recursively unwraps nested `Params` objects and constructs an object
+      # of the same shape, but using the objects' internal representations
+      # (Ruby hashes) in place of the objects. The result is a hash consisting
+      # purely of Ruby primitives.
+      #
+      #   Mutation warning!
+      #
+      #   1. This method mutates the internal representation of the `Params`
+      #      objects in order to save object allocations.
+      #
+      #   2. The value you get back is a reference to the internal hash
+      #      representation, not a copy.
+      #
+      #   3. Because the `Params` object's internal representation is mutable
+      #      through the `#[]=` method, it is not thread safe. The result of
+      #      getting the hash representation while another thread is adding a
+      #      key to it is non-deterministic.
+      #
+      def to_h
+        @params.each do |key, value|
+          case value
+          when self
+            # Handle circular references gracefully.
+            @params[key] = @params
+          when Params
+            @params[key] = value.to_h
+          when Array
+            value.map! { |v| v.kind_of?(Params) ? v.to_h : v }
+          else
+            # Ignore anything that is not a `Params` object or
+            # a collection that can contain one.
           end
         end
-        hash
+        @params
       end
+      alias_method :to_params_hash, :to_h
     end
   end
 end