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

IndifferentHash improvements #1427

Merged
merged 4 commits into from
Aug 4, 2018
Merged

Conversation

mwpastore
Copy link
Member

No description provided.

@mwpastore
Copy link
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
Member

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

@mwpastore
Copy link
Member Author

@namusyaka Hmm, not quite.

@namusyaka
Copy link
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
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
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
Copy link
Member Author

mwpastore commented Jun 22, 2018

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
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.
Copy link
Member

@namusyaka namusyaka left a comment

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
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