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

Fixed #303 (deep_merge/stringify_keys/symbolize_keys not working via hash.extend(…)) #304

Merged
merged 1 commit into from Jun 10, 2015

Conversation

regexident
Copy link

Fixes issue #303

@michaelherold
Copy link
Member

Hmm ... I've thought about it a little bit and I came to a conclusion. I think duck-checking for this would be more appropriate, since ActiveSupport and the deep_merge gem, as well as others provide functionality like deep_merge, stringify_keys, etc. Since ActiveSupport automatically monkey-patches hashes when used in the Rails configuration, normal hashes will have a lot of these methods already.

Duck-checking also makes it a little more readable than checking the singleton class of the object:

copy = dup
copy.extend(Hashie::Extensions::DeepMerge) unless copy.respond_to?(:deep_merge!)
copy.deep_merge!(other_hash, &block)

Does anyone else have thoughts on the matter?

Also, this needs a change log entry and some style cleanup (per Rubocop) before we can merge it.

@mtrense
Copy link

mtrense commented Jun 5, 2015

@michaelherold But the dup'ed object either already has a deep_merge method (when included in it's class – in which case one would rather use the existing method instead of explicitly extending that one object) or does not get one at all (because it's a newly created object of the original objects class). So both checks are effectively equal to each other.

@michaelherold
Copy link
Member

They're not quite equivalent to each other. If our DeepMerge extension is unthinkingly placed on an object that already knows how to deep merge, then copy would respond to #deep_merge! so we actually wouldn't need to re-extend it given the assumption that all #deep_merge! behaviors work the same. (I admit that is a strong assumption, but in this case I believe it's true.)

I think the question becomes: what do we want the behavior of our mixins to be when they are extended onto an object that already has those methods? Option 1: always use our implementation. Option 2: use the object's pre-existing implementation, if available.

This behavior can be show with this little proof-of-concept:

class BaseClass
  def my_method
    puts 'base class bare method'
  end

  def my_method!
    puts 'base class bang method'
  end
end

module DuckCheckingExtension
  def my_method
    copy = dup
    copy.extend(DuckCheckingExtension) unless copy.respond_to?(:my_method!)
    copy.my_method!
  end

  def my_method!
    puts 'extension method'
  end
end

module SingletonCheckingExtension
  def my_method
    copy = dup
    copy.extend(SingletonCheckingExtension) unless (class << copy; self; end).included_modules.include?(SingletonCheckingExtension)
    copy.my_method!
  end

  def my_method!
    puts 'extension method'
  end
end

test = BaseClass.new
test.extend(DuckCheckingExtension)
test.my_method
#=> base class bang method

test2 = BaseClass.new
test2.extend(SingletonCheckingExtension)
test2.my_method
#=> extension method

The duck checking is also significantly faster on the proof-of-concept, since we're not building and checking the singleton class:

require 'benchmark'

GC.disable

N = 1_000_000

Benchmark.bmbm do |x|
  x.report(:duck) { N.times { test.my_method } }
  x.report(:singleton) { N.times { test2.my_method } }
end
Rehearsal ---------------------------------------------
duck        0.320000   0.000000   0.320000 (  0.322056)
singleton   1.320000   0.170000   1.490000 (  1.484601)
------------------------------------ total: 1.810000sec

                user     system      total        real
duck        0.290000   0.010000   0.300000 (  0.307832)
singleton   1.320000   0.140000   1.460000 (  1.445568)

@dblock
Copy link
Member

dblock commented Jun 7, 2015

There're some build errors, to start. Also please squash these commits.

@dblock
Copy link
Member

dblock commented Jun 10, 2015

@michaelherold Do you see any issues with this? Feel free to merge if you like.

michaelherold added a commit that referenced this pull request Jun 10, 2015
Fixed #303 (deep_merge/stringify_keys/symbolize_keys not working via hash.extend(…))
@michaelherold michaelherold merged commit edeef56 into hashie:master Jun 10, 2015
@regexident
Copy link
Author

Thanks a bunch! 🙇

jsonn pushed a commit to jsonn/pkgsrc that referenced this pull request Dec 12, 2015
## 3.4.3 (10/25/2015)

* [#314](hashie/hashie#314): Added a
  `StrictKeyAccess` extension that will raise an error whenever a key is
  accessed that does not exist in the hash -
  [@pboling](https://github.com/pboling).

* [#304](hashie/hashie#304): Ensured compatibility
  of `Hash` extensions with singleton objects -
  [@regexident](https://github.com/regexident).

* [#306](hashie/hashie#306): Added
  `Hashie::Extensions::Dash::Coercion` -
  [@marshall-lee](https://github.com/marshall-lee).

* [#310](hashie/hashie#310): Fixed
  `Hashie::Extensions::SafeAssignment` bug with private methods -
  [@marshall-lee](https://github.com/marshall-lee).

* [#313](hashie/hashie#313): Restrict pending spec
  to only Ruby versions 2.2.0-2.2.2 - [@pboling](https://github.com/pboling).

* [#315](hashie/hashie#315): Default `bin/` scripts:
  `console` and `setup` - [@pboling](https://github.com/pboling).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants