hashie/hashie

Rails 6 #except incompatible with Hashie::Mash

philipbjorge opened this issue · 7 comments

I wasn't really sure where to file this bug so I'm putting it here and rails core.


Steps to reproduce

foo = Hashie::Mash.new(x: 1, y: 2)
foo.except(:x)
# => {"x"=>1, "y"=>2}

This is a change in behavior from rails 5 to 6 related to this change: rails/rails@32db884

Expected behavior

Return a new Hashie object with its :x key removed.

Actual behavior

Returns a new hash that contains the x key still

System configuration

Rails version: rails 6.0.0.rc1

Ruby version: ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin18]


rails/rails#36444

Looks legit. Would be helpful to get a failing spec in https://github.com/intridea/hashie/tree/master/spec/integration, we have a few rails-specific ones.

Want to also contribute to https://code.dblock.org/2017/02/24/the-demonic-possession-of-hashie-mash.html ? :)

@dblock -- I think at its core, we're running into the issue defined here: #449

Can you give me a start on where/how these additional functions would be implemented (e.g. in mash.rb? in an extension? in mash and rash?)?

We generally want things to go way of extensions, but if these are only useful for Mash, then directly in there. But I bet @michaelherold has a stronger and better educated opinion.

I think at its core, we're running into the issue defined here: #449

This is a different issue. This is related to how we store keys for indifferent access. You expect that the Mash stores the key as :x but it's really storing the key as 'x'.

Rails itself works around this problem in ActiveSupport::HashWithIndifferentAccess by defining its own version of #except for HashWithIndifferentAccess that converts the keys to exclude. That they changed the implementation of that method in order to maintain compatibility with the core extension indicates that they changes the semantics of the method.

I really do not want to introduce Rails-specific behavior into Hashie in order to maintain compatibility with Rails' extensions. The only way I would really want to add this is through a Railtie because I don't want the #except method to exist outside of a Rails environment.

Perhaps a hashie-rails extension with Rails-specific logic would make sense. But I don't believe it belongs in Hashie proper.

(For other's benefit)
At this time, we're resolving by patching in the previous behavior.

class Hash
  def except(*keys)
    dup.except!(*keys)
  end
end

@michaelherold Is the issue the way the keys are stored or the fact that they switched to using #slice in their implementation?

Rails 6 switched their implementation of #except to use slice

@BobbyMcWho you've hit on something there. Thanks for that! I've reviewed your PR and will accept it with some changes.