diff options
author | Lucas Nestor <lnestor@covermymeds.com> | 2018-08-02 16:05:21 -0400 |
---|---|---|
committer | Michael Herold <opensource@michaeljherold.com> | 2018-08-11 21:45:43 -0500 |
commit | 1d943216394f1b4a4eb7e0584144aaeeefa1e19a (patch) | |
tree | 437d95976cb0bfc9646708fb81b38cbc10ee5483 | |
parent | 7b415991772fa3f6ac30ca44b34c2c92b0114b08 (diff) | |
download | hashie-1d943216394f1b4a4eb7e0584144aaeeefa1e19a.tar.gz |
Add MethodOverridingInitializer extension
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | README.md | 20 | ||||
-rw-r--r-- | lib/hashie/extensions/method_access.rb | 54 | ||||
-rw-r--r-- | spec/hashie/extensions/method_access_spec.rb | 40 |
4 files changed, 103 insertions, 12 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 365ecfc..326e7f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ scheme are considered to be bugs. ### Added +* [#455](https://github.com/intridea/hashie/pull/455): Allow overriding methods when passing in a hash - [@lnestor](https://github.com/lnestor). * Your contribution here. ### Changed @@ -243,6 +243,26 @@ overriding.zip #=> 'a-dee-doo-dah' overriding.__zip #=> [[['zip', 'a-dee-doo-dah']]] ``` +### MethodOverridingInitializer + +The MethodOverridingInitializer extension will override hash methods if you pass in a normal hash to the constructor. It aliases any overridden method with two leading underscores. To include only this initializing functionality, you can include the single module `Hashie::Extensions::MethodOverridingInitializer`. + +```ruby +class MyHash < Hash +end + +class MyOverridingHash < Hash + include Hashie::Extensions::MethodOverridingInitializer +end + +non_overriding = MyHash.new(zip: 'a-dee-doo-dah') +non_overriding.zip #=> [] + +overriding = MyOverridingHash.new(zip: 'a-dee-doo-dah') +overriding.zip #=> 'a-dee-doo-dah' +overriding.__zip #=> [[['zip', 'a-dee-doo-dah']]] +``` + ### IndifferentAccess This extension can be mixed in to your Hash subclass to allow you to use Strings or Symbols interchangeably as keys; similar to the `params` hash in Rails. diff --git a/lib/hashie/extensions/method_access.rb b/lib/hashie/extensions/method_access.rb index e1ad56a..cf13da0 100644 --- a/lib/hashie/extensions/method_access.rb +++ b/lib/hashie/extensions/method_access.rb @@ -154,6 +154,22 @@ module Hashie end end + # A module shared between MethodOverridingWriter and MethodOverridingInitializer + # to contained shared logic. This module aids in redefining existing hash methods. + module RedefineMethod + protected + + def method?(name) + methods.map(&:to_s).include?(name) + end + + def redefine_method(method_name) + eigenclass = class << self; self; end + eigenclass.__send__(:alias_method, "__#{method_name}", method_name) + eigenclass.__send__(:define_method, method_name, -> { self[method_name] }) + end + end + # MethodOverridingWriter gives you #key_name= shortcuts for # writing to your hash. It allows methods to be overridden by # #key_name= shortcuts and aliases those methods with two @@ -179,6 +195,8 @@ module Hashie # h.__zip # => [[['awesome', 'sauce'], ['zip', 'a-dee-doo-dah']]] # module MethodOverridingWriter + include RedefineMethod + def convert_key(key) key.to_s end @@ -203,16 +221,6 @@ module Hashie def already_overridden?(name) method?("__#{name}") end - - def method?(name) - methods.map(&:to_s).include?(name) - end - - def redefine_method(method_name) - eigenclass = class << self; self; end - eigenclass.__send__(:alias_method, "__#{method_name}", method_name) - eigenclass.__send__(:define_method, method_name, -> { self[method_name] }) - end end # A macro module that will automatically include MethodReader, @@ -223,10 +231,34 @@ module Hashie # underscores. module MethodAccessWithOverride def self.included(base) - [MethodReader, MethodOverridingWriter, MethodQuery].each do |mod| + [MethodReader, MethodOverridingWriter, MethodQuery, MethodOverridingInitializer].each do |mod| base.send :include, mod end end end + + # MethodOverridingInitializer allows you to override default hash + # methods when passing in values from an existing hash. The overriden + # methods are aliased with two leading underscores. + # + # @example + # class MyHash < Hash + # include Hashie::Extensions::MethodOverridingInitializer + # end + # + # h = MyHash.new(zip: 'a-dee-doo-dah') + # h.zip # => 'a-dee-doo-dah' + # h.__zip # => [[['zip', 'a-dee-doo-dah']]] + module MethodOverridingInitializer + include RedefineMethod + + def initialize(hash = {}) + hash.each do |key, value| + skey = key.to_s + redefine_method(skey) if method?(skey) + self[skey] = value + end + end + end end end diff --git a/spec/hashie/extensions/method_access_spec.rb b/spec/hashie/extensions/method_access_spec.rb index 9ad09c7..dcd049d 100644 --- a/spec/hashie/extensions/method_access_spec.rb +++ b/spec/hashie/extensions/method_access_spec.rb @@ -181,8 +181,46 @@ end describe Hashie::Extensions::MethodAccessWithOverride do it 'includes all of the other method mixins' do + mod_list = [ + Hashie::Extensions::MethodReader, + Hashie::Extensions::MethodOverridingWriter, + Hashie::Extensions::MethodQuery, + Hashie::Extensions::MethodOverridingInitializer + ] + klass = Class.new(Hash) klass.send :include, Hashie::Extensions::MethodAccessWithOverride - expect((klass.ancestors & [Hashie::Extensions::MethodReader, Hashie::Extensions::MethodOverridingWriter, Hashie::Extensions::MethodQuery]).size).to eq 3 + + expect((klass.ancestors & mod_list).size).to eq 4 + end +end + +describe Hashie::Extensions::MethodOverridingInitializer do + class OverridingHash < Hash + include Hashie::Extensions::MethodOverridingInitializer + end + + context 'when the key is a string' do + subject { OverridingHash.new('zip' => 'a-dee-doo-dah') } + + it 'overrides the original method' do + expect(subject.zip).to eq 'a-dee-doo-dah' + end + + it 'aliases the method with two leading underscores' do + expect(subject.__zip).to eq [[%w[zip a-dee-doo-dah]]] + end + end + + context 'when the key is a symbol' do + subject { OverridingHash.new(zip: 'a-dee-doo-dah') } + + it 'overrides the original method' do + expect(subject.zip).to eq 'a-dee-doo-dah' + end + + it 'aliases the method with two leading underscores' do + expect(subject.__zip).to eq [[%w[zip a-dee-doo-dah]]] + end end end |