Skip to content

IndifferentHash improvements#1427

Merged
namusyaka merged 4 commits into
sinatra:masterfrom
mwpastore:indiff-hash
Aug 4, 2018
Merged

IndifferentHash improvements#1427
namusyaka merged 4 commits into
sinatra:masterfrom
mwpastore:indiff-hash

Conversation

@mwpastore

Copy link
Copy Markdown
Member

No description provided.

@mwpastore

Copy link
Copy Markdown
Member Author

Hrrm, it doesn't like ActiveSupport's hash extensions, which get pulled in by RABL during testing. I'll look into it.

@namusyaka

Copy link
Copy Markdown
Member

@mwpastore Broken test was fixed in #1425. Please rebase these commits.

@mwpastore

Copy link
Copy Markdown
Member Author

@namusyaka Hmm, not quite.

@namusyaka

Copy link
Copy Markdown
Member

This is due to the method defined by activesupport, see: https://github.com/rails/rails/blob/ee21e058424b2cb55bf74981c28b1ac0fb98b576/activesupport/lib/active_support/core_ext/hash/keys.rb
By requiring this, transform_keys returns Hash, not Sinatra::IndifferentHash.
The feature is required in rabl test.

We can avoid the failure by patching temporary workaround:

diff --git a/lib/sinatra/indifferent_hash.rb b/lib/sinatra/indifferent_hash.rb
index bf94f4e1..38427f05 100644
--- a/lib/sinatra/indifferent_hash.rb
+++ b/lib/sinatra/indifferent_hash.rb
@@ -164,7 +164,7 @@ module Sinatra
 
       def transform_keys!
         super
-        super(&method(:convert_key))
+        IndifferentHash[super(&method(:convert_key))]
       end
     end
 
diff --git a/test/indifferent_hash_test.rb b/test/indifferent_hash_test.rb
index a22d33ee..0a7ed94b 100644
--- a/test/indifferent_hash_test.rb
+++ b/test/indifferent_hash_test.rb
@@ -4,6 +4,7 @@
 #
 require 'minitest/autorun' unless defined?(Minitest)
 
+require 'active_support/core_ext/hash/conversions'
 require_relative '../lib/sinatra/indifferent_hash'
 
 class TestIndifferentHashBasics < Minitest::Test

However, I don't like the temporary patch. If you have any better solution, please write up or send pull request.

I'm going to re-visit this issue next week.
Thanks,

@namusyaka namusyaka added this to the v2.0.4 milestone Jun 19, 2018
@namusyaka namusyaka self-assigned this Jun 19, 2018
@mwpastore

Copy link
Copy Markdown
Member Author

Disregard my last comment. Whether we remove ActiveSupport or not, we still need to make IndifferentHash work when active_support/core_ext/hash is loaded. Let me make another run at it.

@mwpastore

Copy link
Copy Markdown
Member Author

@namusyaka

By requiring this, transform_keys returns Hash, not Sinatra::IndifferentHash.

I don't think that's correct. Sinatra::IndifferentHash#transform_keys(!) never calls Hash#transform_keys; it only ever calls Hash#transform_keys!. By design.

irb(main):003:0> require 'active_support/core_ext/hash/conversions'
=> true
irb(main):004:0> foo = Sinatra::IndifferentHash.new
=> {}
irb(main):005:0> foo['bar'] = 'qux'
=> "qux"
irb(main):009:0> foo.transform_keys!(&:upcase).class
=> Sinatra::IndifferentHash
irb(main):008:0> foo.transform_keys(&:upcase).class
=> Sinatra::IndifferentHash

This is without your patch above.

@mwpastore

mwpastore commented Jun 22, 2018

Copy link
Copy Markdown
Member Author

The problem is that Sinatra::IndifferentHash is being loaded before active_support/core_ext/hash when the full test suite is run under Ruby <2.5. It doesn't think Hash#transform_keys! is defined, so it doesn't attempt to redefine its behavior. At runtime, however, the method is there (on Sinatra::IndifferentHash's ancestor(s)), so it uses that without the indifferent access tweaks.

Fixing it "correctly" for both the test suite and production use would require some foreknowledge of whether or not active_support/core_ext/hash either has been or will be loaded in the current Ruby process. This issue extends to other Hash methods that active_support/core_ext/hash backports to older Rubies (so it's not restricted to just e.g. active_support/core_ext/hash/keys or active_support/core_ext/hash/conversions).

Here's a list of solutions and workarounds and why they're bad:

  1. Remove the method_defined? checks from Sinatra::IndifferentHash so these methods get defined whether or not they're already defined. This will simply break in the case when Sinatra::IndifferentHash is loaded before active_support/core_ext/hash, because it does the same check there.
  2. REMOVED
  3. If ActiveSupport is present in LOAD_PATH, we can preemptively load active_support/core_ext/hash. We shouldn't force all Sinatra users to use a monkey-patched Hash class in their apps.
  4. If ActiveSupport remains a Sinatra development dependency (i.e. if we don't remove RABL), we can preemptively load active_support/core_ext/hash in the test suite. This is probably the closest thing to a workable solution, but it only solves the test suite case, not the production use case.

Hopefully there's an option I'm overlooking. If not, I think the best approach is a hybrid of 4 and 3 above: we preemptively load active_support/core_ext/hash in the test suite, and print a warning on boot if ActiveSupport is present in LOAD_PATH (but not yet loaded?). The warning should say something to the effect of:

If you plan to load any of ActiveSupport's hash extensions, be sure to do so _before_ loading Sinatra::Application or Sinatra::Base. Otherwise, you may disregard this warning.

Let me know your thoughts.

@namusyaka

Copy link
Copy Markdown
Member

The 3 + 4 hybrid plan seems to be good.
I cannot fully predict what changes will be made by activesupport in the future.

Specifically, between ActiveSupport's core extensions to Hash and our
own IndifferentHash class.

@namusyaka namusyaka left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

@namusyaka namusyaka merged commit 1731bf5 into sinatra:master Aug 4, 2018
netbsd-srcmastr pushed a commit to NetBSD/pkgsrc that referenced this pull request Sep 23, 2018
## 2.0.4 / 2018-09-15

* Don't blow up when passing frozen string to `send_file` disposition [#1137](sinatra/sinatra#1137) by Andrew Selder

* Fix ubygems LoadError [#1436](sinatra/sinatra#1436) by Pavel Rosick�«ò

* Unescape regex captures [#1446](sinatra/sinatra#1446) by Jordan Owens

* Slight performance improvements for IndifferentHash [#1427](sinatra/sinatra#1427) by Mike Pastore

* Improve development support and documentation and source code by Will Yang, Jake Craige, Grey Baker and Guilherme Goettems Schneider

## 2.0.3 / 2018-06-09

* Fix the backports gem regression [#1442](sinatra/sinatra#1442) by Marc-Andr�«± Lafortune

## 2.0.2 / 2018-06-05

* Escape invalid query parameters [#1432](sinatra/sinatra#1432) by Kunpei Sakai
  * The patch fixes [CVE-2018-11627](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-11627).

* Fix undefined method error for `Sinatra::RequiredParams` with hash key [#1431](sinatra/sinatra#1431) by Arpit Chauhan

* Add xml content-types to valid html_types for Rack::Protection [#1413](sinatra/sinatra#1413) by Reenan Arbitrario

* Encode route parameters using :default_encoding setting [#1412](sinatra/sinatra#1412) by Brian m. Carlson

* Fix unpredictable behaviour from Sinatra::ConfigFile [#1244](sinatra/sinatra#1244) by John Hope

* Add Sinatra::IndifferentHash#slice [#1405](sinatra/sinatra#1405) by Shota Iguchi

* Remove status code 205 from drop body response [#1398](sinatra/sinatra#1398) by Shota Iguchi

* Ignore empty captures from params [#1390](sinatra/sinatra#1390) by Shota Iguchi

* Improve development support and documentation and source code by Zp Yuan, Andreas Finger, Olle Jonsson, Shota Iguchi, Nikita Bulai and Joshua O'Brien
msk pushed a commit to msk/pkgsrc that referenced this pull request May 11, 2026
## 2.0.4 / 2018-09-15

* Don't blow up when passing frozen string to `send_file` disposition [#1137](sinatra/sinatra#1137) by Andrew Selder

* Fix ubygems LoadError [#1436](sinatra/sinatra#1436) by Pavel Rosick�«ò

* Unescape regex captures [#1446](sinatra/sinatra#1446) by Jordan Owens

* Slight performance improvements for IndifferentHash [#1427](sinatra/sinatra#1427) by Mike Pastore

* Improve development support and documentation and source code by Will Yang, Jake Craige, Grey Baker and Guilherme Goettems Schneider

## 2.0.3 / 2018-06-09

* Fix the backports gem regression [#1442](sinatra/sinatra#1442) by Marc-Andr�«± Lafortune

## 2.0.2 / 2018-06-05

* Escape invalid query parameters [#1432](sinatra/sinatra#1432) by Kunpei Sakai
  * The patch fixes [CVE-2018-11627](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-11627).

* Fix undefined method error for `Sinatra::RequiredParams` with hash key [#1431](sinatra/sinatra#1431) by Arpit Chauhan

* Add xml content-types to valid html_types for Rack::Protection [#1413](sinatra/sinatra#1413) by Reenan Arbitrario

* Encode route parameters using :default_encoding setting [#1412](sinatra/sinatra#1412) by Brian m. Carlson

* Fix unpredictable behaviour from Sinatra::ConfigFile [#1244](sinatra/sinatra#1244) by John Hope

* Add Sinatra::IndifferentHash#slice [#1405](sinatra/sinatra#1405) by Shota Iguchi

* Remove status code 205 from drop body response [#1398](sinatra/sinatra#1398) by Shota Iguchi

* Ignore empty captures from params [#1390](sinatra/sinatra#1390) by Shota Iguchi

* Improve development support and documentation and source code by Zp Yuan, Andreas Finger, Olle Jonsson, Shota Iguchi, Nikita Bulai and Joshua O'Brien
jperkin pushed a commit to TritonDataCenter/pkgsrc that referenced this pull request May 14, 2026
## 2.0.4 / 2018-09-15

* Don't blow up when passing frozen string to `send_file` disposition [#1137](sinatra/sinatra#1137) by Andrew Selder

* Fix ubygems LoadError [#1436](sinatra/sinatra#1436) by Pavel Rosick�«ò

* Unescape regex captures [#1446](sinatra/sinatra#1446) by Jordan Owens

* Slight performance improvements for IndifferentHash [#1427](sinatra/sinatra#1427) by Mike Pastore

* Improve development support and documentation and source code by Will Yang, Jake Craige, Grey Baker and Guilherme Goettems Schneider

## 2.0.3 / 2018-06-09

* Fix the backports gem regression [#1442](sinatra/sinatra#1442) by Marc-Andr�«± Lafortune

## 2.0.2 / 2018-06-05

* Escape invalid query parameters [#1432](sinatra/sinatra#1432) by Kunpei Sakai
  * The patch fixes [CVE-2018-11627](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-11627).

* Fix undefined method error for `Sinatra::RequiredParams` with hash key [#1431](sinatra/sinatra#1431) by Arpit Chauhan

* Add xml content-types to valid html_types for Rack::Protection [#1413](sinatra/sinatra#1413) by Reenan Arbitrario

* Encode route parameters using :default_encoding setting [#1412](sinatra/sinatra#1412) by Brian m. Carlson

* Fix unpredictable behaviour from Sinatra::ConfigFile [#1244](sinatra/sinatra#1244) by John Hope

* Add Sinatra::IndifferentHash#slice [#1405](sinatra/sinatra#1405) by Shota Iguchi

* Remove status code 205 from drop body response [#1398](sinatra/sinatra#1398) by Shota Iguchi

* Ignore empty captures from params [#1390](sinatra/sinatra#1390) by Shota Iguchi

* Improve development support and documentation and source code by Zp Yuan, Andreas Finger, Olle Jonsson, Shota Iguchi, Nikita Bulai and Joshua O'Brien
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants