hashie-3.4.3/0000755000004100000410000000000012620101230012775 5ustar www-datawww-datahashie-3.4.3/Rakefile0000644000004100000410000000044012620101230014440 0ustar www-datawww-datarequire 'rubygems' require 'bundler' Bundler.setup Bundler::GemHelper.install_tasks require 'rspec/core/rake_task' RSpec::Core::RakeTask.new do |spec| spec.pattern = 'spec/**/*_spec.rb' end require 'rubocop/rake_task' RuboCop::RakeTask.new(:rubocop) task default: [:rubocop, :spec] hashie-3.4.3/UPGRADING.md0000644000004100000410000001041212620101230014635 0ustar www-datawww-dataUpgrading Hashie ================ ### Upgrading to 3.2.2 #### Testing if key defined In versions <= 3.2.1 Hash object being questioned doesn't return a boolean value as it's mentioned in README.md ```ruby class MyHash < Hash include Hashie::Extensions::MethodAccess end h = MyHash.new h.abc = 'def' h.abc # => 'def' h.abc? # => 'def' ``` In versions >= 3.2.2 it returns a boolean value ```ruby h.abc? # => true h.abb? # => false ``` ### Upgrading to 3.2.1 #### Possible coercion changes The improvements made to coercions in version 3.2.1 [issue #200](https://github.com/intridea/hashie/pull/200) do not break the documented API, but are significant enough that changes may effect undocumented side-effects. Applications that depended on those side-effects will need to be updated. **Change**: Type coercion no longer creates new objects if the input matches the target type. Previously coerced properties always resulted in the creation of a new object, even when it wasn't necessary. This had the effect of a `dup` or `clone` on coerced properties but not uncoerced ones. If necessary, `dup` or `clone` your own objects. Do not assume Hashie will do it for you. **Change**: Failed coercion attempts now raise Hashie::CoercionError. Hashie now raises a Hashie::CoercionError that details on the property that could not be coerced, the source and target type of the coercion, and the internal error. Previously only the internal error was raised. Applications that were attempting to rescuing the internal errors should be updated to rescue Hashie::CoercionError instead. ### Upgrading to 3.0 #### Compatibility with Rails 4 Strong Parameters Version 2.1 introduced support to prevent default Rails 4 mass-assignment protection behavior. This was [issue #89](https://github.com/intridea/hashie/issues/89), resolved in [#104](https://github.com/intridea/hashie/pull/104). In version 2.2 this behavior has been removed in [#147](https://github.com/intridea/hashie/pull/147) in favor of a mixin and finally extracted into a separate gem in Hashie 3.0. To enable 2.1 compatible behavior with Rails 4, use the [hashie_rails](http://rubygems.org/gems/hashie_rails) gem. ``` gem 'hashie_rails' ``` See [#154](https://github.com/intridea/hashie/pull/154) and [Mash and Rails 4 Strong Parameters](README.md#mash-and-rails-4-strong-parameters) for more details. #### Key Conversions in Hashie::Dash and Hashie::Trash Version 2.1 and older of Hashie::Dash and Hashie::Trash converted keys to strings by default. This is no longer the case in 2.2. Consider the following code. ```ruby class Person < Hashie::Dash property :name end p = Person.new(name: 'dB.') ``` Version 2.1 behaves as follows. ```ruby p.name # => 'dB.' p[:name] # => 'dB.' p['name'] # => 'dB.' # not what I put in p.inspect # => { 'name' => 'dB.' } p.to_hash # => { 'name' => 'dB.' } ``` It was not possible to achieve the behavior of preserving keys, as described in [issue #151](https://github.com/intridea/hashie/issues/151). Version 2.2 does not perform this conversion by default. ```ruby p.name # => 'dB.' p[:name] # => 'dB.' # p['name'] # => NoMethodError p.inspect # => { :name => 'dB.' } p.to_hash # => { :name => 'dB.' } ``` To enable behavior compatible with older versions, use `Hashie::Extensions::Dash::IndifferentAccess`. ```ruby class Person < Hashie::Dash include Hashie::Extensions::Dash::IndifferentAccess property :name end ``` #### Key Conversions in Hashie::Hash#to_hash Version 2.1 or older of Hash#to_hash converted keys to strings automatically. ```ruby instance = Hashie::Hash[first: 'First', 'last' => 'Last'] instance.to_hash # => { "first" => 'First', "last" => 'Last' } ``` It was possible to symbolize keys by passing `:symbolize_keys`, however it was not possible to retrieve the hash with initial key values. ```ruby instance.to_hash(symbolize_keys: true) # => { :first => 'First', :last => 'Last' } instance.to_hash(stringify_keys: true) # => { "first" => 'First', "last" => 'Last' } ``` Version 2.2 no longer converts keys by default. ```ruby instance = Hashie::Hash[first: 'First', 'last' => 'Last'] instance.to_hash # => { :first => 'First', "last" => 'Last' } ``` The behavior with `symbolize_keys` and `stringify_keys` is unchanged. See [#152](https://github.com/intridea/hashie/pull/152) for more information. hashie-3.4.3/spec/0000755000004100000410000000000012620101230013727 5ustar www-datawww-datahashie-3.4.3/spec/spec_helper.rb0000644000004100000410000000042112620101230016542 0ustar www-datawww-dataif ENV['CI'] require 'codeclimate-test-reporter' CodeClimate::TestReporter.start end require 'pry' require 'rspec' require 'hashie' require 'rspec/pending_for' RSpec.configure do |config| config.expect_with :rspec do |expect| expect.syntax = :expect end end hashie-3.4.3/spec/hashie/0000755000004100000410000000000012620101230015170 5ustar www-datawww-datahashie-3.4.3/spec/hashie/rash_spec.rb0000644000004100000410000000415212620101230017466 0ustar www-datawww-datarequire 'spec_helper' describe Hashie::Rash do subject do Hashie::Rash.new( /hello/ => 'hello', /world/ => 'world', 'other' => 'whee', true => false, 1 => 'awesome', 1..1000 => 'rangey', /(bcd)/ => proc { |m| m[1] } # /.+/ => "EVERYTHING" ) end it 'finds strings' do expect(subject['other']).to eq 'whee' expect(subject['well hello there']).to eq 'hello' expect(subject['the world is round']).to eq 'world' expect(subject.all('hello world').sort).to eq %w(hello world) end it 'finds regexps' do expect(subject[/other/]).to eq 'whee' end it 'finds other objects' do expect(subject[true]).to eq false expect(subject[1]).to eq 'awesome' end it 'finds numbers from ranges' do expect(subject[250]).to eq 'rangey' expect(subject[999]).to eq 'rangey' expect(subject[1000]).to eq 'rangey' expect(subject[1001]).to be_nil end it 'finds floats from ranges' do expect(subject[10.1]).to eq 'rangey' expect(subject[1.0]).to eq 'rangey' expect(subject[1000.1]).to be_nil end it 'evaluates proc values' do expect(subject['abcdef']).to eq 'bcd' expect(subject['ffffff']).to be_nil end it 'finds using the find method' do expect(subject.fetch(10.1)).to eq 'rangey' expect(subject.fetch(true)).to be false end it 'raises in find unless a key matches' do expect { subject.fetch(1_000_000) }.to raise_error(KeyError) end it 'yields in find unless a key matches' do expect { |y| subject.fetch(1_000_000, &y) }.to yield_control expect { |y| subject.fetch(10.1, &y) }.to_not yield_control end it 'gives a default value' do expect(subject.fetch(10.1, 'noop')).to eq 'rangey' expect(subject.fetch(1_000_000, 'noop')).to eq 'noop' expect(subject.fetch(1_000_000) { 'noop' }).to eq 'noop' expect(subject.fetch(1_000_000) { |k| k }).to eq 1_000_000 expect(subject.fetch(1_000_000, 'noop') { 'op' }).to eq 'op' end it 'responds to hash methods' do expect(subject.respond_to?(:to_a)).to be true expect(subject.methods).to_not include(:to_a) end end hashie-3.4.3/spec/hashie/trash_spec.rb0000644000004100000410000002070512620101230017654 0ustar www-datawww-datarequire 'spec_helper' describe Hashie::Trash do class TrashTest < Hashie::Trash property :first_name, from: :firstName end let(:trash) { TrashTest.new } describe 'translating properties' do it 'adds the property to the list' do expect(TrashTest.properties).to include(:first_name) end it 'creates a method for reading the property' do expect(trash).to respond_to(:first_name) end it 'creates a method for writing the property' do expect(trash).to respond_to(:first_name=) end it 'creates a method for writing the translated property' do expect(trash).to respond_to(:firstName=) end it 'does not create a method for reading the translated property' do expect(trash).not_to respond_to(:firstName) end it 'maintains translations hash mapping from the original to the translated name' do expect(TrashTest.translations[:firstName]).to eq(:first_name) end it 'maintains inverse translations hash mapping from the translated to the original name' do expect(TrashTest.inverse_translations[:first_name]).to eq :firstName end it '#permitted_input_keys contain the :from key of properties with translations' do expect(TrashTest.permitted_input_keys).to include :firstName end end describe 'standard properties' do class TrashTestPermitted < Hashie::Trash property :id end it '#permitted_input_keys contain names of properties without translations' do expect(TrashTestPermitted.permitted_input_keys).to include :id end end describe 'writing to properties' do it 'does not write to a non-existent property using []=' do expect { trash['abc'] = 123 }.to raise_error(NoMethodError) end it 'writes to an existing property using []=' do expect { trash[:first_name] = 'Bob' }.not_to raise_error expect(trash.first_name).to eq('Bob') expect { trash['first_name'] = 'John' }.to raise_error(NoMethodError) end it 'writes to a translated property using []=' do expect { trash[:firstName] = 'Bob' }.not_to raise_error expect { trash['firstName'] = 'Bob' }.to raise_error(NoMethodError) end it 'reads/writes to an existing property using a method call' do trash.first_name = 'Franklin' expect(trash.first_name).to eq 'Franklin' end it 'writes to an translated property using a method call' do trash.firstName = 'Franklin' expect(trash.first_name).to eq 'Franklin' end it 'writes to a translated property using #replace' do trash.replace(firstName: 'Franklin') expect(trash.first_name).to eq 'Franklin' end it 'writes to a non-translated property using #replace' do trash.replace(first_name: 'Franklin') expect(trash.first_name).to eq 'Franklin' end end describe ' initializing with a Hash' do it 'does not initialize non-existent properties' do expect { TrashTest.new(bork: 'abc') }.to raise_error(NoMethodError) end it 'sets the desired properties' do expect(TrashTest.new(first_name: 'Michael').first_name).to eq 'Michael' end context 'with both the translated property and the property' do it 'sets the desired properties' do expect(TrashTest.new(first_name: 'Michael', firstName: 'Maeve').first_name).to eq 'Michael' end end it 'sets the translated properties' do expect(TrashTest.new(firstName: 'Michael').first_name).to eq 'Michael' end end describe 'translating properties using a proc' do class TrashLambdaTest < Hashie::Trash property :first_name, from: :firstName, with: ->(value) { value.reverse } end let(:lambda_trash) { TrashLambdaTest.new } it 'translates the value given on initialization with the given lambda' do expect(TrashLambdaTest.new(firstName: 'Michael').first_name).to eq 'Michael'.reverse end it 'does not translate the value if given with the right property' do expect(TrashTest.new(first_name: 'Michael').first_name).to eq 'Michael' end it 'translates the value given as property with the given lambda' do lambda_trash.firstName = 'Michael' expect(lambda_trash.first_name).to eq 'Michael'.reverse end it 'does not translate the value given as right property' do lambda_trash.first_name = 'Michael' expect(lambda_trash.first_name).to eq 'Michael' end end describe 'translating multiple properties using a proc' do class SomeDataModel < Hashie::Trash property :value_a, from: :config, with: ->(config) { config.a } property :value_b, from: :config, with: ->(config) { config.b } end ConfigDataModel = Struct.new(:a, :b) subject { SomeDataModel.new(config: ConfigDataModel.new('value in a', 'value in b')) } it 'translates the first key' do expect(subject.value_a).to eq 'value in a' end it 'translates the second key' do expect(subject.value_b).to eq 'value in b' end it 'maintains translations hash mapping from the original to the translated name' do expect(SomeDataModel.translations).to eq(config: [:value_a, :value_b]) end end describe 'uses with or transform_with interchangeably' do class TrashLambdaTestTransformWith < Hashie::Trash property :first_name, from: :firstName, transform_with: ->(value) { value.reverse } end let(:lambda_trash) { TrashLambdaTestTransformWith.new } it 'translates the value given as property with the given lambda' do lambda_trash.firstName = 'Michael' expect(lambda_trash.first_name).to eq 'Michael'.reverse end it 'does not translate the value given as right property' do lambda_trash.first_name = 'Michael' expect(lambda_trash.first_name).to eq 'Michael' end end describe 'translating properties without from option using a proc' do class TrashLambdaTestWithProperties < Hashie::Trash property :first_name, transform_with: ->(value) { value.reverse } end let(:lambda_trash) { TrashLambdaTestWithProperties.new } it 'translates the value given as property with the given lambda' do lambda_trash.first_name = 'Michael' expect(lambda_trash.first_name).to eq 'Michael'.reverse end it 'transforms the value when given in constructor' do expect(TrashLambdaTestWithProperties.new(first_name: 'Michael').first_name).to eq 'Michael'.reverse end context 'when :from option is given' do class TrashLambdaTest3 < Hashie::Trash property :first_name, from: :firstName, transform_with: ->(value) { value.reverse } end it 'does not override the :from option in the constructor' do expect(TrashLambdaTest3.new(first_name: 'Michael').first_name).to eq 'Michael' end it 'does not override the :from option when given as property' do t = TrashLambdaTest3.new t.first_name = 'Michael' expect(t.first_name).to eq 'Michael' end end end describe 'inheritable transforms' do class TransformA < Hashie::Trash property :some_value, transform_with: ->(v) { v.to_i } end class TransformB < TransformA property :some_other_value, transform_with: ->(v) { v.to_i } end class TransformC < TransformB property :some_value, transform_with: ->(v) { -v.to_i } end it 'inherit properties transforms' do expect(TransformB.new(some_value: '123', some_other_value: '456').some_value).to eq(123) end it 'replaces property transform' do expect(TransformC.new(some_value: '123', some_other_value: '456').some_value).to eq(-123) end end describe 'inheritable translations' do class TranslationA < Hashie::Trash property :some_value, from: 'someValue', with: ->(v) { v.to_i } end class TranslationB < TranslationA property :some_other_value, from: 'someOtherValue' end it 'inherit properties translations' do expect(TranslationB.new('someValue' => '123').some_value).to eq(123) end end it 'raises an error when :from have the same value as property' do expect do class WrongTrash < Hashie::Trash property :first_name, from: :first_name end end.to raise_error(ArgumentError) end context 'when subclassing' do class Person < Hashie::Trash property :first_name, from: :firstName end class Hobbit < Person; end subject { Hobbit.new(firstName: 'Frodo') } it 'keeps translation definitions in subclasses' do expect(subject.first_name).to eq('Frodo') end end end hashie-3.4.3/spec/hashie/mash_spec.rb0000644000004100000410000005230512620101230017464 0ustar www-datawww-datarequire 'spec_helper' require 'delegate' describe Hashie::Mash do subject { Hashie::Mash.new } it 'inherits from Hash' do expect(subject.is_a?(Hash)).to be_truthy end it 'sets hash values through method= calls' do subject.test = 'abc' expect(subject['test']).to eq 'abc' end it 'retrieves set values through method calls' do subject['test'] = 'abc' expect(subject.test).to eq 'abc' end it 'retrieves set values through blocks' do subject['test'] = 'abc' value = nil subject.[]('test') { |v| value = v } expect(value).to eq 'abc' end it 'retrieves set values through blocks with method calls' do subject['test'] = 'abc' value = nil subject.test { |v| value = v } expect(value).to eq 'abc' end it 'tests for already set values when passed a ? method' do expect(subject.test?).to be_falsy subject.test = 'abc' expect(subject.test?).to be_truthy end it 'returns false on a ? method if a value has been set to nil or false' do subject.test = nil expect(subject).not_to be_test subject.test = false expect(subject).not_to be_test end it 'makes all [] and []= into strings for consistency' do subject['abc'] = 123 expect(subject.key?('abc')).to be_truthy expect(subject['abc']).to eq 123 end it 'has a to_s that is identical to its inspect' do subject.abc = 123 expect(subject.to_s).to eq subject.inspect end it 'returns nil instead of raising an error for attribute-esque method calls' do expect(subject.abc).to be_nil end it 'returns the default value if set like Hash' do subject.default = 123 expect(subject.abc).to eq 123 end it 'gracefully handles being accessed with arguments' do expect(subject.abc('foobar')).to eq nil subject.abc = 123 expect(subject.abc('foobar')).to eq 123 end # Added due to downstream gems assuming indifferent access to be true for Mash # When this is not, bump major version so that downstream gems can target # correct version and fix accordingly. # See https://github.com/intridea/hashie/pull/197 it 'maintains indifferent access when nested' do subject[:a] = { b: 'c' } expect(subject[:a][:b]).to eq 'c' expect(subject[:a]['b']).to eq 'c' end it 'returns a Hashie::Mash when passed a bang method to a non-existenct key' do expect(subject.abc!.is_a?(Hashie::Mash)).to be_truthy end it 'returns the existing value when passed a bang method for an existing key' do subject.name = 'Bob' expect(subject.name!).to eq 'Bob' end it 'returns a Hashie::Mash when passed an under bang method to a non-existenct key' do expect(subject.abc_.is_a?(Hashie::Mash)).to be_truthy end it 'returns the existing value when passed an under bang method for an existing key' do subject.name = 'Bob' expect(subject.name_).to eq 'Bob' end it '#initializing_reader returns a Hashie::Mash when passed a non-existent key' do expect(subject.initializing_reader(:abc).is_a?(Hashie::Mash)).to be_truthy end it 'allows for multi-level assignment through bang methods' do subject.author!.name = 'Michael Bleigh' expect(subject.author).to eq Hashie::Mash.new(name: 'Michael Bleigh') subject.author!.website!.url = 'http://www.mbleigh.com/' expect(subject.author.website).to eq Hashie::Mash.new(url: 'http://www.mbleigh.com/') end it 'allows for multi-level under bang testing' do expect(subject.author_.website_.url).to be_nil expect(subject.author_.website_.url?).to eq false expect(subject.author).to be_nil end it 'does not call super if id is not a key' do expect(subject.id).to eq nil end it 'returns the value if id is a key' do subject.id = 'Steve' expect(subject.id).to eq 'Steve' end it 'does not call super if type is not a key' do expect(subject.type).to eq nil end it 'returns the value if type is a key' do subject.type = 'Steve' expect(subject.type).to eq 'Steve' end context 'updating' do subject do described_class.new( first_name: 'Michael', last_name: 'Bleigh', details: { email: 'michael@asf.com', address: 'Nowhere road' }) end describe '#deep_update' do it 'recursively Hashie::Mash Hashie::Mashes and hashes together' do subject.deep_update(details: { email: 'michael@intridea.com', city: 'Imagineton' }) expect(subject.first_name).to eq 'Michael' expect(subject.details.email).to eq 'michael@intridea.com' expect(subject.details.address).to eq 'Nowhere road' expect(subject.details.city).to eq 'Imagineton' end it 'converts values only once' do class ConvertedMash < Hashie::Mash end rhs = ConvertedMash.new(email: 'foo@bar.com') expect(subject).to receive(:convert_value).exactly(1).times subject.deep_update(rhs) end it 'makes #update deep by default' do expect(subject.update(details: { address: 'Fake street' })).to eql(subject) expect(subject.details.address).to eq 'Fake street' expect(subject.details.email).to eq 'michael@asf.com' end it 'clones before a #deep_merge' do duped = subject.deep_merge(details: { address: 'Fake street' }) expect(duped).not_to eql(subject) expect(duped.details.address).to eq 'Fake street' expect(subject.details.address).to eq 'Nowhere road' expect(duped.details.email).to eq 'michael@asf.com' end it 'default #merge is deep' do duped = subject.merge(details: { email: 'michael@intridea.com' }) expect(duped).not_to eql(subject) expect(duped.details.email).to eq 'michael@intridea.com' expect(duped.details.address).to eq 'Nowhere road' end # http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-update it 'accepts a block' do duped = subject.merge(details: { address: 'Pasadena CA' }) { |_, oldv, newv| [oldv, newv].join(', ') } expect(duped.details.address).to eq 'Nowhere road, Pasadena CA' end it 'copies values for non-duplicate keys when a block is supplied' do duped = subject.merge(details: { address: 'Pasadena CA', state: 'West Thoughtleby' }) { |_, oldv, _| oldv } expect(duped.details.address).to eq 'Nowhere road' expect(duped.details.state).to eq 'West Thoughtleby' end end describe 'shallow update' do it 'shallowly Hashie::Mash Hashie::Mashes and hashes together' do expect(subject.shallow_update(details: { email: 'michael@intridea.com', city: 'Imagineton' })).to eql(subject) expect(subject.first_name).to eq 'Michael' expect(subject.details.email).to eq 'michael@intridea.com' expect(subject.details.address).to be_nil expect(subject.details.city).to eq 'Imagineton' end it 'clones before a #regular_merge' do duped = subject.shallow_merge(details: { address: 'Fake street' }) expect(duped).not_to eql(subject) end it 'default #merge is shallow' do duped = subject.shallow_merge(details: { address: 'Fake street' }) expect(duped.details.address).to eq 'Fake street' expect(subject.details.address).to eq 'Nowhere road' expect(duped.details.email).to be_nil end end describe '#replace' do before do subject.replace( middle_name: 'Cain', details: { city: 'Imagination' } ) end it 'returns self' do expect(subject.replace(foo: 'bar').to_hash).to eq('foo' => 'bar') end it 'sets all specified keys to their corresponding values' do expect(subject.middle_name?).to be_truthy expect(subject.details?).to be_truthy expect(subject.middle_name).to eq 'Cain' expect(subject.details.city?).to be_truthy expect(subject.details.city).to eq 'Imagination' end it 'leaves only specified keys' do expect(subject.keys.sort).to eq %w(details middle_name) expect(subject.first_name?).to be_falsy expect(subject).not_to respond_to(:first_name) expect(subject.last_name?).to be_falsy expect(subject).not_to respond_to(:last_name) end end describe 'delete' do it 'deletes with String key' do subject.delete('details') expect(subject.details).to be_nil expect(subject).not_to be_respond_to :details end it 'deletes with Symbol key' do subject.delete(:details) expect(subject.details).to be_nil expect(subject).not_to be_respond_to :details end end end it 'converts hash assignments into Hashie::Mashes' do subject.details = { email: 'randy@asf.com', address: { state: 'TX' } } expect(subject.details.email).to eq 'randy@asf.com' expect(subject.details.address.state).to eq 'TX' end it 'does not convert the type of Hashie::Mashes childs to Hashie::Mash' do class MyMash < Hashie::Mash end record = MyMash.new record.son = MyMash.new expect(record.son.class).to eq MyMash end it 'does not change the class of Mashes when converted' do class SubMash < Hashie::Mash end record = Hashie::Mash.new son = SubMash.new record['submash'] = son expect(record['submash']).to be_kind_of(SubMash) end it 'respects the class when passed a bang method for a non-existent key' do record = Hashie::Mash.new expect(record.non_existent!).to be_kind_of(Hashie::Mash) class SubMash < Hashie::Mash end son = SubMash.new expect(son.non_existent!).to be_kind_of(SubMash) end it 'respects the class when passed an under bang method for a non-existent key' do record = Hashie::Mash.new expect(record.non_existent_).to be_kind_of(Hashie::Mash) class SubMash < Hashie::Mash end son = SubMash.new expect(son.non_existent_).to be_kind_of(SubMash) end it 'respects the class when converting the value' do record = Hashie::Mash.new record.details = Hashie::Mash.new(email: 'randy@asf.com') expect(record.details).to be_kind_of(Hashie::Mash) end it 'respects another subclass when converting the value' do record = Hashie::Mash.new class SubMash < Hashie::Mash end son = SubMash.new(email: 'foo@bar.com') record.details = son expect(record.details).to be_kind_of(SubMash) end describe '#respond_to?' do subject do Hashie::Mash.new(abc: 'def') end it 'responds to a normal method' do expect(subject).to be_respond_to(:key?) end it 'responds to a set key' do expect(subject).to be_respond_to(:abc) expect(subject.method(:abc)).to_not be_nil end it 'responds to a set key with a suffix' do %w(= ? ! _).each do |suffix| expect(subject).to be_respond_to(:"abc#{suffix}") end end it 'is able to access the suffixed key as a method' do %w(= ? ! _).each do |suffix| expect(subject.method(:"abc#{suffix}")).to_not be_nil end end it 'responds to an unknown key with a suffix' do %w(= ? ! _).each do |suffix| expect(subject).to be_respond_to(:"xyz#{suffix}") end end it 'is able to access an unknown suffixed key as a method' do # See https://github.com/intridea/hashie/pull/285 for more information pending_for(engine: 'ruby', versions: %w(2.2.0 2.2.1 2.2.2)) %w(= ? ! _).each do |suffix| expect(subject.method(:"xyz#{suffix}")).to_not be_nil end end it 'does not respond to an unknown key without a suffix' do expect(subject).not_to be_respond_to(:xyz) expect { subject.method(:xyz) }.to raise_error(NameError) end end context '#initialize' do it 'converts an existing hash to a Hashie::Mash' do converted = Hashie::Mash.new(abc: 123, name: 'Bob') expect(converted.abc).to eq 123 expect(converted.name).to eq 'Bob' end it 'converts hashes recursively into Hashie::Mashes' do converted = Hashie::Mash.new(a: { b: 1, c: { d: 23 } }) expect(converted.a.is_a?(Hashie::Mash)).to be_truthy expect(converted.a.b).to eq 1 expect(converted.a.c.d).to eq 23 end it 'converts hashes in arrays into Hashie::Mashes' do converted = Hashie::Mash.new(a: [{ b: 12 }, 23]) expect(converted.a.first.b).to eq 12 expect(converted.a.last).to eq 23 end it 'converts an existing Hashie::Mash into a Hashie::Mash' do initial = Hashie::Mash.new(name: 'randy', address: { state: 'TX' }) copy = Hashie::Mash.new(initial) expect(initial.name).to eq copy.name expect(initial.__id__).not_to eq copy.__id__ expect(copy.address.state).to eq 'TX' copy.address.state = 'MI' expect(initial.address.state).to eq 'TX' expect(copy.address.__id__).not_to eq initial.address.__id__ end it 'accepts a default block' do initial = Hashie::Mash.new { |h, i| h[i] = [] } expect(initial.default_proc).not_to be_nil expect(initial.default).to be_nil expect(initial.test).to eq [] expect(initial.test?).to be_truthy end it 'allows assignment of an empty array in a default block' do initial = Hashie::Mash.new { |h, k| h[k] = [] } initial.hello << 100 expect(initial.hello).to eq [100] initial['hi'] << 100 expect(initial['hi']).to eq [100] end it 'allows assignment of a non-empty array in a default block' do initial = Hashie::Mash.new { |h, k| h[k] = [100] } initial.hello << 200 expect(initial.hello).to eq [100, 200] initial['hi'] << 200 expect(initial['hi']).to eq [100, 200] end it 'allows assignment of an empty hash in a default block' do initial = Hashie::Mash.new { |h, k| h[k] = {} } initial.hello[:a] = 100 expect(initial.hello).to eq Hashie::Mash.new(a: 100) initial[:hi][:a] = 100 expect(initial[:hi]).to eq Hashie::Mash.new(a: 100) end it 'allows assignment of a non-empty hash in a default block' do initial = Hashie::Mash.new { |h, k| h[k] = { a: 100 } } initial.hello[:b] = 200 expect(initial.hello).to eq Hashie::Mash.new(a: 100, b: 200) initial[:hi][:b] = 200 expect(initial[:hi]).to eq Hashie::Mash.new(a: 100, b: 200) end it 'converts Hashie::Mashes within Arrays back to Hashes' do initial_hash = { 'a' => [{ 'b' => 12, 'c' => ['d' => 50, 'e' => 51] }, 23] } converted = Hashie::Mash.new(initial_hash) expect(converted.to_hash['a'].first.is_a?(Hashie::Mash)).to be_falsy expect(converted.to_hash['a'].first.is_a?(Hash)).to be_truthy expect(converted.to_hash['a'].first['c'].first.is_a?(Hashie::Mash)).to be_falsy end end describe '#fetch' do let(:hash) { { one: 1, other: false } } let(:mash) { Hashie::Mash.new(hash) } context 'when key exists' do it 'returns the value' do expect(mash.fetch(:one)).to eql(1) end it 'returns the value even if the value is falsy' do expect(mash.fetch(:other)).to eql(false) end context 'when key has other than original but acceptable type' do it 'returns the value' do expect(mash.fetch('one')).to eql(1) end end end context 'when key does not exist' do it 'raises KeyError' do error = RUBY_VERSION =~ /1.8/ ? IndexError : KeyError expect { mash.fetch(:two) }.to raise_error(error) end context 'with default value given' do it 'returns default value' do expect(mash.fetch(:two, 8)).to eql(8) end it 'returns default value even if it is falsy' do expect(mash.fetch(:two, false)).to eql(false) end end context 'with block given' do it 'returns default value' do expect(mash.fetch(:two) do 'block default value' end).to eql('block default value') end end end end describe '#to_hash' do let(:hash) { { 'outer' => { 'inner' => 42 }, 'testing' => [1, 2, 3] } } let(:mash) { Hashie::Mash.new(hash) } it 'returns a standard Hash' do expect(mash.to_hash).to be_a(::Hash) end it 'includes all keys' do expect(mash.to_hash.keys).to eql(%w(outer testing)) end it 'converts keys to symbols when symbolize_keys option is true' do expect(mash.to_hash(symbolize_keys: true).keys).to include(:outer) expect(mash.to_hash(symbolize_keys: true).keys).not_to include('outer') end it 'leaves keys as strings when symbolize_keys option is false' do expect(mash.to_hash(symbolize_keys: false).keys).to include('outer') expect(mash.to_hash(symbolize_keys: false).keys).not_to include(:outer) end it 'symbolizes keys recursively' do expect(mash.to_hash(symbolize_keys: true)[:outer].keys).to include(:inner) expect(mash.to_hash(symbolize_keys: true)[:outer].keys).not_to include('inner') end end describe '#stringify_keys' do it 'turns all keys into strings recursively' do hash = Hashie::Mash[:a => 'hey', 123 => { 345 => 'hey' }] hash.stringify_keys! expect(hash).to eq Hashie::Hash['a' => 'hey', '123' => { '345' => 'hey' }] end end describe '#values_at' do let(:hash) { { 'key_one' => 1, :key_two => 2 } } let(:mash) { Hashie::Mash.new(hash) } context 'when the original type is given' do it 'returns the values' do expect(mash.values_at('key_one', :key_two)).to eq([1, 2]) end end context 'when a different, but acceptable type is given' do it 'returns the values' do expect(mash.values_at(:key_one, 'key_two')).to eq([1, 2]) end end context 'when a key is given that is not in the Mash' do it 'returns nil for that value' do expect(mash.values_at('key_one', :key_three)).to eq([1, nil]) end end end describe '.load(filename, options = {})' do let(:config) do { 'production' => { 'foo' => 'production_foo' } } end let(:path) { 'database.yml' } let(:parser) { double(:parser) } subject { described_class.load(path, parser: parser) } before do |ex| unless ex.metadata == :test_cache described_class.instance_variable_set('@_mashes', nil) # clean the cached mashes end end context 'if the file exists' do before do expect(File).to receive(:file?).with(path).and_return(true) expect(parser).to receive(:perform).with(path).and_return(config) end it { is_expected.to be_a(Hashie::Mash) } it 'return a Mash from a file' do expect(subject.production).not_to be_nil expect(subject.production.keys).to eq config['production'].keys expect(subject.production.foo).to eq config['production']['foo'] end it 'freeze the attribtues' do expect { subject.production = {} }.to raise_exception(RuntimeError, /can't modify frozen/) end end context 'if the fils does not exists' do before do expect(File).to receive(:file?).with(path).and_return(false) end it 'raise an ArgumentError' do expect { subject }.to raise_exception(ArgumentError) end end describe 'results are cached' do let(:parser) { double(:parser) } subject { described_class.load(path, parser: parser) } before do expect(File).to receive(:file?).with(path).and_return(true) expect(File).to receive(:file?).with("#{path}+1").and_return(true) expect(parser).to receive(:perform).once.with(path).and_return(config) expect(parser).to receive(:perform).once.with("#{path}+1").and_return(config) end it 'cache the loaded yml file', :test_cache do 2.times do expect(subject).to be_a(described_class) expect(described_class.load("#{path}+1", parser: parser)).to be_a(described_class) end expect(subject.object_id).to eq subject.object_id end end end describe '#to_module(mash_method_name)' do let(:mash) { described_class.new } subject { Class.new.extend mash.to_module } it 'defines a settings method on the klass class that extends the module' do expect(subject).to respond_to(:settings) expect(subject.settings).to eq mash end context 'when a settings_method_name is set' do let(:mash_method_name) { 'config' } subject { Class.new.extend mash.to_module(mash_method_name) } it 'defines a settings method on the klass class that extends the module' do expect(subject).to respond_to(mash_method_name.to_sym) expect(subject.send(mash_method_name.to_sym)).to eq mash end end end describe '#extractable_options?' do require 'active_support' subject { described_class.new(name: 'foo') } let(:args) { [101, 'bar', subject] } it 'can be extracted from an array' do expect(args.extract_options!).to eq subject expect(args).to eq [101, 'bar'] end end describe '#reverse_merge' do subject { described_class.new(a: 1, b: 2) } it 'unifies strings and symbols' do expect(subject.reverse_merge(a: 2).length).to eq 2 expect(subject.reverse_merge('a' => 2).length).to eq 2 end it 'does not overwrite values' do expect(subject.reverse_merge(a: 5).a).to eq subject.a end end end hashie-3.4.3/spec/hashie/version_spec.rb0000644000004100000410000000016612620101230020217 0ustar www-datawww-datarequire 'spec_helper' describe Hashie do it 'has a version' do expect(Hashie::VERSION).not_to be_nil end end hashie-3.4.3/spec/hashie/parsers/0000755000004100000410000000000012620101230016647 5ustar www-datawww-datahashie-3.4.3/spec/hashie/parsers/yaml_erb_parser_spec.rb0000644000004100000410000000115412620101230023355 0ustar www-datawww-datarequire 'spec_helper' describe Hashie::Extensions::Parsers::YamlErbParser do describe '.perform' do let(:config) do <<-EOF --- foo: verbatim bar: <%= "erb" %> baz: "<%= __FILE__ %>" EOF end let(:path) { 'template.yml' } subject { described_class.new(path).perform } before do expect(File).to receive(:read).with(path).and_return(config) end it { is_expected.to be_a(Hash) } it 'parses YAML after interpolating ERB' do expect(subject['foo']).to eq 'verbatim' expect(subject['bar']).to eq 'erb' expect(subject['baz']).to eq path end end end hashie-3.4.3/spec/hashie/clash_spec.rb0000644000004100000410000000263512620101230017627 0ustar www-datawww-datarequire 'spec_helper' describe Hashie::Clash do subject { Hashie::Clash.new } it 'is able to set an attribute via method_missing' do subject.foo('bar') expect(subject[:foo]).to eq 'bar' end it 'is able to set multiple attributes' do subject.foo('bar').baz('wok') expect(subject).to eq(foo: 'bar', baz: 'wok') end it 'converts multiple arguments into an array' do subject.foo(1, 2, 3) expect(subject[:foo]).to eq [1, 2, 3] end it 'is able to use bang notation to create a new Clash on a key' do subject.foo! expect(subject[:foo]).to be_kind_of(Hashie::Clash) end it 'is able to chain onto the new Clash when using bang notation' do subject.foo!.bar('abc').baz(123) expect(subject).to eq(foo: { bar: 'abc', baz: 123 }) end it 'is able to jump back up to the parent in the chain with #_end!' do subject.foo!.bar('abc')._end!.baz(123) expect(subject).to eq(foo: { bar: 'abc' }, baz: 123) end it 'merges rather than replaces existing keys' do subject.where(abc: 'def').where(hgi: 123) expect(subject).to eq(where: { abc: 'def', hgi: 123 }) end it 'is able to replace all of its own keys with #replace' do subject.foo(:bar).hello(:world) expect(subject.replace(baz: 123, hgi: 123)).to eq(baz: 123, hgi: 123) expect(subject).to eq(baz: 123, hgi: 123) expect(subject[:foo]).to be_nil expect(subject[:hello]).to be_nil end end hashie-3.4.3/spec/hashie/extensions/0000755000004100000410000000000012620101230017367 5ustar www-datawww-datahashie-3.4.3/spec/hashie/extensions/indifferent_access_spec.rb0000644000004100000410000001366412620101230024556 0ustar www-datawww-datarequire 'spec_helper' describe Hashie::Extensions::IndifferentAccess do class IndifferentHashWithMergeInitializer < Hash include Hashie::Extensions::MergeInitializer include Hashie::Extensions::IndifferentAccess class << self alias_method :build, :new end end class IndifferentHashWithArrayInitializer < Hash include Hashie::Extensions::IndifferentAccess class << self alias_method :build, :[] end end class IndifferentHashWithTryConvertInitializer < Hash include Hashie::Extensions::IndifferentAccess class << self alias_method :build, :try_convert end end class IndifferentHashWithDash < Hashie::Dash include Hashie::Extensions::IndifferentAccess property :foo end describe 'when included in dash' do let(:params) { { foo: 'bar' } } subject { IndifferentHashWithDash.new(params) } it 'initialize with a symbol' do expect(subject.foo).to eq params[:foo] end end shared_examples_for 'hash with indifferent access' do it 'is able to access via string or symbol' do h = subject.build(abc: 123) expect(h[:abc]).to eq 123 expect(h['abc']).to eq 123 end describe '#values_at' do it 'indifferently finds values' do h = subject.build(:foo => 'bar', 'baz' => 'qux') expect(h.values_at('foo', :baz)).to eq %w(bar qux) end it 'returns the same instance of the hash that was set' do hash = Hash.new h = subject.build(foo: hash) expect(h.values_at(:foo)[0]).to be(hash) end it 'returns the same instance of the array that was set' do array = Array.new h = subject.build(foo: array) expect(h.values_at(:foo)[0]).to be(array) end it 'returns the same instance of the string that was set' do str = 'my string' h = subject.build(foo: str) expect(h.values_at(:foo)[0]).to be(str) end it 'returns the same instance of the object that was set' do object = Object.new h = subject.build(foo: object) expect(h.values_at(:foo)[0]).to be(object) end end describe '#fetch' do it 'works like normal fetch, but indifferent' do h = subject.build(foo: 'bar') expect(h.fetch(:foo)).to eq h.fetch('foo') expect(h.fetch(:foo)).to eq 'bar' end it 'returns the same instance of the hash that was set' do hash = Hash.new h = subject.build(foo: hash) expect(h.fetch(:foo)).to be(hash) end it 'returns the same instance of the array that was set' do array = Array.new h = subject.build(foo: array) expect(h.fetch(:foo)).to be(array) end it 'returns the same instance of the string that was set' do str = 'my string' h = subject.build(foo: str) expect(h.fetch(:foo)).to be(str) end it 'returns the same instance of the object that was set' do object = Object.new h = subject.build(foo: object) expect(h.fetch(:foo)).to be(object) end it 'yields with key name if key does not exists' do h = subject.build(a: 0) expect(h.fetch(:foo) { |key| ['default for', key] }).to eq ['default for', 'foo'] end end describe '#delete' do it 'deletes indifferently' do h = subject.build(:foo => 'bar', 'baz' => 'qux') h.delete('foo') h.delete(:baz) expect(h).to be_empty end end describe '#key?' do let(:h) { subject.build(foo: 'bar') } it 'finds it indifferently' do expect(h).to be_key(:foo) expect(h).to be_key('foo') end %w(include? member? has_key?).each do |key_alias| it "is aliased as #{key_alias}" do expect(h.send(key_alias.to_sym, :foo)).to be(true) expect(h.send(key_alias.to_sym, 'foo')).to be(true) end end end describe '#update' do let(:h) { subject.build(foo: 'bar') } it 'allows keys to be indifferent still' do h.update(baz: 'qux') expect(h['foo']).to eq 'bar' expect(h['baz']).to eq 'qux' end it 'recursively injects indifference into sub-hashes' do h.update(baz: { qux: 'abc' }) expect(h['baz']['qux']).to eq 'abc' end it 'does not change the ancestors of the injected object class' do h.update(baz: { qux: 'abc' }) expect(Hash.new).not_to be_respond_to(:indifferent_access?) end end describe '#replace' do let(:h) { subject.build(foo: 'bar').replace(bar: 'baz', hi: 'bye') } it 'returns self' do expect(h).to be_a(subject) end it 'removes old keys' do [:foo, 'foo'].each do |k| expect(h[k]).to be_nil expect(h.key?(k)).to be_falsy end end it 'creates new keys with indifferent access' do [:bar, 'bar', :hi, 'hi'].each { |k| expect(h.key?(k)).to be_truthy } expect(h[:bar]).to eq 'baz' expect(h['bar']).to eq 'baz' expect(h[:hi]).to eq 'bye' expect(h['hi']).to eq 'bye' end end describe '#try_convert' do describe 'with conversion' do let(:h) { subject.try_convert(foo: 'bar') } it 'is a subject' do expect(h).to be_a(subject) end end describe 'without conversion' do let(:h) { subject.try_convert('{ :foo => bar }') } it 'is nil' do expect(h).to be_nil end end end end describe 'with merge initializer' do subject { IndifferentHashWithMergeInitializer } it_should_behave_like 'hash with indifferent access' end describe 'with array initializer' do subject { IndifferentHashWithArrayInitializer } it_should_behave_like 'hash with indifferent access' end describe 'with try convert initializer' do subject { IndifferentHashWithTryConvertInitializer } it_should_behave_like 'hash with indifferent access' end end hashie-3.4.3/spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb0000644000004100000410000001407412620101230030007 0ustar www-datawww-data# This set of tests verifies that Hashie::Extensions::IndifferentAccess works with # ActiveSupport HashWithIndifferentAccess hashes. See #164 and #166 for details. require 'active_support/hash_with_indifferent_access' require 'active_support/core_ext/hash' require 'spec_helper' describe Hashie::Extensions::IndifferentAccess do class IndifferentHashWithMergeInitializer < Hash include Hashie::Extensions::MergeInitializer include Hashie::Extensions::IndifferentAccess class << self alias_method :build, :new end end class IndifferentHashWithArrayInitializer < Hash include Hashie::Extensions::IndifferentAccess class << self alias_method :build, :[] end end class IndifferentHashWithTryConvertInitializer < Hash include Hashie::Extensions::IndifferentAccess class << self alias_method :build, :try_convert end end class CoercableHash < Hash include Hashie::Extensions::Coercion include Hashie::Extensions::MergeInitializer end class MashWithIndifferentAccess < Hashie::Mash include Hashie::Extensions::IndifferentAccess end shared_examples_for 'hash with indifferent access' do it 'is able to access via string or symbol' do indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(abc: 123) h = subject.build(indifferent_hash) expect(h[:abc]).to eq 123 expect(h['abc']).to eq 123 end describe '#values_at' do it 'indifferently finds values' do indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new( :foo => 'bar', 'baz' => 'qux' ) h = subject.build(indifferent_hash) expect(h.values_at('foo', :baz)).to eq %w(bar qux) end end describe '#fetch' do it 'works like normal fetch, but indifferent' do indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar') h = subject.build(indifferent_hash) expect(h.fetch(:foo)).to eq h.fetch('foo') expect(h.fetch(:foo)).to eq 'bar' end end describe '#delete' do it 'deletes indifferently' do indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new( :foo => 'bar', 'baz' => 'qux' ) h = subject.build(indifferent_hash) h.delete('foo') h.delete(:baz) expect(h).to be_empty end end describe '#key?' do let(:h) do indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar') subject.build(indifferent_hash) end it 'finds it indifferently' do expect(h).to be_key(:foo) expect(h).to be_key('foo') end %w(include? member? has_key?).each do |key_alias| it "is aliased as #{key_alias}" do expect(h.send(key_alias.to_sym, :foo)).to be(true) expect(h.send(key_alias.to_sym, 'foo')).to be(true) end end end describe '#update' do let(:h) do indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar') subject.build(indifferent_hash) end it 'allows keys to be indifferent still' do h.update(baz: 'qux') expect(h['foo']).to eq 'bar' expect(h['baz']).to eq 'qux' end it 'recursively injects indifference into sub-hashes' do h.update(baz: { qux: 'abc' }) expect(h['baz']['qux']).to eq 'abc' end it 'does not change the ancestors of the injected object class' do h.update(baz: { qux: 'abc' }) expect(Hash.new).not_to be_respond_to(:indifferent_access?) end end describe '#replace' do let(:h) do indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar') subject.build(indifferent_hash).replace(bar: 'baz', hi: 'bye') end it 'returns self' do expect(h).to be_a(subject) end it 'removes old keys' do [:foo, 'foo'].each do |k| expect(h[k]).to be_nil expect(h.key?(k)).to be_falsy end end it 'creates new keys with indifferent access' do [:bar, 'bar', :hi, 'hi'].each { |k| expect(h.key?(k)).to be_truthy } expect(h[:bar]).to eq 'baz' expect(h['bar']).to eq 'baz' expect(h[:hi]).to eq 'bye' expect(h['hi']).to eq 'bye' end end describe '#try_convert' do describe 'with conversion' do let(:h) do indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar') subject.try_convert(indifferent_hash) end it 'is a subject' do expect(h).to be_a(subject) end end describe 'without conversion' do let(:h) { subject.try_convert('{ :foo => bar }') } it 'is nil' do expect(h).to be_nil end end end end describe 'with merge initializer' do subject { IndifferentHashWithMergeInitializer } it_should_behave_like 'hash with indifferent access' end describe 'with array initializer' do subject { IndifferentHashWithArrayInitializer } it_should_behave_like 'hash with indifferent access' end describe 'with try convert initializer' do subject { IndifferentHashWithTryConvertInitializer } it_should_behave_like 'hash with indifferent access' end describe 'with coercion' do subject { CoercableHash } let(:instance) { subject.new } it 'supports coercion for ActiveSupport::HashWithIndifferentAccess' do subject.coerce_key :foo, ActiveSupport::HashWithIndifferentAccess.new(Coercable => Coercable) instance[:foo] = { 'bar_key' => 'bar_value', 'bar2_key' => 'bar2_value' } expect(instance[:foo].keys).to all(be_coerced) expect(instance[:foo].values).to all(be_coerced) expect(instance[:foo]).to be_a(ActiveSupport::HashWithIndifferentAccess) end end describe 'Mash with indifferent access' do it 'is able to be created for a deep nested HashWithIndifferentAccess' do indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(abc: { def: 123 }) MashWithIndifferentAccess.new(indifferent_hash) end end end hashie-3.4.3/spec/hashie/extensions/ignore_undeclared_spec.rb0000644000004100000410000000276712620101230024413 0ustar www-datawww-datarequire 'spec_helper' describe Hashie::Extensions::IgnoreUndeclared do context 'included in Trash' do class ForgivingTrash < Hashie::Trash include Hashie::Extensions::IgnoreUndeclared property :city property :state, from: :provence end subject { ForgivingTrash } it 'silently ignores undeclared properties on initialization' do expect { subject.new(city: 'Toronto', provence: 'ON', country: 'Canada') }.to_not raise_error end it 'works with translated properties (with symbol keys)' do expect(subject.new(provence: 'Ontario').state).to eq('Ontario') end it 'works with translated properties (with string keys)' do expect(subject.new(provence: 'Ontario').state).to eq('Ontario') end it 'requires properties to be declared on assignment' do hash = subject.new(city: 'Toronto') expect { hash.country = 'Canada' }.to raise_error(NoMethodError) end end context 'combined with DeepMerge' do class ForgivingTrashWithMerge < Hashie::Trash include Hashie::Extensions::DeepMerge include Hashie::Extensions::IgnoreUndeclared property :some_key end it 'deep merges' do class ForgivingTrashWithMergeAndProperty < ForgivingTrashWithMerge property :some_other_key end hash = ForgivingTrashWithMergeAndProperty.new(some_ignored_key: 17, some_key: 12) expect(hash.deep_merge(some_other_key: 55, some_ignored_key: 18)).to eq(some_key: 12, some_other_key: 55) end end end hashie-3.4.3/spec/hashie/extensions/symbolize_keys_spec.rb0000644000004100000410000000635312620101230024005 0ustar www-datawww-datarequire 'spec_helper' require 'support/module_context' def invoke(method) if subject == object subject.public_send(method) else subject.public_send(method, object) end end shared_examples 'symbolize_keys!' do it 'converts keys to symbols' do object['abc'] = 'abc' object['def'] = 'def' invoke :symbolize_keys! expect((object.keys & [:abc, :def]).size).to eq 2 end it 'converts nested instances of the same class' do object['ab'] = dummy_class.new object['ab']['cd'] = dummy_class.new object['ab']['cd']['ef'] = 'abcdef' invoke :symbolize_keys! expect(object).to eq(ab: { cd: { ef: 'abcdef' } }) end it 'converts nested hashes' do object['ab'] = { 'cd' => { 'ef' => 'abcdef' } } invoke :symbolize_keys! expect(object).to eq(ab: { cd: { ef: 'abcdef' } }) end it 'performs deep conversion within nested arrays' do object['ab'] = [] object['ab'] << dummy_class.new object['ab'] << dummy_class.new object['ab'][0]['cd'] = 'abcd' object['ab'][1]['ef'] = 'abef' new_object = invoke :symbolize_keys expect(new_object).to eq(ab: [{ cd: 'abcd' }, { ef: 'abef' }]) end end shared_examples 'symbolize_keys' do it 'converts keys to symbols' do object['abc'] = 'def' copy = invoke :symbolize_keys expect(copy[:abc]).to eq 'def' end it 'does not alter the original' do object['abc'] = 'def' copy = invoke :symbolize_keys expect(object.keys).to eq ['abc'] expect(copy.keys).to eq [:abc] end end describe Hashie::Extensions::SymbolizeKeys do include_context 'included hash module' let(:object) { subject } describe '#symbolize_keys!' do include_examples 'symbolize_keys!' let(:object) { subject } it 'returns itself' do expect(subject.symbolize_keys!).to eq subject end end describe '#symbolize_keys' do include_examples 'symbolize_keys' end context 'class methods' do subject { described_class } let(:object) { Hash.new } describe '.symbolize_keys' do include_examples 'symbolize_keys' end describe '.symbolize_keys!' do include_examples 'symbolize_keys!' end end context 'singleton methods' do subject { Hash } let(:object) { subject.new.merge('a' => 1, 'b' => { 'c' => 2 }).extend(Hashie::Extensions::SymbolizeKeys) } let(:expected_hash) { { a: 1, b: { c: 2 } } } describe '.symbolize_keys' do it 'does not raise error' do expect { object.symbolize_keys }.not_to raise_error end it 'produces expected symbolized hash' do expect(object.symbolize_keys).to eq(expected_hash) end end describe '.symbolize_keys!' do it 'does not raise error' do expect { object.symbolize_keys! }.not_to raise_error end it 'produces expected symbolized hash' do expect(object.symbolize_keys!).to eq(expected_hash) end end end end describe Hashie do let!(:dummy_class) do klass = Class.new(::Hash) klass.send :include, Hashie::Extensions::StringifyKeys klass end subject { described_class } let(:object) { Hash.new } describe '.symbolize_keys' do include_examples 'symbolize_keys' end describe '.symbolize_keys!' do include_examples 'symbolize_keys!' end end hashie-3.4.3/spec/hashie/extensions/autoload_spec.rb0000644000004100000410000000213412620101230022536 0ustar www-datawww-datarequire 'spec_helper' require 'hashie' describe Hashie::Extensions do describe 'autloads constants' do it { is_expected.to be_const_defined(:MethodAccess) } it { is_expected.to be_const_defined(:Coercion) } it { is_expected.to be_const_defined(:DeepMerge) } it { is_expected.to be_const_defined(:IgnoreUndeclared) } it { is_expected.to be_const_defined(:IndifferentAccess) } it { is_expected.to be_const_defined(:MergeInitializer) } it { is_expected.to be_const_defined(:MethodAccess) } it { is_expected.to be_const_defined(:MethodQuery) } it { is_expected.to be_const_defined(:MethodReader) } it { is_expected.to be_const_defined(:MethodWriter) } it { is_expected.to be_const_defined(:StringifyKeys) } it { is_expected.to be_const_defined(:SymbolizeKeys) } it { is_expected.to be_const_defined(:DeepFetch) } it { is_expected.to be_const_defined(:DeepFind) } it { is_expected.to be_const_defined(:PrettyInspect) } it { is_expected.to be_const_defined(:KeyConversion) } it { is_expected.to be_const_defined(:MethodAccessWithOverride) } end end hashie-3.4.3/spec/hashie/extensions/mash/0000755000004100000410000000000012620101230020317 5ustar www-datawww-datahashie-3.4.3/spec/hashie/extensions/mash/safe_assignment_spec.rb0000644000004100000410000000244512620101230025031 0ustar www-datawww-datarequire 'spec_helper' describe Hashie::Extensions::Mash::SafeAssignment do class MashWithSafeAssignment < Hashie::Mash include Hashie::Extensions::Mash::SafeAssignment private def my_own_private :hello! end end context 'when included in Mash' do subject { MashWithSafeAssignment.new } context 'when not attempting to override a method' do it 'assigns just fine' do expect do subject.blabla = 'Test' subject.blabla = 'Test' end.to_not raise_error end end context 'when attempting to override a method' do it 'raises an error' do expect { subject.zip = 'Test' }.to raise_error(ArgumentError) end end context 'when attempting to override a private method' do it 'raises an error' do expect { subject.my_own_private = 'Test' }.to raise_error(ArgumentError) end end context 'when attempting to initialize with predefined method' do it 'raises an error' do expect { MashWithSafeAssignment.new(zip: true) }.to raise_error(ArgumentError) end end context 'when setting as a hash key' do it 'still raises if conflicts with a method' do expect { subject[:zip] = 'Test' }.to raise_error(ArgumentError) end end end end hashie-3.4.3/spec/hashie/extensions/deep_locate_spec.rb0000644000004100000410000000566412620101230023205 0ustar www-datawww-datarequire 'spec_helper' describe Hashie::Extensions::DeepLocate do let(:hash) do { from: 0, size: 25, query: { bool: { must: [ { query_string: { query: 'foobar', default_operator: 'AND', fields: [ 'title^2', '_all' ] } }, { match: { field_1: 'value_1' } }, { range: { lsr09: { gte: 2014 } } } ], should: [ { match: { field_2: 'value_2' } } ], must_not: [ { range: { lsr10: { gte: 2014 } } } ] } } } end describe '.deep_locate' do context 'if called with a non-callable comparator' do it 'creates a key comparator on-th-fly' do expect(described_class.deep_locate(:lsr10, hash)).to eq([hash[:query][:bool][:must_not][0][:range]]) end end it 'locates enumerables for which the given comparator returns true for at least one element' do examples = [ [ ->(key, _value, _object) { key == :fields }, [ hash[:query][:bool][:must].first[:query_string] ] ], [ ->(_key, value, _object) { value.is_a?(String) && value.include?('value') }, [ hash[:query][:bool][:must][1][:match], hash[:query][:bool][:should][0][:match] ] ], [ lambda do |_key, _value, object| object.is_a?(Array) && !object.extend(described_class).deep_locate(:match).empty? end, [ hash[:query][:bool][:must], hash[:query][:bool][:should] ] ] ] examples.each do |comparator, expected_result| expect(described_class.deep_locate(comparator, hash)).to eq(expected_result) end end it 'returns an empty array if nothing was found' do expect(described_class.deep_locate(:muff, foo: 'bar')).to eq([]) end end context 'if extending an existing object' do let(:extended_hash) do hash.extend(described_class) end it 'adds #deep_locate' do expect(extended_hash.deep_locate(:bool)).to eq([hash[:query]]) end end context 'if included in a hash' do let(:derived_hash_with_extension_included) do Class.new(Hash) do include Hashie::Extensions::DeepLocate end end let(:instance) do derived_hash_with_extension_included.new.update(hash) end it 'adds #deep_locate' do expect(instance.deep_locate(:bool)).to eq([hash[:query]]) end end end hashie-3.4.3/spec/hashie/extensions/coercion_spec.rb0000644000004100000410000004465012620101230022540 0ustar www-datawww-datarequire 'spec_helper' describe Hashie::Extensions::Coercion do class NotInitializable private_class_method :new end class Initializable attr_reader :coerced, :value def initialize(obj, coerced = nil) @coerced = coerced @value = obj.class.to_s end def coerced? !@coerced.nil? end end class Coercable < Initializable def self.coerce(obj) new(obj, true) end end before(:each) do class ExampleCoercableHash < Hash include Hashie::Extensions::Coercion include Hashie::Extensions::MergeInitializer end end subject { ExampleCoercableHash } let(:instance) { subject.new } describe '#coerce_key' do context 'nesting' do class BaseCoercableHash < Hash include Hashie::Extensions::Coercion include Hashie::Extensions::MergeInitializer end class NestedCoercableHash < BaseCoercableHash coerce_key :foo, String coerce_key :bar, Integer end class OtherNestedCoercableHash < BaseCoercableHash coerce_key :foo, Symbol end class RootCoercableHash < BaseCoercableHash coerce_key :nested, NestedCoercableHash coerce_key :other, OtherNestedCoercableHash coerce_key :nested_list, Array[NestedCoercableHash] coerce_key :nested_hash, Hash[String => NestedCoercableHash] end def test_nested_object(obj) expect(obj).to be_a(NestedCoercableHash) expect(obj[:foo]).to be_a(String) expect(obj[:bar]).to be_an(Integer) end subject { RootCoercableHash } let(:instance) { subject.new } it 'does not add coercions to superclass' do instance[:nested] = { foo: 'bar' } instance[:other] = { foo: 'bar' } expect(instance[:nested][:foo]).to be_a String expect(instance[:other][:foo]).to be_a Symbol end it 'coerces nested objects' do instance[:nested] = { foo: 123, bar: '456' } test_nested_object(instance[:nested]) end it 'coerces nested arrays' do instance[:nested_list] = [ { foo: 123, bar: '456' }, { foo: 234, bar: '567' }, { foo: 345, bar: '678' } ] expect(instance[:nested_list]).to be_a Array expect(instance[:nested_list].size).to eq(3) instance[:nested_list].each do | nested | test_nested_object nested end end it 'coerces nested hashes' do instance[:nested_hash] = { a: { foo: 123, bar: '456' }, b: { foo: 234, bar: '567' }, c: { foo: 345, bar: '678' } } expect(instance[:nested_hash]).to be_a Hash expect(instance[:nested_hash].size).to eq(3) instance[:nested_hash].each do | key, nested | expect(key).to be_a(String) test_nested_object nested end end context 'when repetitively including the module' do class RepetitiveCoercableHash < NestedCoercableHash include Hashie::Extensions::Coercion include Hashie::Extensions::MergeInitializer coerce_key :nested, NestedCoercableHash end subject { RepetitiveCoercableHash } let(:instance) { subject.new } it 'does not raise a stack overflow error' do expect do instance[:nested] = { foo: 123, bar: '456' } test_nested_object(instance[:nested]) end.not_to raise_error end end end it { expect(subject).to be_respond_to(:coerce_key) } it 'runs through coerce on a specified key' do subject.coerce_key :foo, Coercable instance[:foo] = 'bar' expect(instance[:foo]).to be_coerced end it 'skips unnecessary coercions' do subject.coerce_key :foo, Coercable instance[:foo] = Coercable.new('bar') expect(instance[:foo]).to_not be_coerced end it 'supports an array of keys' do subject.coerce_keys :foo, :bar, Coercable instance[:foo] = 'bar' instance[:bar] = 'bax' expect(instance[:foo]).to be_coerced expect(instance[:bar]).to be_coerced end it 'supports coercion for Array' do subject.coerce_key :foo, Array[Coercable] instance[:foo] = %w('bar', 'bar2') expect(instance[:foo]).to all(be_coerced) expect(instance[:foo]).to be_a(Array) end it 'supports coercion for Set' do subject.coerce_key :foo, Set[Coercable] instance[:foo] = Set.new(%w('bar', 'bar2')) expect(instance[:foo]).to all(be_coerced) expect(instance[:foo]).to be_a(Set) end it 'supports coercion for Set of primitive' do subject.coerce_key :foo, Set[Initializable] instance[:foo] = %w('bar', 'bar2') expect(instance[:foo].map(&:value)).to all(eq 'String') expect(instance[:foo]).to be_none(&:coerced?) expect(instance[:foo]).to be_a(Set) end it 'supports coercion for Hash' do subject.coerce_key :foo, Hash[Coercable => Coercable] instance[:foo] = { 'bar_key' => 'bar_value', 'bar2_key' => 'bar2_value' } expect(instance[:foo].keys).to all(be_coerced) expect(instance[:foo].values).to all(be_coerced) expect(instance[:foo]).to be_a(Hash) end it 'supports coercion for Hash with primitive as value' do subject.coerce_key :foo, Hash[Coercable => Initializable] instance[:foo] = { 'bar_key' => '1', 'bar2_key' => '2' } expect(instance[:foo].values.map(&:value)).to all(eq 'String') expect(instance[:foo].keys).to all(be_coerced) end context 'coercing core types' do def test_coercion(literal, target_type, coerce_method) subject.coerce_key :foo, target_type instance[:foo] = literal expect(instance[:foo]).to be_a(target_type) expect(instance[:foo]).to eq(literal.send(coerce_method)) end RSpec.shared_examples 'coerces from numeric types' do |target_type, coerce_method| it "coerces from String to #{target_type} via #{coerce_method}" do test_coercion '2.0', target_type, coerce_method end it "coerces from Integer to #{target_type} via #{coerce_method}" do # Fixnum test_coercion 2, target_type, coerce_method # Bignum test_coercion 12_345_667_890_987_654_321, target_type, coerce_method end it "coerces from Rational to #{target_type} via #{coerce_method}" do test_coercion Rational(2, 3), target_type, coerce_method end end RSpec.shared_examples 'coerces from alphabetical types' do |target_type, coerce_method| it "coerces from String to #{target_type} via #{coerce_method}" do test_coercion 'abc', target_type, coerce_method end it "coerces from Symbol to #{target_type} via #{coerce_method}" do test_coercion :abc, target_type, coerce_method end end include_examples 'coerces from numeric types', Integer, :to_i include_examples 'coerces from numeric types', Float, :to_f include_examples 'coerces from numeric types', String, :to_s include_examples 'coerces from alphabetical types', String, :to_s include_examples 'coerces from alphabetical types', Symbol, :to_sym it 'can coerce String to Rational when possible' do test_coercion '2/3', Rational, :to_r end it 'can coerce String to Complex when possible' do test_coercion '2/3+3/4i', Complex, :to_c end it 'coerces collections with core types' do subject.coerce_key :foo, Hash[String => String] instance[:foo] = { abc: 123, xyz: 987 } expect(instance[:foo]).to eq( 'abc' => '123', 'xyz' => '987' ) end it 'can coerce via a proc' do subject.coerce_key(:foo, lambda do |v| case v when String return !!(v =~ /^(true|t|yes|y|1)$/i) when Numeric return !v.to_i.zero? else return v == true end end) true_values = [true, 'true', 't', 'yes', 'y', '1', 1, -1] false_values = [false, 'false', 'f', 'no', 'n', '0', 0] true_values.each do |v| instance[:foo] = v expect(instance[:foo]).to be_a(TrueClass) end false_values.each do |v| instance[:foo] = v expect(instance[:foo]).to be_a(FalseClass) end end it 'raises errors for non-coercable types' do subject.coerce_key :foo, NotInitializable expect { instance[:foo] = 'true' }.to raise_error(Hashie::CoercionError, /NotInitializable is not a coercable type/) end it 'can coerce false' do subject.coerce_key :foo, Coercable instance[:foo] = false expect(instance[:foo]).to be_coerced expect(instance[:foo].value).to eq('FalseClass') end it 'does not coerce nil' do subject.coerce_key :foo, String instance[:foo] = nil expect(instance[:foo]).to_not eq('') expect(instance[:foo]).to be_nil end end it 'calls #new if no coerce method is available' do subject.coerce_key :foo, Initializable instance[:foo] = 'bar' expect(instance[:foo].value).to eq 'String' expect(instance[:foo]).not_to be_coerced end it 'coerces when the merge initializer is used' do subject.coerce_key :foo, Coercable instance = subject.new(foo: 'bar') expect(instance[:foo]).to be_coerced end context 'when #replace is used' do before { subject.coerce_key :foo, :bar, Coercable } let(:instance) do subject.new(foo: 'bar').replace(foo: 'foz', bar: 'baz', hi: 'bye') end it 'coerces relevant keys' do expect(instance[:foo]).to be_coerced expect(instance[:bar]).to be_coerced expect(instance[:hi]).not_to respond_to(:coerced?) end it 'sets correct values' do expect(instance[:hi]).to eq 'bye' end end context 'when used with a Mash' do class UserMash < Hashie::Mash end class TweetMash < Hashie::Mash include Hashie::Extensions::Coercion coerce_key :user, UserMash end it 'coerces with instance initialization' do tweet = TweetMash.new(user: { email: 'foo@bar.com' }) expect(tweet[:user]).to be_a(UserMash) end it 'coerces when setting with attribute style' do tweet = TweetMash.new tweet.user = { email: 'foo@bar.com' } expect(tweet[:user]).to be_a(UserMash) end it 'coerces when setting with string index' do tweet = TweetMash.new tweet['user'] = { email: 'foo@bar.com' } expect(tweet[:user]).to be_a(UserMash) end it 'coerces when setting with symbol index' do tweet = TweetMash.new tweet[:user] = { email: 'foo@bar.com' } expect(tweet[:user]).to be_a(UserMash) end end context 'when used with a Trash' do class UserTrash < Hashie::Trash property :email end class TweetTrash < Hashie::Trash include Hashie::Extensions::Coercion property :user, from: :user_data coerce_key :user, UserTrash end it 'coerces with instance initialization' do tweet = TweetTrash.new(user_data: { email: 'foo@bar.com' }) expect(tweet[:user]).to be_a(UserTrash) end end context 'when used with IndifferentAccess to coerce a Mash' do class MyHash < Hash include Hashie::Extensions::Coercion include Hashie::Extensions::IndifferentAccess include Hashie::Extensions::MergeInitializer end class UserHash < MyHash end class TweetHash < MyHash coerce_key :user, UserHash end it 'coerces with instance initialization' do tweet = TweetHash.new(user: Hashie::Mash.new(email: 'foo@bar.com')) expect(tweet[:user]).to be_a(UserHash) end it 'coerces when setting with string index' do tweet = TweetHash.new tweet['user'] = Hashie::Mash.new(email: 'foo@bar.com') expect(tweet[:user]).to be_a(UserHash) end it 'coerces when setting with symbol index' do tweet = TweetHash.new tweet[:user] = Hashie::Mash.new(email: 'foo@bar.com') expect(tweet[:user]).to be_a(UserHash) end end context 'when subclassing' do class MyOwnBase < Hash include Hashie::Extensions::Coercion end class MyOwnHash < MyOwnBase coerce_key :value, Integer end class MyOwnSubclass < MyOwnHash end it 'inherits key coercions' do expect(MyOwnHash.key_coercions).to eql(MyOwnSubclass.key_coercions) end it 'the superclass does not accumulate coerced attributes from subclasses' do expect(MyOwnBase.key_coercions).to eq({}) end end context 'when using circular coercion' do context 'with a proc on one side' do class CategoryHash < Hash include Hashie::Extensions::Coercion include Hashie::Extensions::MergeInitializer coerce_key :products, lambda { |value| return value.map { |v| ProductHash.new(v) } if value.respond_to?(:map) ProductHash.new(v) } end class ProductHash < Hash include Hashie::Extensions::Coercion include Hashie::Extensions::MergeInitializer coerce_key :categories, Array[CategoryHash] end let(:category) { CategoryHash.new(type: 'rubygem', products: [Hashie::Mash.new(name: 'Hashie')]) } let(:product) { ProductHash.new(name: 'Hashie', categories: [Hashie::Mash.new(type: 'rubygem')]) } it 'coerces CategoryHash[:products] correctly' do expected = [ProductHash] actual = category[:products].map(&:class) expect(actual).to eq(expected) end it 'coerces ProductHash[:categories] correctly' do expected = [CategoryHash] actual = product[:categories].map(&:class) expect(actual).to eq(expected) end end context 'without a proc on either side' do it 'fails with a NameError since the other class is not defined yet' do attempted_code = lambda do class AnotherCategoryHash < Hash include Hashie::Extensions::Coercion include Hashie::Extensions::MergeInitializer coerce_key :products, Array[AnotherProductHash] end class AnotherProductHash < Hash include Hashie::Extensions::Coercion include Hashie::Extensions::MergeInitializer coerce_key :categories, Array[AnotherCategoryHash] end end expect { attempted_code.call }.to raise_error(NameError) end end end end describe '#coerce_value' do context 'with strict: true' do it 'coerces any value of the exact right class' do subject.coerce_value String, Coercable instance[:foo] = 'bar' instance[:bar] = 'bax' instance[:hi] = :bye expect(instance[:foo]).to be_coerced expect(instance[:bar]).to be_coerced expect(instance[:hi]).not_to respond_to(:coerced?) end it 'coerces values from a #replace call' do subject.coerce_value String, Coercable instance[:foo] = :bar instance.replace(foo: 'bar', bar: 'bax') expect(instance[:foo]).to be_coerced expect(instance[:bar]).to be_coerced end it 'does not coerce superclasses' do klass = Class.new(String) subject.coerce_value klass, Coercable instance[:foo] = 'bar' expect(instance[:foo]).not_to be_kind_of(Coercable) instance[:foo] = klass.new expect(instance[:foo]).to be_kind_of(Coercable) end end context 'core types' do it 'coerces String to Integer when possible' do subject.coerce_value String, Integer instance[:foo] = '2' instance[:bar] = '2.7' instance[:hi] = 'hi' expect(instance[:foo]).to be_a(Integer) expect(instance[:foo]).to eq(2) expect(instance[:bar]).to be_a(Integer) expect(instance[:bar]).to eq(2) expect(instance[:hi]).to be_a(Integer) expect(instance[:hi]).to eq(0) # not what I expected... end it 'coerces non-numeric from String to Integer' do # This was surprising, but I guess it's "correct" # unless there is a stricter `to_i` alternative subject.coerce_value String, Integer instance[:hi] = 'hi' expect(instance[:hi]).to be_a(Integer) expect(instance[:hi]).to eq(0) end it 'raises a CoercionError when coercion is not possible' do subject.coerce_value Fixnum, Symbol expect { instance[:hi] = 1 }.to raise_error(Hashie::CoercionError, /Cannot coerce property :hi from Fixnum to Symbol/) end it 'coerces Integer to String' do subject.coerce_value Integer, String { fixnum: 2, bignum: 12_345_667_890_987_654_321, float: 2.7, rational: Rational(2, 3), complex: Complex(1) }.each do | k, v | instance[k] = v if v.is_a? Integer expect(instance[k]).to be_a(String) expect(instance[k]).to eq(v.to_s) else expect(instance[k]).to_not be_a(String) expect(instance[k]).to eq(v) end end end it 'coerces Numeric to String' do subject.coerce_value Numeric, String { fixnum: 2, bignum: 12_345_667_890_987_654_321, float: 2.7, rational: Rational(2, 3), complex: Complex(1) }.each do | k, v | instance[k] = v expect(instance[k]).to be_a(String) expect(instance[k]).to eq(v.to_s) end end it 'can coerce via a proc' do subject.coerce_value(String, lambda do |v| return !!(v =~ /^(true|t|yes|y|1)$/i) end) true_values = %w(true t yes y 1) false_values = %w(false f no n 0) true_values.each do |v| instance[:foo] = v expect(instance[:foo]).to be_a(TrueClass) end false_values.each do |v| instance[:foo] = v expect(instance[:foo]).to be_a(FalseClass) end end end end after(:each) do Object.send(:remove_const, :ExampleCoercableHash) end end hashie-3.4.3/spec/hashie/extensions/merge_initializer_spec.rb0000644000004100000410000000103512620101230024427 0ustar www-datawww-datarequire 'spec_helper' describe Hashie::Extensions::MergeInitializer do class MergeInitializerHash < Hash include Hashie::Extensions::MergeInitializer end subject { MergeInitializerHash } it 'initializes with no arguments' do expect(subject.new).to eq({}) end it 'initializes with a hash' do expect(subject.new(abc: 'def')).to eq(abc: 'def') end it 'initializes with a hash and a default' do h = subject.new({ abc: 'def' }, 'bar') expect(h[:foo]).to eq 'bar' expect(h[:abc]).to eq 'def' end end hashie-3.4.3/spec/hashie/extensions/dash/0000755000004100000410000000000012620101230020306 5ustar www-datawww-datahashie-3.4.3/spec/hashie/extensions/dash/indifferent_access_spec.rb0000644000004100000410000000573612620101230025476 0ustar www-datawww-datarequire 'spec_helper' describe Hashie::Extensions::Dash::IndifferentAccess do class TrashWithIndifferentAccess < Hashie::Trash include Hashie::Extensions::Dash::IndifferentAccess property :per_page, transform_with: ->(v) { v.to_i } property :total, from: :total_pages end class DashWithIndifferentAccess < Hashie::Dash include Hashie::Extensions::Dash::IndifferentAccess property :name end context 'when included in Trash' do let(:params) { { per_page: '1', total_pages: 2 } } subject { TrashWithIndifferentAccess.new(params) } it 'gets the expected behaviour' do expect(subject.per_page).to eq params[:per_page].to_i expect(subject.total).to eq params[:total_pages] end end context 'when included in Dash' do let(:patch) { Hashie::Extensions::Dash::IndifferentAccess::ClassMethods } let(:dash_class) { Class.new(Hashie::Dash) } it 'extends with the patch once' do expect(patch).to receive(:extended).with(dash_class).once dash_class.send(:include, Hashie::Extensions::Dash::IndifferentAccess) end end context 'initialized with' do it 'string' do instance = DashWithIndifferentAccess.new('name' => 'Name') expect(instance.name).to eq('Name') expect(instance['name']).to eq('Name') expect(instance[:name]).to eq('Name') expect(instance.inspect).to eq('#') expect(instance.to_hash).to eq('name' => 'Name') end it 'key' do instance = DashWithIndifferentAccess.new(name: 'Name') expect(instance.name).to eq('Name') expect(instance['name']).to eq('Name') expect(instance[:name]).to eq('Name') expect(instance.inspect).to eq('#') expect(instance.to_hash).to eq('name' => 'Name') end end it 'updates' do instance = DashWithIndifferentAccess.new instance['name'] = 'Updated String' expect(instance.name).to eq('Updated String') instance[:name] = 'Updated Symbol' expect(instance.name).to eq('Updated Symbol') instance.name = 'Updated Method' expect(instance.name).to eq('Updated Method') end context 'initialized with both prefers last assignment' do it 'string, then symbol' do instance = DashWithIndifferentAccess.new('name' => 'First', name: 'Last') expect(instance.name).to eq('Last') expect(instance['name']).to eq('Last') expect(instance[:name]).to eq('Last') expect(instance.inspect).to eq('#') expect(instance.to_hash).to eq('name' => 'Last') end it 'symbol then string' do instance = DashWithIndifferentAccess.new(name: 'Last', 'name' => 'First') expect(instance.name).to eq('First') expect(instance['name']).to eq('First') expect(instance[:name]).to eq('First') expect(instance.inspect).to eq('#') expect(instance.to_hash).to eq('name' => 'First') end end end hashie-3.4.3/spec/hashie/extensions/dash/coercion_spec.rb0000644000004100000410000000051012620101230023442 0ustar www-datawww-datarequire 'spec_helper' describe Hashie::Extensions::Dash::Coercion do class DashWithCoercion < Hashie::Dash include Hashie::Extensions::Dash::Coercion property :type, coerce: Symbol end it 'does the coercion of properties' do expect(DashWithCoercion.new(type: 'something')).to eq(type: :something) end end hashie-3.4.3/spec/hashie/extensions/method_access_spec.rb0000644000004100000410000001205212620101230023527 0ustar www-datawww-datarequire 'spec_helper' describe Hashie::Extensions::MethodReader do class ReaderHash < Hash include Hashie::Extensions::MethodReader def initialize(hash = {}) update(hash) end end subject { ReaderHash } it 'reads string keys from the method' do expect(subject.new('awesome' => 'sauce').awesome).to eq 'sauce' end it 'reads symbol keys from the method' do expect(subject.new(awesome: 'sauce').awesome).to eq 'sauce' end it 'reads nil and false values out properly' do h = subject.new(nil: nil, false: false) expect(h.nil).to eq nil expect(h.false).to eq false end it 'raises a NoMethodError for undefined keys' do expect { subject.new.awesome }.to raise_error(NoMethodError) end it 'returns false for undefined keys if key with question has been called ' do expect(subject.new.awesome?).to eq false end it 'returns true for defined keys if key with question has been called' do expect(subject.new(awesome: 'sauce').awesome?).to eq true end describe '#respond_to?' do it 'is true for string keys' do expect(subject.new('awesome' => 'sauce')).to be_respond_to(:awesome) end it 'is true for symbol keys' do expect(subject.new(awesome: 'sauce')).to be_respond_to(:awesome) end it 'is false for non-keys' do expect(subject.new).not_to be_respond_to(:awesome) end end end describe Hashie::Extensions::MethodWriter do class WriterHash < Hash include Hashie::Extensions::MethodWriter end subject { WriterHash.new } it 'writes from a method call' do subject.awesome = 'sauce' expect(subject['awesome']).to eq 'sauce' end it 'converts the key using the #convert_key method' do allow(subject).to receive(:convert_key).and_return(:awesome) subject.awesome = 'sauce' expect(subject[:awesome]).to eq 'sauce' end it 'raises NoMethodError on non equals-ending methods' do expect { subject.awesome }.to raise_error(NoMethodError) end it '#respond_to? correctly' do expect(subject).to be_respond_to(:abc=) expect(subject).not_to be_respond_to(:abc) end end describe Hashie::Extensions::MethodQuery do class QueryHash < Hash include Hashie::Extensions::MethodQuery def initialize(hash = {}) update(hash) end end subject { QueryHash } it 'is true for non-nil string key values' do expect(subject.new('abc' => 123)).to be_abc end it 'is true for non-nil symbol key values' do expect(subject.new(abc: 123)).to be_abc end it 'is false for nil key values' do expect(subject.new(abc: false)).not_to be_abc end it 'raises a NoMethodError for non-set keys' do expect { subject.new.abc? }.to raise_error(NoMethodError) end it '#respond_to? for existing string keys' do expect(subject.new('abc' => 'def')).to be_respond_to('abc?') end it '#respond_to? for existing symbol keys' do expect(subject.new(abc: 'def')).to be_respond_to(:abc?) end it 'does not #respond_to? for non-existent keys' do expect(subject.new).not_to be_respond_to('abc?') end end describe Hashie::Extensions::MethodAccess do it 'includes all of the other method mixins' do klass = Class.new(Hash) klass.send :include, Hashie::Extensions::MethodAccess expect((klass.ancestors & [Hashie::Extensions::MethodReader, Hashie::Extensions::MethodWriter, Hashie::Extensions::MethodQuery]).size).to eq 3 end end describe Hashie::Extensions::MethodOverridingWriter do class OverridingHash < Hash include Hashie::Extensions::MethodOverridingWriter end subject { OverridingHash.new } it 'writes from a method call' do subject.awesome = 'sauce' expect(subject['awesome']).to eq 'sauce' end it 'convertes the key using the #convert_key method' do allow(subject).to receive(:convert_key).and_return(:awesome) subject.awesome = 'sauce' expect(subject[:awesome]).to eq 'sauce' end it 'raises NoMethodError on non equals-ending methods' do expect { subject.awesome }.to raise_error(NoMethodError) end it '#respond_to_missing? correctly' do expect(subject).to respond_to(:abc=) expect(subject).not_to respond_to(:abc) expect(subject.method(:abc=)).not_to be_nil end context 'when writing a Hash method' do before { subject.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 it 'does not re-alias when overriding an already overridden method' do subject.zip = 'test' expect(subject.zip).to eq 'test' expect(subject.__zip).to eq [[%w(zip test)]] end end end describe Hashie::Extensions::MethodAccessWithOverride do it 'includes all of the other method mixins' do 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 end end hashie-3.4.3/spec/hashie/extensions/deep_merge_spec.rb0000644000004100000410000000373412620101230023031 0ustar www-datawww-datarequire 'spec_helper' describe Hashie::Extensions::DeepMerge do class DeepMergeHash < Hash include Hashie::Extensions::DeepMerge end subject { DeepMergeHash } it 'should return initial hash for arguments that are not hash' do hash = subject.new.merge(a: 'a') expect(hash.deep_merge('abc')).to eq(hash) end context 'without &block' do let(:h1) { subject.new.merge(a: 'a', a1: 42, b: 'b', c: { c1: 'c1', c2: { a: 'b' }, c3: { d1: 'd1' } }) } let(:h2) { { a: 1, a1: 1, c: { c1: 2, c2: 'c2', c3: { d2: 'd2' } } } } let(:expected_hash) { { a: 1, a1: 1, b: 'b', c: { c1: 2, c2: 'c2', c3: { d1: 'd1', d2: 'd2' } } } } it 'deep merges two hashes' do expect(h1.deep_merge(h2)).to eq expected_hash end it 'deep merges another hash in place via bang method' do h1.deep_merge!(h2) expect(h1).to eq expected_hash end end context 'with &block' do let(:h1) { subject.new.merge(a: 100, b: 200, c: { c1: 100 }) } let(:h2) { { b: 250, c: { c1: 200 } } } let(:expected_hash) { { a: 100, b: 450, c: { c1: 300 } } } let(:block) { proc { |_, this_val, other_val| this_val + other_val } } it 'deep merges two hashes' do expect(h1.deep_merge(h2, &block)).to eq expected_hash end it 'deep merges another hash in place via bang method' do h1.deep_merge!(h2, &block) expect(h1).to eq expected_hash end end context 'from extended object' do subject { Hash } let(:h1) { subject.new.merge(a: 100, c: { c1: 100 }).extend(Hashie::Extensions::DeepMerge) } let(:h2) { { b: 250, c: { c1: 200 } } } let(:expected_hash) { { a: 100, b: 250, c: { c1: 200 } } } it 'does not raise error' do expect { h1.deep_merge(h2) } .not_to raise_error end it 'deep merges two hashes' do expect(h1.deep_merge(h2)).to eq expected_hash end it 'deep merges another hash in place via bang method' do h1.deep_merge!(h2) expect(h1).to eq expected_hash end end end hashie-3.4.3/spec/hashie/extensions/deep_fetch_spec.rb0000644000004100000410000000612512620101230023020 0ustar www-datawww-datarequire 'spec_helper' module Hashie module Extensions describe DeepFetch do subject { Class.new(Hash) { include Hashie::Extensions::DeepFetch } } let(:hash) do { library: { books: [ { title: 'Call of the Wild' }, { title: 'Moby Dick' } ], shelves: nil, location: { address: '123 Library St.' } } } end let(:instance) { subject.new.update(hash) } describe '#deep_fetch' do it 'extracts a value from a nested hash' do expect(instance.deep_fetch(:library, :location, :address)).to eq('123 Library St.') end it 'extracts a value from a nested array' do expect(instance.deep_fetch(:library, :books, 1, :title)).to eq('Moby Dick') end context 'when one of the keys is not present' do context 'when a block is provided' do it 'returns the value of the block' do value = instance.deep_fetch(:library, :unknown_key, :location) { 'block value' } expect(value).to eq('block value') end end context 'when a block is not provided' do context 'when the nested object is an array' do it 'raises an UndefinedPathError' do expect do instance.deep_fetch(:library, :books, 2) end.to( raise_error( DeepFetch::UndefinedPathError, 'Could not fetch path (library > books > 2) at 2' ) ) end end context 'when the nested object is a hash' do it 'raises a UndefinedPathError' do expect do instance.deep_fetch(:library, :location, :unknown_key) end.to( raise_error( DeepFetch::UndefinedPathError, 'Could not fetch path (library > location > unknown_key) at unknown_key' ) ) end end context 'when the nested object is missing' do it 'raises an UndefinedPathError' do expect do instance.deep_fetch(:library, :unknown_key, :books) end.to( raise_error( DeepFetch::UndefinedPathError, 'Could not fetch path (library > unknown_key > books) at unknown_key' ) ) end end context 'when the nested object is nil' do it 'raises an UndefinedPathError' do expect do instance.deep_fetch(:library, :shelves, :address) end.to( raise_error( DeepFetch::UndefinedPathError, 'Could not fetch path (library > shelves > address) at address' ) ) end end end end end end end end hashie-3.4.3/spec/hashie/extensions/stringify_keys_spec.rb0000644000004100000410000000614712620101230024007 0ustar www-datawww-datarequire 'spec_helper' require 'support/module_context' def invoke(method) if subject == object subject.public_send(method) else subject.public_send(method, object) end end shared_examples 'stringify_keys!' do it 'converts keys to strings' do object[:abc] = 'abc' object[123] = '123' invoke :stringify_keys! expect((object.keys & %w(abc 123)).size).to eq 2 end it 'converts nested instances of the same class' do object[:ab] = dummy_class.new object[:ab][:cd] = dummy_class.new object[:ab][:cd][:ef] = 'abcdef' invoke :stringify_keys! expect(object).to eq('ab' => { 'cd' => { 'ef' => 'abcdef' } }) end it 'converts nested hashes' do object[:ab] = { cd: { ef: 'abcdef' } } invoke :stringify_keys! expect(object).to eq('ab' => { 'cd' => { 'ef' => 'abcdef' } }) end it 'converts nested arrays' do object[:ab] = [] object[:ab] << dummy_class.new object[:ab] << dummy_class.new object[:ab][0][:cd] = 'abcd' object[:ab][1][:ef] = 'abef' invoke :stringify_keys! expect(object).to eq('ab' => [{ 'cd' => 'abcd' }, { 'ef' => 'abef' }]) end end shared_examples 'stringify_keys' do it 'converts keys to strings' do object[:abc] = 'def' copy = invoke :stringify_keys expect(copy['abc']).to eq 'def' end it 'does not alter the original' do object[:abc] = 'def' copy = invoke :stringify_keys expect(object.keys).to eq [:abc] expect(copy.keys).to eq %w(abc) end end describe Hashie::Extensions::StringifyKeys do include_context 'included hash module' let(:object) { subject } describe '#stringify_keys!' do include_examples 'stringify_keys!' it 'returns itself' do expect(subject.stringify_keys!).to eq subject end end context 'class methods' do subject { described_class } let(:object) { Hash.new } describe '.stringify_keys' do include_examples 'stringify_keys' end describe '.stringify_keys!' do include_examples 'stringify_keys!' end end context 'singleton methods' do subject { Hash } let(:object) { subject.new.merge(a: 1, b: { c: 2 }).extend(Hashie::Extensions::StringifyKeys) } let(:expected_hash) { { 'a' => 1, 'b' => { 'c' => 2 } } } describe '.stringify_keys' do it 'does not raise error' do expect { object.stringify_keys } .not_to raise_error end it 'produces expected stringified hash' do expect(object.stringify_keys).to eq(expected_hash) end end describe '.stringify_keys!' do it 'does not raise error' do expect { object.stringify_keys! } .not_to raise_error end it 'produces expected stringified hash' do expect(object.stringify_keys!).to eq(expected_hash) end end end end describe Hashie do let!(:dummy_class) do klass = Class.new(::Hash) klass.send :include, Hashie::Extensions::StringifyKeys klass end subject { described_class } let(:object) { Hash.new } describe '.stringify_keys' do include_examples 'stringify_keys' end describe '.stringify_keys!' do include_examples 'stringify_keys!' end end hashie-3.4.3/spec/hashie/extensions/key_conversion_spec.rb0000644000004100000410000000050712620101230023765 0ustar www-datawww-datarequire 'spec_helper' require 'support/module_context' describe Hashie::Extensions::KeyConversion do include_context 'included hash module' it { should respond_to(:stringify_keys) } it { should respond_to(:stringify_keys!) } it { should respond_to(:symbolize_keys) } it { should respond_to(:symbolize_keys!) } end hashie-3.4.3/spec/hashie/extensions/deep_find_spec.rb0000644000004100000410000000223312620101230022643 0ustar www-datawww-datarequire 'spec_helper' describe Hashie::Extensions::DeepFind do subject { Class.new(Hash) { include Hashie::Extensions::DeepFind } } let(:hash) do { library: { books: [ { title: 'Call of the Wild' }, { title: 'Moby Dick' } ], shelves: nil, location: { address: '123 Library St.', title: 'Main Library' } } } end let(:instance) { subject.new.update(hash) } describe '#deep_find' do it 'detects a value from a nested hash' do expect(instance.deep_find(:address)).to eq('123 Library St.') end it 'detects a value from a nested array' do expect(instance.deep_find(:title)).to eq('Call of the Wild') end it 'returns nil if it does not find a match' do expect(instance.deep_find(:wahoo)).to be_nil end end describe '#deep_find_all' do it 'detects all values from a nested hash' do expect(instance.deep_find_all(:title)).to eq(['Call of the Wild', 'Moby Dick', 'Main Library']) end it 'returns nil if it does not find any matches' do expect(instance.deep_find_all(:wahoo)).to be_nil end end end hashie-3.4.3/spec/hashie/extensions/strict_key_access_spec.rb0000644000004100000410000001052012620101230024425 0ustar www-datawww-datarequire 'spec_helper' describe Hashie::Extensions::StrictKeyAccess do class StrictKeyAccessHash < Hash include Hashie::Extensions::StrictKeyAccess end shared_examples_for 'StrictKeyAccess with valid key' do |options = {}| before { pending_for(options[:pending]) } if options[:pending] context 'set' do let(:new_value) { 42 } it('returns value') do expect(instance.send(:[]=, valid_key, new_value)).to eq new_value end end context 'access' do it('returns value') do expect(instance[valid_key]).to eq valid_value end end context 'lookup' do it('returns key') do expect(instance.key(valid_value)).to eq valid_key end end end shared_examples_for 'StrictKeyAccess with invalid key' do |options = {}| before { pending_for(options[:pending]) } if options[:pending] context 'access' do it('raises an error') do # Formatting of the error message varies on Rubinius and ruby-head expect { instance[invalid_key] }.to raise_error KeyError end end context 'lookup' do it('raises an error') do # Formatting of the error message does not vary here because raised by StrictKeyAccess expect { instance.key(invalid_value) }.to raise_error KeyError, %(key not found with value of #{invalid_value.inspect}) end end end shared_examples_for 'StrictKeyAccess raises KeyError instead of allowing defaults' do context '#default' do it 'raises an error' do expect { instance.default(invalid_key) }.to raise_error Hashie::Extensions::StrictKeyAccess::DefaultError, 'Setting or using a default with Hashie::Extensions::StrictKeyAccess does not make sense' end end context '#default=' do it 'raises an error' do expect { instance.default = invalid_key }.to raise_error Hashie::Extensions::StrictKeyAccess::DefaultError, 'Setting or using a default with Hashie::Extensions::StrictKeyAccess does not make sense' end end context '#default_proc' do it 'raises an error' do expect { instance.default_proc }.to raise_error Hashie::Extensions::StrictKeyAccess::DefaultError, 'Setting or using a default with Hashie::Extensions::StrictKeyAccess does not make sense' end end context '#default_proc=' do it 'raises an error' do expect { instance.default_proc = proc {} }.to raise_error Hashie::Extensions::StrictKeyAccess::DefaultError, 'Setting or using a default with Hashie::Extensions::StrictKeyAccess does not make sense' end end end let(:klass) { StrictKeyAccessHash } let(:instance) { StrictKeyAccessHash.new(*initialization_args) } let(:initialization_args) do [ { valid_key => valid_value } ] end let(:valid_key) { :abc } let(:valid_value) { 'def' } let(:invalid_key) { :mega } let(:invalid_value) { 'death' } context '.new' do context 'no defaults at initialization' do let(:initialization_args) { [] } before do instance.merge!(valid_key => valid_value) end it_behaves_like 'StrictKeyAccess with valid key' it_behaves_like 'StrictKeyAccess with invalid key' it_behaves_like 'StrictKeyAccess raises KeyError instead of allowing defaults' end context 'with defaults at initialization' do before do instance.merge!(valid_key => valid_value) end it_behaves_like 'StrictKeyAccess with valid key' it_behaves_like 'StrictKeyAccess with invalid key' it_behaves_like 'StrictKeyAccess raises KeyError instead of allowing defaults' end it_behaves_like 'StrictKeyAccess with invalid key' it_behaves_like 'StrictKeyAccess raises KeyError instead of allowing defaults' end context '.[]' do let(:instance) { StrictKeyAccessHash[*initialization_args] } it_behaves_like 'StrictKeyAccess with valid key', pending: { engine: 'rbx' } it_behaves_like 'StrictKeyAccess with invalid key', pending: { engine: 'rbx' } it_behaves_like 'StrictKeyAccess raises KeyError instead of allowing defaults' end end hashie-3.4.3/spec/hashie/hash_spec.rb0000644000004100000410000000703312620101230017455 0ustar www-datawww-datarequire 'spec_helper' describe Hash do it 'is convertible to a Hashie::Mash' do mash = Hashie::Hash[some: 'hash'].to_mash expect(mash.is_a?(Hashie::Mash)).to be_truthy expect(mash.some).to eq 'hash' end it '#stringify_keys! turns all keys into strings' do hash = Hashie::Hash[:a => 'hey', 123 => 'bob'] hash.stringify_keys! expect(hash).to eq Hashie::Hash['a' => 'hey', '123' => 'bob'] end it '#stringify_keys! turns all keys into strings recursively' do hash = Hashie::Hash[:a => 'hey', 123 => { 345 => 'hey' }] hash.stringify_keys! expect(hash).to eq Hashie::Hash['a' => 'hey', '123' => { '345' => 'hey' }] end it '#stringify_keys returns a hash with stringified keys' do hash = Hashie::Hash[:a => 'hey', 123 => 'bob'] stringified_hash = hash.stringify_keys expect(hash).to eq Hashie::Hash[:a => 'hey', 123 => 'bob'] expect(stringified_hash).to eq Hashie::Hash['a' => 'hey', '123' => 'bob'] end it '#to_hash returns a hash with same keys' do hash = Hashie::Hash['a' => 'hey', 123 => 'bob', 'array' => [1, 2, 3]] stringified_hash = hash.to_hash expect(stringified_hash).to eq('a' => 'hey', 123 => 'bob', 'array' => [1, 2, 3]) end it '#to_hash with stringify_keys set to true returns a hash with stringified_keys' do hash = Hashie::Hash['a' => 'hey', 123 => 'bob', 'array' => [1, 2, 3]] symbolized_hash = hash.to_hash(stringify_keys: true) expect(symbolized_hash).to eq('a' => 'hey', '123' => 'bob', 'array' => [1, 2, 3]) end it '#to_hash with symbolize_keys set to true returns a hash with symbolized keys' do hash = Hashie::Hash['a' => 'hey', 123 => 'bob', 'array' => [1, 2, 3]] symbolized_hash = hash.to_hash(symbolize_keys: true) expect(symbolized_hash).to eq(:a => 'hey', :"123" => 'bob', :array => [1, 2, 3]) end it "#to_hash should not blow up when #to_hash doesn't accept arguments" do class BareCustomMash < Hashie::Mash def to_hash {} end end h = Hashie::Hash.new h[:key] = BareCustomMash.new expect { h.to_hash }.not_to raise_error end describe 'when the value is an object that respond_to to_hash' do class ClassRespondsToHash def to_hash(options = {}) Hashie::Hash['a' => 'hey', b: 'bar', 123 => 'bob', 'array' => [1, 2, 3]].to_hash(options) end end it '#to_hash returns a hash with same keys' do hash = Hashie::Hash['a' => 'hey', 123 => 'bob', 'array' => [1, 2, 3], subhash: ClassRespondsToHash.new] stringified_hash = hash.to_hash expect(stringified_hash).to eq('a' => 'hey', 123 => 'bob', 'array' => [1, 2, 3], subhash: { 'a' => 'hey', b: 'bar', 123 => 'bob', 'array' => [1, 2, 3] }) end it '#to_hash with stringify_keys set to true returns a hash with stringified_keys' do hash = Hashie::Hash['a' => 'hey', 123 => 'bob', 'array' => [1, 2, 3], subhash: ClassRespondsToHash.new] symbolized_hash = hash.to_hash(stringify_keys: true) expect(symbolized_hash).to eq('a' => 'hey', '123' => 'bob', 'array' => [1, 2, 3], 'subhash' => { 'a' => 'hey', 'b' => 'bar', '123' => 'bob', 'array' => [1, 2, 3] }) end it '#to_hash with symbolize_keys set to true returns a hash with symbolized keys' do hash = Hashie::Hash['a' => 'hey', 123 => 'bob', 'array' => [1, 2, 3], subhash: ClassRespondsToHash.new] symbolized_hash = hash.to_hash(symbolize_keys: true) expect(symbolized_hash).to eq(:a => 'hey', :"123" => 'bob', :array => [1, 2, 3], subhash: { :a => 'hey', :b => 'bar', :'123' => 'bob', :array => [1, 2, 3] }) end end end hashie-3.4.3/spec/hashie/dash_spec.rb0000644000004100000410000003764712620101230017467 0ustar www-datawww-datarequire 'spec_helper' Hashie::Hash.class_eval do def self.inherited(klass) klass.instance_variable_set('@inheritance_test', true) end end class DashTest < Hashie::Dash property :first_name, required: true property :email property :count, default: 0 end class DashNoRequiredTest < Hashie::Dash property :first_name property :email property :count, default: 0 end class DashWithCoercion < Hashie::Dash include Hashie::Extensions::Coercion property :person property :city coerce_key :person, ::DashNoRequiredTest end class PropertyBangTest < Hashie::Dash property :important! end class SubclassedTest < DashTest property :last_name, required: true end class RequiredMessageTest < DashTest property :first_name, required: true, message: 'must be set.' end class DashDefaultTest < Hashie::Dash property :aliases, default: ['Snake'] end class DeferredTest < Hashie::Dash property :created_at, default: proc { Time.now } end class DeferredWithSelfTest < Hashie::Dash property :created_at, default: -> { Time.now } property :updated_at, default: ->(test) { test.created_at } end describe DashTest do def property_required_error(property) [ArgumentError, "The property '#{property}' is required for #{subject.class.name}."] end def property_required_custom_error(property) [ArgumentError, "The property '#{property}' must be set."] end def property_message_without_required_error [ArgumentError, 'The :message option should be used with :required option.'] end def no_property_error(property) [NoMethodError, "The property '#{property}' is not defined for #{subject.class.name}."] end subject { DashTest.new(first_name: 'Bob', email: 'bob@example.com') } let(:required_message) { RequiredMessageTest.new(first_name: 'Bob') } it('subclasses Hashie::Hash') { should respond_to(:to_mash) } describe '#to_s' do subject { super().to_s } it { should eq '#' } end it 'lists all set properties in inspect' do subject.first_name = 'Bob' subject.email = 'bob@example.com' expect(subject.inspect).to eq '#' end describe '#count' do subject { super().count } it { should be_zero } end it { should respond_to(:first_name) } it { should respond_to(:first_name=) } it { should_not respond_to(:nonexistent) } it 'errors out for a non-existent property' do expect { subject['nonexistent'] }.to raise_error(*no_property_error('nonexistent')) end it 'errors out when attempting to set a required property to nil' do expect { subject.first_name = nil }.to raise_error(*property_required_error('first_name')) end it 'errors out when message added to not required property' do expect do class DashMessageOptionWithoutRequiredTest < Hashie::Dash property :first_name, message: 'is required.' end end.to raise_error(*property_message_without_required_error) expect do class DashMessageOptionWithoutRequiredTest < Hashie::Dash property :first_name, required: false, message: 'is required.' end end.to raise_error(*property_message_without_required_error) end context 'writing to properties' do it 'fails writing a required property to nil' do expect { subject.first_name = nil }.to raise_error(*property_required_error('first_name')) expect { required_message.first_name = nil }.to raise_error(*property_required_custom_error('first_name')) end it 'fails writing a required property to nil using []=' do expect { subject[:first_name] = nil }.to raise_error(*property_required_error('first_name')) expect { required_message[:first_name] = nil }.to raise_error(*property_required_custom_error('first_name')) end it 'fails writing to a non-existent property using []=' do expect { subject['nonexistent'] = 123 }.to raise_error(*no_property_error('nonexistent')) end it 'works for an existing property using []=' do subject[:first_name] = 'Bob' expect(subject[:first_name]).to eq 'Bob' expect { subject['first_name'] }.to raise_error(*no_property_error('first_name')) end it 'works for an existing property using a method call' do subject.first_name = 'Franklin' expect(subject.first_name).to eq 'Franklin' end end context 'reading from properties' do it 'fails reading from a non-existent property using []' do expect { subject['nonexistent'] }.to raise_error(*no_property_error('nonexistent')) end it 'is able to retrieve properties through blocks' do subject[:first_name] = 'Aiden' value = nil subject.[](:first_name) { |v| value = v } expect(value).to eq 'Aiden' end it 'is able to retrieve properties through blocks with method calls' do subject[:first_name] = 'Frodo' value = nil subject.first_name { |v| value = v } expect(value).to eq 'Frodo' end end context 'reading from deferred properties' do it 'evaluates proc after initial read' do expect(DeferredTest.new[:created_at]).to be_instance_of(Time) end it 'does not evalute proc after subsequent reads' do deferred = DeferredTest.new expect(deferred[:created_at].object_id).to eq deferred[:created_at].object_id end end context 'reading from a deferred property based on context' do it 'provides the current hash as context for evaluation' do deferred = DeferredWithSelfTest.new expect(deferred[:created_at].object_id).to eq deferred[:created_at].object_id expect(deferred[:updated_at].object_id).to eq deferred[:created_at].object_id end end describe '#new' do it 'fails with non-existent properties' do expect { described_class.new(bork: '') }.to raise_error(*no_property_error('bork')) end it 'sets properties that it is able to' do obj = described_class.new first_name: 'Michael' expect(obj.first_name).to eq 'Michael' end it 'accepts nil' do expect { DashNoRequiredTest.new(nil) }.not_to raise_error end it 'accepts block to define a global default' do obj = described_class.new { |_, key| key.to_s.upcase } expect(obj.first_name).to eq 'FIRST_NAME' expect(obj.count).to be_zero end it 'fails when required values are missing' do expect { DashTest.new }.to raise_error(*property_required_error('first_name')) end it 'does not overwrite default values' do obj1 = DashDefaultTest.new obj1.aliases << 'El Rey' obj2 = DashDefaultTest.new expect(obj2.aliases).not_to include 'El Rey' end end describe '#merge' do it 'creates a new instance of the Dash' do new_dash = subject.merge(first_name: 'Robert') expect(subject.object_id).not_to eq new_dash.object_id end it 'merges the given hash' do new_dash = subject.merge(first_name: 'Robert', email: 'robert@example.com') expect(new_dash.first_name).to eq 'Robert' expect(new_dash.email).to eq 'robert@example.com' end it 'fails with non-existent properties' do expect { subject.merge(middle_name: 'James') }.to raise_error(*no_property_error('middle_name')) end it 'errors out when attempting to set a required property to nil' do expect { subject.merge(first_name: nil) }.to raise_error(*property_required_error('first_name')) end context 'given a block' do it "sets merged key's values to the block's return value" do expect(subject.merge(first_name: 'Jim') do |key, oldval, newval| "#{key}: #{newval} #{oldval}" end.first_name).to eq 'first_name: Jim Bob' end end end describe '#merge!' do it 'modifies the existing instance of the Dash' do original_dash = subject.merge!(first_name: 'Robert') expect(subject.object_id).to eq original_dash.object_id end it 'merges the given hash' do subject.merge!(first_name: 'Robert', email: 'robert@example.com') expect(subject.first_name).to eq 'Robert' expect(subject.email).to eq 'robert@example.com' end it 'fails with non-existent properties' do expect { subject.merge!(middle_name: 'James') }.to raise_error(NoMethodError) end it 'errors out when attempting to set a required property to nil' do expect { subject.merge!(first_name: nil) }.to raise_error(ArgumentError) end context 'given a block' do it "sets merged key's values to the block's return value" do expect(subject.merge!(first_name: 'Jim') do |key, oldval, newval| "#{key}: #{newval} #{oldval}" end.first_name).to eq 'first_name: Jim Bob' end end end describe 'properties' do it 'lists defined properties' do expect(described_class.properties).to eq Set.new([:first_name, :email, :count]) end it 'checks if a property exists' do expect(described_class.property?(:first_name)).to be_truthy expect(described_class.property?('first_name')).to be_falsy end it 'checks if a property is required' do expect(described_class.required?(:first_name)).to be_truthy expect(described_class.required?('first_name')).to be_falsy end it 'doesnt include property from subclass' do expect(described_class.property?(:last_name)).to be_falsy end it 'lists declared defaults' do expect(described_class.defaults).to eq(count: 0) end it 'allows properties that end in bang' do expect(PropertyBangTest.property?(:important!)).to be_truthy end end describe '#replace' do before { subject.replace(first_name: 'Cain') } it 'return self' do expect(subject.replace(email: 'bar').to_hash).to eq(email: 'bar', count: 0) end it 'sets all specified keys to their corresponding values' do expect(subject.first_name).to eq 'Cain' end it 'leaves only specified keys and keys with default values' do expect(subject.keys.sort_by(&:to_s)).to eq [:count, :first_name] expect(subject.email).to be_nil expect(subject.count).to eq 0 end context 'when replacing keys with default values' do before { subject.replace(count: 3) } it 'sets all specified keys to their corresponding values' do expect(subject.count).to eq 3 end end end describe '#update_attributes!(params)' do let(:params) { { first_name: 'Alice', email: 'alice@example.com' } } context 'when there is coercion' do let(:params_before) { { city: 'nyc', person: { first_name: 'Bob', email: 'bob@example.com' } } } let(:params_after) { { city: 'sfo', person: { first_name: 'Alice', email: 'alice@example.com' } } } subject { DashWithCoercion.new(params_before) } it 'update the attributes' do expect(subject.person.first_name).to eq params_before[:person][:first_name] subject.update_attributes!(params_after) expect(subject.person.first_name).to eq params_after[:person][:first_name] end end it 'update the attributes' do subject.update_attributes!(params) expect(subject.first_name).to eq params[:first_name] expect(subject.email).to eq params[:email] expect(subject.count).to eq subject.class.defaults[:count] end context 'when required property is update to nil' do let(:params) { { first_name: nil, email: 'alice@example.com' } } it 'raise an ArgumentError' do expect { subject.update_attributes!(params) }.to raise_error(ArgumentError) end end context 'when a default property is update to nil' do let(:params) { { count: nil, email: 'alice@example.com' } } it 'set the property back to the default value' do subject.update_attributes!(params) expect(subject.email).to eq params[:email] expect(subject.count).to eq subject.class.defaults[:count] end end end end describe Hashie::Dash, 'inheritance' do before do @top = Class.new(Hashie::Dash) @middle = Class.new(@top) @bottom = Class.new(@middle) end it 'reports empty properties when nothing defined' do expect(@top.properties).to be_empty expect(@top.defaults).to be_empty end it 'inherits properties downwards' do @top.property :echo expect(@middle.properties).to include(:echo) expect(@bottom.properties).to include(:echo) end it 'doesnt inherit properties upwards' do @middle.property :echo expect(@top.properties).not_to include(:echo) expect(@bottom.properties).to include(:echo) end it 'allows overriding a default on an existing property' do @top.property :echo @middle.property :echo, default: 123 expect(@bottom.properties.to_a).to eq [:echo] expect(@bottom.new.echo).to eq 123 end it 'allows clearing an existing default' do @top.property :echo @middle.property :echo, default: 123 @bottom.property :echo expect(@bottom.properties.to_a).to eq [:echo] expect(@bottom.new.echo).to be_nil end it 'allows nil defaults' do @bottom.property :echo, default: nil expect(@bottom.new).to have_key(:echo) expect(@bottom.new).to_not have_key('echo') end end describe SubclassedTest do subject { SubclassedTest.new(first_name: 'Bob', last_name: 'McNob', email: 'bob@example.com') } describe '#count' do subject { super().count } it { should be_zero } end it { should respond_to(:first_name) } it { should respond_to(:first_name=) } it { should respond_to(:last_name) } it { should respond_to(:last_name=) } it 'has one additional property' do expect(described_class.property?(:last_name)).to be_truthy end it "didn't override superclass inheritance logic" do expect(described_class.instance_variable_get('@inheritance_test')).to be_truthy end end class ConditionallyRequiredTest < Hashie::Dash property :username property :password, required: -> { !username.nil? }, message: 'must be set, too.' end describe ConditionallyRequiredTest do it 'does not allow a conditionally required property to be set to nil if required' do expect { ConditionallyRequiredTest.new(username: 'bob.smith', password: nil) }.to raise_error(ArgumentError, "The property 'password' must be set, too.") end it 'allows a conditionally required property to be set to nil if not required' do expect { ConditionallyRequiredTest.new(username: nil, password: nil) }.not_to raise_error end it 'allows a conditionally required property to be set if required' do expect { ConditionallyRequiredTest.new(username: 'bob.smith', password: '$ecure!') }.not_to raise_error end end class MixedPropertiesTest < Hashie::Dash property :symbol property 'string' end describe MixedPropertiesTest do subject { MixedPropertiesTest.new('string' => 'string', symbol: 'symbol') } it { should respond_to('string') } it { should respond_to(:symbol) } it 'property?' do expect(described_class.property?('string')).to be_truthy expect(described_class.property?(:symbol)).to be_truthy end it 'fetch' do expect(subject['string']).to eq('string') expect { subject[:string] }.to raise_error(NoMethodError) expect(subject[:symbol]).to eq('symbol') expect { subject['symbol'] }.to raise_error(NoMethodError) end it 'double define' do klass = Class.new(MixedPropertiesTest) do property 'symbol' end instance = klass.new(symbol: 'one', 'symbol' => 'two') expect(instance[:symbol]).to eq('one') expect(instance['symbol']).to eq('two') end it 'assign' do subject['string'] = 'updated' expect(subject['string']).to eq('updated') expect { subject[:string] = 'updated' }.to raise_error(NoMethodError) subject[:symbol] = 'updated' expect(subject[:symbol]).to eq('updated') expect { subject['symbol'] = 'updated' }.to raise_error(NoMethodError) end end context 'Dynamic Dash Class' do it 'define property' do klass = Class.new(Hashie::Dash) my_property = 'my_property' my_orig = my_property.dup klass.property(my_property) expect(my_property).to eq(my_orig) end end hashie-3.4.3/spec/support/0000755000004100000410000000000012620101230015443 5ustar www-datawww-datahashie-3.4.3/spec/support/module_context.rb0000644000004100000410000000030412620101230021016 0ustar www-datawww-datashared_context 'included hash module' do let!(:dummy_class) do klass = Class.new(::Hash) klass.send :include, described_class klass end subject do dummy_class.new end end hashie-3.4.3/lib/0000755000004100000410000000000012620101230013543 5ustar www-datawww-datahashie-3.4.3/lib/hashie/0000755000004100000410000000000012620101230015004 5ustar www-datawww-datahashie-3.4.3/lib/hashie/rash.rb0000644000004100000410000000667712620101230016306 0ustar www-datawww-datamodule Hashie # # Rash is a Hash whose keys can be Regexps, or Ranges, which will # match many input keys. # # A good use case for this class is routing URLs in a web framework. # The Rash's keys match URL patterns, and the values specify actions # which can handle the URL. When the Rash's value is proc, the proc # will be automatically called with the regexp's matched groups as # block arguments. # # Usage example: # # greeting = Hashie::Rash.new( /^Mr./ => "Hello sir!", /^Mrs./ => "Evening, madame." ) # greeting["Mr. Steve Austin"] #=> "Hello sir!" # greeting["Mrs. Steve Austin"] #=> "Evening, madame." # # Note: The Rash is automatically optimized every 500 accesses # (Regexps get sorted by how often they get matched). # If this is too low or too high, you can tune it by # setting: `rash.optimize_every = n` # class Rash attr_accessor :optimize_every def initialize(initial = {}) @hash = {} @regexes = [] @ranges = [] @regex_counts = Hash.new(0) @optimize_every = 500 @lookups = 0 update(initial) end def update(other) other.each do |key, value| self[key] = value end self end def []=(key, value) case key when Regexp # key = normalize_regex(key) # this used to just do: /#{regexp}/ @regexes << key when Range @ranges << key end @hash[key] = value end # # Return the first thing that matches the key. # def [](key) all(key).first end # # Raise (or yield) unless something matches the key. # def fetch(*args) fail ArgumentError, "Expected 1-2 arguments, got #{args.length}" \ unless (1..2).cover?(args.length) key, default = args all(key) do |value| return value end if block_given? yield key elsif default default else fail KeyError, "key not found: #{key.inspect}" end end # # Return everything that matches the query. # def all(query) return to_enum(:all, query) unless block_given? if @hash.include? query yield @hash[query] return end case query when String optimize_if_necessary! # see if any of the regexps match the string @regexes.each do |regex| match = regex.match(query) next unless match @regex_counts[regex] += 1 value = @hash[regex] if value.respond_to? :call yield value.call(match) else yield value end end when Numeric # see if any of the ranges match the integer @ranges.each do |range| yield @hash[range] if range.cover? query end when Regexp # Reverse operation: `rash[/regexp/]` returns all the hash's string keys which match the regexp @hash.each do |key, val| yield val if key.is_a?(String) && query =~ key end end end def method_missing(*args, &block) @hash.send(*args, &block) end def respond_to_missing?(*args) @hash.respond_to?(*args) end private def optimize_if_necessary! return unless (@lookups += 1) >= @optimize_every @regexes = @regex_counts.sort_by { |_, count| -count }.map { |regex, _| regex } @lookups = 0 end end end hashie-3.4.3/lib/hashie/hash.rb0000644000004100000410000000315112620101230016254 0ustar www-datawww-datarequire 'hashie/extensions/stringify_keys' require 'hashie/extensions/pretty_inspect' module Hashie # A Hashie Hash is simply a Hash that has convenience # functions baked in such as stringify_keys that may # not be available in all libraries. class Hash < ::Hash include Hashie::Extensions::PrettyInspect include Hashie::Extensions::StringifyKeys # Convert this hash into a Mash def to_mash ::Hashie::Mash.new(self) end # Converts a mash back to a hash (with stringified or symbolized keys) def to_hash(options = {}) out = {} keys.each do |k| assignment_key = if options[:stringify_keys] k.to_s elsif options[:symbolize_keys] k.to_s.to_sym else k end if self[k].is_a?(Array) out[assignment_key] ||= [] self[k].each do |array_object| out[assignment_key] << (Hash === array_object ? flexibly_convert_to_hash(array_object, options) : array_object) end else out[assignment_key] = (Hash === self[k] || self[k].respond_to?(:to_hash)) ? flexibly_convert_to_hash(self[k], options) : self[k] end end out end # The C generator for the json gem doesn't like mashies def to_json(*args) to_hash.to_json(*args) end private def flexibly_convert_to_hash(object, options = {}) if object.method(:to_hash).arity == 0 object.to_hash else object.to_hash(options) end end end end hashie-3.4.3/lib/hashie/mash.rb0000644000004100000410000002004212620101230016257 0ustar www-datawww-datarequire 'hashie/hash' module Hashie # Mash allows you to create pseudo-objects that have method-like # accessors for hash keys. This is useful for such implementations # as an API-accessing library that wants to fake robust objects # without the overhead of actually doing so. Think of it as OpenStruct # with some additional goodies. # # A Mash will look at the methods you pass it and perform operations # based on the following rules: # # * No punctuation: Returns the value of the hash for that key, or nil if none exists. # * Assignment (=): Sets the attribute of the given method name. # * Existence (?): Returns true or false depending on whether that key has been set. # * Bang (!): Forces the existence of this key, used for deep Mashes. Think of it as "touch" for mashes. # * Under Bang (_): Like Bang, but returns a new Mash rather than creating a key. Used to test existance in deep Mashes. # # == Basic Example # # mash = Mash.new # mash.name? # => false # mash.name = "Bob" # mash.name # => "Bob" # mash.name? # => true # # == Hash Conversion Example # # hash = {:a => {:b => 23, :d => {:e => "abc"}}, :f => [{:g => 44, :h => 29}, 12]} # mash = Mash.new(hash) # mash.a.b # => 23 # mash.a.d.e # => "abc" # mash.f.first.g # => 44 # mash.f.last # => 12 # # == Bang Example # # mash = Mash.new # mash.author # => nil # mash.author! # => # # mash = Mash.new # mash.author!.name = "Michael Bleigh" # mash.author # => # # == Under Bang Example # # mash = Mash.new # mash.author # => nil # mash.author_ # => # mash.author_.name # => nil # # mash = Mash.new # mash.author_.name = "Michael Bleigh" (assigned to temp object) # mash.author # => # class Mash < Hash include Hashie::Extensions::PrettyInspect ALLOWED_SUFFIXES = %w(? ! = _) def self.load(path, options = {}) @_mashes ||= new return @_mashes[path] if @_mashes.key?(path) fail ArgumentError, "The following file doesn't exist: #{path}" unless File.file?(path) parser = options.fetch(:parser) { Hashie::Extensions::Parsers::YamlErbParser } @_mashes[path] = new(parser.perform(path)).freeze end def to_module(mash_method_name = :settings) mash = self Module.new do |m| m.send :define_method, mash_method_name.to_sym do mash end end end alias_method :to_s, :inspect # If you pass in an existing hash, it will # convert it to a Mash including recursively # descending into arrays and hashes, converting # them as well. def initialize(source_hash = nil, default = nil, &blk) deep_update(source_hash) if source_hash default ? super(default) : super(&blk) end class << self; alias_method :[], :new; end alias_method :regular_reader, :[] alias_method :regular_writer, :[]= # Retrieves an attribute set in the Mash. Will convert # any key passed in to a string before retrieving. def custom_reader(key) default_proc.call(self, key) if default_proc && !key?(key) value = regular_reader(convert_key(key)) yield value if block_given? value end # Sets an attribute in the Mash. Key will be converted to # a string before it is set, and Hashes will be converted # into Mashes for nesting purposes. def custom_writer(key, value, convert = true) #:nodoc: regular_writer(convert_key(key), convert ? convert_value(value) : value) end alias_method :[], :custom_reader alias_method :[]=, :custom_writer # This is the bang method reader, it will return a new Mash # if there isn't a value already assigned to the key requested. def initializing_reader(key) ck = convert_key(key) regular_writer(ck, self.class.new) unless key?(ck) regular_reader(ck) end # This is the under bang method reader, it will return a temporary new Mash # if there isn't a value already assigned to the key requested. def underbang_reader(key) ck = convert_key(key) if key?(ck) regular_reader(ck) else self.class.new end end def fetch(key, *args) super(convert_key(key), *args) end def delete(key) super(convert_key(key)) end def values_at(*keys) super(*keys.map { |key| convert_key(key) }) end alias_method :regular_dup, :dup # Duplicates the current mash as a new mash. def dup self.class.new(self, default) end def key?(key) super(convert_key(key)) end alias_method :has_key?, :key? alias_method :include?, :key? alias_method :member?, :key? # Performs a deep_update on a duplicate of the # current mash. def deep_merge(other_hash, &blk) dup.deep_update(other_hash, &blk) end alias_method :merge, :deep_merge # Recursively merges this mash with the passed # in hash, merging each hash in the hierarchy. def deep_update(other_hash, &blk) other_hash.each_pair do |k, v| key = convert_key(k) if regular_reader(key).is_a?(Mash) && v.is_a?(::Hash) custom_reader(key).deep_update(v, &blk) else value = convert_value(v, true) value = convert_value(blk.call(key, self[k], value), true) if blk && self.key?(k) custom_writer(key, value, false) end end self end alias_method :deep_merge!, :deep_update alias_method :update, :deep_update alias_method :merge!, :update # Assigns a value to a key def assign_property(name, value) self[name] = value end # Performs a shallow_update on a duplicate of the current mash def shallow_merge(other_hash) dup.shallow_update(other_hash) end # Merges (non-recursively) the hash from the argument, # changing the receiving hash def shallow_update(other_hash) other_hash.each_pair do |k, v| regular_writer(convert_key(k), convert_value(v, true)) end self end def replace(other_hash) (keys - other_hash.keys).each { |key| delete(key) } other_hash.each { |key, value| self[key] = value } self end def respond_to_missing?(method_name, *args) return true if key?(method_name) suffix = method_suffix(method_name) if suffix true else super end end def prefix_method?(method_name) method_name = method_name.to_s method_name.end_with?(*ALLOWED_SUFFIXES) && key?(method_name.chop) end def method_missing(method_name, *args, &blk) return self.[](method_name, &blk) if key?(method_name) name, suffix = method_name_and_suffix(method_name) case suffix when '='.freeze assign_property(name, args.first) when '?'.freeze !!self[name] when '!'.freeze initializing_reader(name) when '_'.freeze underbang_reader(name) else self[method_name] end end # play nice with ActiveSupport Array#extract_options! def extractable_options? true end # another ActiveSupport method, see issue #270 def reverse_merge(other_hash) Hashie::Mash.new(other_hash).merge(self) end protected def method_name_and_suffix(method_name) method_name = method_name.to_s if method_name.end_with?(*ALLOWED_SUFFIXES) [method_name[0..-2], method_name[-1]] else [method_name[0..-1], nil] end end def method_suffix(method_name) method_name = method_name.to_s method_name[-1] if method_name.end_with?(*ALLOWED_SUFFIXES) end def convert_key(key) #:nodoc: key.to_s end def convert_value(val, duping = false) #:nodoc: case val when self.class val.dup when Hash duping ? val.dup : val when ::Hash val = val.dup if duping self.class.new(val) when Array val.map { |e| convert_value(e) } else val end end end end hashie-3.4.3/lib/hashie/trash.rb0000644000004100000410000000067212620101230016457 0ustar www-datawww-datarequire 'hashie/dash' require 'hashie/extensions/dash/property_translation' module Hashie # A Trash is a 'translated' Dash where the keys can be remapped from a source # hash. # # Trashes are useful when you need to read data from another application, # such as a Java api, where the keys are named differently from how we would # in Ruby. class Trash < Dash include Hashie::Extensions::Dash::PropertyTranslation end end hashie-3.4.3/lib/hashie/version.rb0000644000004100000410000000004612620101230017016 0ustar www-datawww-datamodule Hashie VERSION = '3.4.3' end hashie-3.4.3/lib/hashie/clash.rb0000644000004100000410000000503712620101230016430 0ustar www-datawww-datarequire 'hashie/hash' module Hashie # # A Clash is a "Chainable Lazy Hash". Inspired by libraries such as Arel, # a Clash allows you to chain together method arguments to build a # hash, something that's especially useful if you're doing something # like constructing a complex options hash. Here's a basic example: # # c = Hashie::Clash.new.conditions(:foo => 'bar').order(:created_at) # c # => {:conditions => {:foo => 'bar'}, :order => :created_at} # # Clash provides another way to create sub-hashes by using bang notation. # You can dive into a sub-hash by providing a key with a bang and dive # back out again with the _end! method. Example: # # c = Hashie::Clash.new.conditions!.foo('bar').baz(123)._end!.order(:created_at) # c # => { conditions: { foo: 'bar', baz: 123 }, order: :created_at} # # Because the primary functionality of Clash is to build options objects, # all keys are converted to symbols since many libraries expect symbols explicitly # for keys. # class Clash < ::Hash class ChainError < ::StandardError; end # The parent Clash if this Clash was created via chaining. attr_reader :_parent # Initialize a new clash by passing in a Hash to # convert and, optionally, the parent to which this # Clash is chained. def initialize(other_hash = {}, parent = nil) @_parent = parent other_hash.each_pair do |k, v| self[k.to_sym] = v end end # Jump back up a level if you are using bang method # chaining. For example: # # c = Hashie::Clash.new.foo('bar') # c.baz!.foo(123) # => c[:baz] # c.baz!._end! # => c def _end! _parent end def id(*args) #:nodoc: method_missing(:id, *args) end def merge_store(key, *args) #:nodoc: case args.length when 1 val = args.first val = self[key].merge(val) if self[key].is_a?(::Hash) && val.is_a?(::Hash) else val = args end self[key.to_sym] = val self end def method_missing(name, *args) #:nodoc: name = name.to_s if name.match(/!$/) && args.empty? key = name[0...-1].to_sym if self[key].nil? self[key] = Clash.new({}, self) elsif self[key].is_a?(::Hash) && !self[key].is_a?(Clash) self[key] = Clash.new(self[key], self) else fail ChainError, 'Tried to chain into a non-hash key.' end self[key] elsif args.any? key = name.to_sym merge_store(key, *args) end end end end hashie-3.4.3/lib/hashie/extensions/0000755000004100000410000000000012620101230017203 5ustar www-datawww-datahashie-3.4.3/lib/hashie/extensions/symbolize_keys.rb0000644000004100000410000000357112620101230022606 0ustar www-datawww-datamodule Hashie module Extensions module SymbolizeKeys # Convert all keys in the hash to symbols. # # @example # test = {'abc' => 'def'} # test.symbolize_keys! # test # => {:abc => 'def'} def symbolize_keys! SymbolizeKeys.symbolize_keys!(self) self end # Return a new hash with all keys converted # to symbols. def symbolize_keys SymbolizeKeys.symbolize_keys(self) end module ClassMethods # Symbolize all keys recursively within nested # hashes and arrays. # @api private def symbolize_keys_recursively!(object) case object when self.class symbolize_keys!(object) when ::Array object.each do |i| symbolize_keys_recursively!(i) end when ::Hash symbolize_keys!(object) end end # Convert all keys in hash to symbols. # # @param [Hash] hash # @example # test = {'abc' => 'def'} # Hashie.symbolize_keys! test # test # => {:abc => 'def'} def symbolize_keys!(hash) hash.extend(Hashie::Extensions::SymbolizeKeys) unless hash.respond_to?(:symbolize_keys!) hash.keys.each do |k| symbolize_keys_recursively!(hash[k]) hash[k.to_sym] = hash.delete(k) end hash end # Return a copy of hash with all keys converted # to symbols. # @param [::Hash] hash def symbolize_keys(hash) copy = hash.dup copy.extend(Hashie::Extensions::SymbolizeKeys) unless copy.respond_to?(:symbolize_keys!) copy.tap do |new_hash| symbolize_keys!(new_hash) end end end class << self include ClassMethods end end end end hashie-3.4.3/lib/hashie/extensions/method_access.rb0000644000004100000410000001424412620101230022336 0ustar www-datawww-datamodule Hashie module Extensions # MethodReader allows you to access keys of the hash # via method calls. This gives you an OStruct like way # to access your hash's keys. It will recognize keys # either as strings or symbols. # # Note that while nil keys will be returned as nil, # undefined keys will raise NoMethodErrors. Also note that # #respond_to? has been patched to appropriately recognize # key methods. # # @example # class User < Hash # include Hashie::Extensions::MethodReader # end # # user = User.new # user['first_name'] = 'Michael' # user.first_name # => 'Michael' # # user[:last_name] = 'Bleigh' # user.last_name # => 'Bleigh' # # user[:birthday] = nil # user.birthday # => nil # # user.not_declared # => NoMethodError module MethodReader def respond_to?(name, include_private = false) return true if key?(name.to_s) || key?(name.to_sym) super end def method_missing(name, *args) if key?(name) self[name] else sname = name.to_s if key?(sname) self[sname] elsif sname[-1] == '?' kname = sname[0..-2] key?(kname) || key?(kname.to_sym) else super end end end end # MethodWriter gives you #key_name= shortcuts for # writing to your hash. Keys are written as strings, # override #convert_key if you would like to have symbols # or something else. # # Note that MethodWriter also overrides #respond_to such # that any #method_name= will respond appropriately as true. # # @example # class MyHash < Hash # include Hashie::Extensions::MethodWriter # end # # h = MyHash.new # h.awesome = 'sauce' # h['awesome'] # => 'sauce' # module MethodWriter def respond_to?(name, include_private = false) return true if name.to_s =~ /=$/ super end def method_missing(name, *args) if args.size == 1 && name.to_s =~ /(.*)=$/ return self[convert_key(Regexp.last_match[1])] = args.first end super end def convert_key(key) key.to_s end end # MethodQuery gives you the ability to check for the truthiness # of a key via method calls. Note that it will return false if # the key is set to a non-truthful value, not if the key isn't # set at all. Use #key? for checking if a key has been set. # # MethodQuery will check against both string and symbol names # of the method for existing keys. It also patches #respond_to # to appropriately detect the query methods. # # @example # class MyHash < Hash # include Hashie::Extensions::MethodQuery # end # # h = MyHash.new # h['abc'] = 123 # h.abc? # => true # h['def'] = nil # h.def? # => false # h.hji? # => NoMethodError module MethodQuery def respond_to?(name, include_private = false) return true if name.to_s =~ /(.*)\?$/ && (key?(Regexp.last_match[1]) || key?(Regexp.last_match[1].to_sym)) super end def method_missing(name, *args) if args.empty? && name.to_s =~ /(.*)\?$/ && (key?(Regexp.last_match[1]) || key?(Regexp.last_match[1].to_sym)) return self[Regexp.last_match[1]] || self[Regexp.last_match[1].to_sym] end super end end # A macro module that will automatically include MethodReader, # MethodWriter, and MethodQuery, giving you the ability to read, # write, and query keys in a hash using method call shortcuts. module MethodAccess def self.included(base) [MethodReader, MethodWriter, MethodQuery].each do |mod| base.send :include, mod end 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 # leading underscores. # # Keys are written as strings. Override #convert_key if you # would like to have symbols or something else. # # Note that MethodOverridingWriter also overrides # #respond_to_missing? such that any #method_name= will respond # appropriately as true. # # @example # class MyHash < Hash # include Hashie::Extensions::MethodOverridingWriter # end # # h = MyHash.new # h.awesome = 'sauce' # h['awesome'] # => 'sauce' # h.zip = 'a-dee-doo-dah' # h.zip # => 'a-dee-doo-dah' # h.__zip # => [[['awesome', 'sauce'], ['zip', 'a-dee-doo-dah']]] # module MethodOverridingWriter def convert_key(key) key.to_s end def method_missing(name, *args) if args.size == 1 && name.to_s =~ /(.*)=$/ key = Regexp.last_match[1] redefine_method(key) if method?(key) && !already_overridden?(key) return self[convert_key(key)] = args.first end super end def respond_to_missing?(name, include_private = false) return true if name.to_s.end_with?('=') super end protected 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, # MethodOverridingWriter, and MethodQuery, giving you the ability # to read, write, and query keys in a hash using method call # shortcuts that can override object methods. Any overridden # object method is automatically aliased with two leading # underscores. module MethodAccessWithOverride def self.included(base) [MethodReader, MethodOverridingWriter, MethodQuery].each do |mod| base.send :include, mod end end end end end hashie-3.4.3/lib/hashie/extensions/coercion.rb0000644000004100000410000001542212620101230021335 0ustar www-datawww-datamodule Hashie class CoercionError < StandardError; end module Extensions module Coercion CORE_TYPES = { Integer => :to_i, Float => :to_f, Complex => :to_c, Rational => :to_r, String => :to_s, Symbol => :to_sym } ABSTRACT_CORE_TYPES = { Integer => [Fixnum, Bignum], Numeric => [Fixnum, Bignum, Float, Complex, Rational] } def self.included(base) base.send :include, InstanceMethods base.extend ClassMethods # NOTE: we wanna make sure we first define set_value_with_coercion before extending base.send :alias_method, :set_value_without_coercion, :[]= unless base.method_defined?(:set_value_without_coercion) base.send :alias_method, :[]=, :set_value_with_coercion end module InstanceMethods def set_value_with_coercion(key, value) into = self.class.key_coercion(key) || self.class.value_coercion(value) unless value.nil? || into.nil? begin value = self.class.fetch_coercion(into).call(value) rescue NoMethodError, TypeError => e raise CoercionError, "Cannot coerce property #{key.inspect} from #{value.class} to #{into}: #{e.message}" end end set_value_without_coercion(key, value) end def custom_writer(key, value, _convert = true) self[key] = value end def replace(other_hash) (keys - other_hash.keys).each { |key| delete(key) } other_hash.each { |key, value| self[key] = value } self end end module ClassMethods attr_writer :key_coercions protected :key_coercions= # Set up a coercion rule such that any time the specified # key is set it will be coerced into the specified class. # Coercion will occur by first attempting to call Class.coerce # and then by calling Class.new with the value as an argument # in either case. # # @param [Object] key the key or array of keys you would like to be coerced. # @param [Class] into the class into which you want the key(s) coerced. # # @example Coerce a "user" subhash into a User object # class Tweet < Hash # include Hashie::Extensions::Coercion # coerce_key :user, User # end def coerce_key(*attrs) into = attrs.pop attrs.each { |key| key_coercions[key] = into } end alias_method :coerce_keys, :coerce_key # Returns a hash of any existing key coercions. def key_coercions @key_coercions ||= {} end # Returns the specific key coercion for the specified key, # if one exists. def key_coercion(key) key_coercions[key.to_sym] end # Set up a coercion rule such that any time a value of the # specified type is set it will be coerced into the specified # class. # # @param [Class] from the type you would like coerced. # @param [Class] into the class into which you would like the value coerced. # @option options [Boolean] :strict (true) whether use exact source class only or include ancestors # # @example Coerce all hashes into this special type of hash # class SpecialHash < Hash # include Hashie::Extensions::Coercion # coerce_value Hash, SpecialHash # # def initialize(hash = {}) # super # hash.each_pair do |k,v| # self[k] = v # end # end # end def coerce_value(from, into, options = {}) options = { strict: true }.merge(options) if ABSTRACT_CORE_TYPES.key? from ABSTRACT_CORE_TYPES[from].each do | type | coerce_value type, into, options end end if options[:strict] strict_value_coercions[from] = into else while from.superclass && from.superclass != Object lenient_value_coercions[from] = into from = from.superclass end end end # Return all value coercions that have the :strict rule as true. def strict_value_coercions @strict_value_coercions ||= {} end # Return all value coercions that have the :strict rule as false. def lenient_value_coercions @lenient_value_coercions ||= {} end # Fetch the value coercion, if any, for the specified object. def value_coercion(value) from = value.class strict_value_coercions[from] || lenient_value_coercions[from] end def fetch_coercion(type) return type if type.is_a? Proc coercion_cache[type] end def coercion_cache @coercion_cache ||= ::Hash.new do |hash, type| hash[type] = build_coercion(type) end end def build_coercion(type) if type.is_a? Enumerable if type.class <= ::Hash type, key_type, value_type = type.class, *type.first build_hash_coercion(type, key_type, value_type) else # Enumerable but not Hash: Array, Set type, value_type = type.class, type.first build_container_coercion(type, value_type) end elsif CORE_TYPES.key? type build_core_type_coercion(type) elsif type.respond_to? :coerce lambda do |value| return value if value.is_a? type type.coerce(value) end elsif type.respond_to? :new lambda do |value| return value if value.is_a? type type.new(value) end else fail TypeError, "#{type} is not a coercable type" end end def build_hash_coercion(type, key_type, value_type) key_coerce = fetch_coercion(key_type) value_coerce = fetch_coercion(value_type) lambda do |value| type[value.map { |k, v| [key_coerce.call(k), value_coerce.call(v)] }] end end def build_container_coercion(type, value_type) value_coerce = fetch_coercion(value_type) lambda do |value| type.new(value.map { |v| value_coerce.call(v) }) end end def build_core_type_coercion(type) name = CORE_TYPES[type] lambda do |value| return value if value.is_a? type return value.send(name) end end def inherited(klass) super klass.key_coercions = key_coercions.dup end end end end end hashie-3.4.3/lib/hashie/extensions/indifferent_access.rb0000644000004100000410000001056712620101230023357 0ustar www-datawww-datamodule Hashie module Extensions # IndifferentAccess gives you the ability to not care # whether your hash has string or symbol keys. Made famous # in Rails for accessing query and POST parameters, this # is a handy tool for making sure your hash has maximum # utility. # # One unique feature of this mixin is that it will recursively # inject itself into sub-hash instances without modifying # the actual class of the sub-hash. # # @example # class MyHash < Hash # include Hashie::Extensions::MergeInitializer # include Hashie::Extensions::IndifferentAccess # end # # h = MyHash.new(:foo => 'bar', 'baz' => 'blip') # h['foo'] # => 'bar' # h[:foo] # => 'bar' # h[:baz] # => 'blip' # h['baz'] # => 'blip' # module IndifferentAccess def self.included(base) Hashie::Extensions::Dash::IndifferentAccess::ClassMethods.tap do |extension| base.extend(extension) if base <= Hashie::Dash && !base.singleton_class.included_modules.include?(extension) end base.class_eval do alias_method :regular_writer, :[]= unless method_defined?(:regular_writer) alias_method :[]=, :indifferent_writer alias_method :store, :indifferent_writer %w(default update replace fetch delete key? values_at).each do |m| alias_method "regular_#{m}", m unless method_defined?("regular_#{m}") alias_method m, "indifferent_#{m}" end %w(include? member? has_key?).each do |key_alias| alias_method key_alias, :indifferent_key? end class << self def [](*) super.convert! end def try_convert(*) (hash = super) && self[hash] end end end end # This will inject indifferent access into an instance of # a hash without modifying the actual class. This is what # allows IndifferentAccess to spread to sub-hashes. def self.inject!(hash) (class << hash; self; end).send :include, IndifferentAccess hash.convert! end # Injects indifferent access into a duplicate of the hash # provided. See #inject! def self.inject(hash) inject!(hash.dup) end def convert_key(key) key.to_s end # Iterates through the keys and values, reconverting them to # their proper indifferent state. Used when IndifferentAccess # is injecting itself into member hashes. def convert! keys.each do |k| regular_writer convert_key(k), indifferent_value(regular_delete(k)) end self end def indifferent_value(value) if hash_lacking_indifference?(value) IndifferentAccess.inject!(value) elsif value.is_a?(::Array) value.replace(value.map { |e| indifferent_value(e) }) else value end end def indifferent_default(key = nil) return self[convert_key(key)] if key?(key) regular_default(key) end def indifferent_update(other_hash) return regular_update(other_hash) if hash_with_indifference?(other_hash) other_hash.each_pair do |k, v| self[k] = v end end def indifferent_writer(key, value) regular_writer convert_key(key), indifferent_value(value) end def indifferent_fetch(key, *args, &block) regular_fetch convert_key(key), *args, &block end def indifferent_delete(key) regular_delete convert_key(key) end def indifferent_key?(key) regular_key? convert_key(key) end def indifferent_values_at(*indices) indices.map { |i| self[i] } end def indifferent_access? true end def indifferent_replace(other_hash) (keys - other_hash.keys).each { |key| delete(key) } other_hash.each { |key, value| self[key] = value } self end protected def hash_lacking_indifference?(other) other.is_a?(::Hash) && !(other.respond_to?(:indifferent_access?) && other.indifferent_access?) end def hash_with_indifference?(other) other.is_a?(::Hash) && other.respond_to?(:indifferent_access?) && other.indifferent_access? end end end end hashie-3.4.3/lib/hashie/extensions/deep_fetch.rb0000644000004100000410000000205312620101230021616 0ustar www-datawww-datamodule Hashie module Extensions # Searches a deeply nested datastructure for a key path, and returns the associated value. # # options = { user: { location: { address: '123 Street' } } } # options.deep_fetch :user, :location, :address #=> '123 Street' # # If a block is provided its value will be returned if the key does not exist. # # options.deep_fetch(:user, :non_existent_key) { 'a value' } #=> 'a value' # # This is particularly useful for fetching values from deeply nested api responses or params hashes. module DeepFetch class UndefinedPathError < StandardError; end def deep_fetch(*args, &block) args.reduce(self) do |obj, arg| begin arg = Integer(arg) if obj.is_a? Array obj.fetch(arg) rescue ArgumentError, IndexError, NoMethodError => e break block.call(arg) if block raise UndefinedPathError, "Could not fetch path (#{args.join(' > ')}) at #{arg}", e.backtrace end end end end end end hashie-3.4.3/lib/hashie/extensions/merge_initializer.rb0000644000004100000410000000135112620101230023232 0ustar www-datawww-datamodule Hashie module Extensions # The MergeInitializer is a super-simple mixin that allows # you to initialize a subclass of Hash with another Hash # to give you faster startup time for Hash subclasses. Note # that you can still provide a default value as a second # argument to the initializer. # # @example # class MyHash < Hash # include Hashie::Extensions::MergeInitializer # end # # h = MyHash.new(:abc => 'def') # h[:abc] # => 'def' # module MergeInitializer def initialize(hash = {}, default = nil, &block) default ? super(default) : super(&block) hash.each do |key, value| self[key] = value end end end end end hashie-3.4.3/lib/hashie/extensions/deep_find.rb0000644000004100000410000000367012620101230021453 0ustar www-datawww-datamodule Hashie module Extensions module DeepFind # Performs a depth-first search on deeply nested data structures for # a key and returns the first occurrence of the key. # # options = {user: {location: {address: '123 Street'}}} # options.extend(Hashie::Extensions::DeepFind) # options.deep_find(:address) # => '123 Street' # # class MyHash < Hash # include Hashie::Extensions::DeepFind # end # # my_hash = MyHash.new # my_hash[:user] = {location: {address: '123 Street'}} # my_hash.deep_find(:address) # => '123 Street' def deep_find(key) _deep_find(key) end alias_method :deep_detect, :deep_find # Performs a depth-first search on deeply nested data structures for # a key and returns all occurrences of the key. # # options = {users: [{location: {address: '123 Street'}}, {location: {address: '234 Street'}}]} # options.extend(Hashie::Extensions::DeepFind) # options.deep_find_all(:address) # => ['123 Street', '234 Street'] # # class MyHash < Hash # include Hashie::Extensions::DeepFind # end # # my_hash = MyHash.new # my_hash[:users] = [{location: {address: '123 Street'}}, {location: {address: '234 Street'}}] # my_hash.deep_find_all(:address) # => ['123 Street', '234 Street'] def deep_find_all(key) matches = _deep_find_all(key) matches.empty? ? nil : matches end alias_method :deep_select, :deep_find_all private def _deep_find(key, object = self) _deep_find_all(key, object).first end def _deep_find_all(key, object = self, matches = []) deep_locate_result = Hashie::Extensions::DeepLocate.deep_locate(key, object).tap do |result| result.map! { |element| element[key] } end matches.concat(deep_locate_result) end end end end hashie-3.4.3/lib/hashie/extensions/mash/0000755000004100000410000000000012620101230020133 5ustar www-datawww-datahashie-3.4.3/lib/hashie/extensions/mash/safe_assignment.rb0000644000004100000410000000056512620101230023634 0ustar www-datawww-datamodule Hashie module Extensions module Mash module SafeAssignment def custom_writer(key, *args) #:nodoc: fail ArgumentError, "The property #{key} clashes with an existing method." if !key?(key) && respond_to?(key, true) super end def []=(*args) custom_writer(*args) end end end end end hashie-3.4.3/lib/hashie/extensions/deep_locate.rb0000644000004100000410000000575612620101230022011 0ustar www-datawww-datamodule Hashie module Extensions module DeepLocate # The module level implementation of #deep_locate, incase you do not want # to include/extend the base datastructure. For further examples please # see #deep_locate. # # @example # books = [ # { # title: "Ruby for beginners", # pages: 120 # }, # ... # ] # # Hashie::Extensions::DeepLocate.deep_locate -> (key, value, object) { key == :title }, books # # => [{:title=>"Ruby for beginners", :pages=>120}, ...] def self.deep_locate(comparator, object) # ensure comparator is a callable unless comparator.respond_to?(:call) comparator = lambda do |non_callable_object| ->(key, _, _) { key == non_callable_object } end.call(comparator) end _deep_locate(comparator, object) end # Performs a depth-first search on deeply nested data structures for a # given comparator callable and returns each Enumerable, for which the # callable returns true for at least one the its elements. # # @example # books = [ # { # title: "Ruby for beginners", # pages: 120 # }, # { # title: "CSS for intermediates", # pages: 80 # }, # { # title: "Collection of ruby books", # books: [ # { # title: "Ruby for the rest of us", # pages: 576 # } # ] # } # ] # # books.extend(Hashie::Extensions::DeepLocate) # # # for ruby 1.9 leave *no* space between the lambda rocket and the braces # # http://ruby-journal.com/becareful-with-space-in-lambda-hash-rocket-syntax-between-ruby-1-dot-9-and-2-dot-0/ # # books.deep_locate -> (key, value, object) { key == :title && value.include?("Ruby") } # # => [{:title=>"Ruby for beginners", :pages=>120}, {:title=>"Ruby for the rest of us", :pages=>576}] # # books.deep_locate -> (key, value, object) { key == :pages && value <= 120 } # # => [{:title=>"Ruby for beginners", :pages=>120}, {:title=>"CSS for intermediates", :pages=>80}] def deep_locate(comparator) Hashie::Extensions::DeepLocate.deep_locate(comparator, self) end private def self._deep_locate(comparator, object, result = []) if object.is_a?(::Enumerable) if object.any? do |value| if object.is_a?(::Hash) key, value = value else key = nil end comparator.call(key, value, object) end result.push object else (object.respond_to?(:values) ? object.values : object.entries).each do |value| _deep_locate(comparator, value, result) end end end result end end end end hashie-3.4.3/lib/hashie/extensions/deep_merge.rb0000644000004100000410000000217312620101230021627 0ustar www-datawww-datamodule Hashie module Extensions module DeepMerge # Returns a new hash with +self+ and +other_hash+ merged recursively. def deep_merge(other_hash, &block) copy = dup copy.extend(Hashie::Extensions::DeepMerge) unless copy.respond_to?(:deep_merge!) copy.deep_merge!(other_hash, &block) end # Returns a new hash with +self+ and +other_hash+ merged recursively. # Modifies the receiver in place. def deep_merge!(other_hash, &block) return self unless other_hash.is_a?(::Hash) _recursive_merge(self, other_hash, &block) self end private def _recursive_merge(hash, other_hash, &block) other_hash.each do |k, v| hash[k] = if hash.key?(k) && hash[k].is_a?(::Hash) && v.is_a?(::Hash) _recursive_merge(hash[k], v, &block) else if hash.key?(k) && block_given? block.call(k, hash[k], v) else v end end end hash end end end end hashie-3.4.3/lib/hashie/extensions/key_conversion.rb0000644000004100000410000000031112620101230022560 0ustar www-datawww-datamodule Hashie module Extensions module KeyConversion def self.included(base) base.send :include, SymbolizeKeys base.send :include, StringifyKeys end end end end hashie-3.4.3/lib/hashie/extensions/pretty_inspect.rb0000644000004100000410000000066312620101230022611 0ustar www-datawww-datamodule Hashie module Extensions module PrettyInspect def self.included(base) base.send :alias_method, :hash_inspect, :inspect base.send :alias_method, :inspect, :hashie_inspect end def hashie_inspect ret = "#<#{self.class}" keys.sort_by(&:to_s).each do |key| ret << " #{key}=#{self[key].inspect}" end ret << '>' ret end end end end hashie-3.4.3/lib/hashie/extensions/parsers/0000755000004100000410000000000012620101230020662 5ustar www-datawww-datahashie-3.4.3/lib/hashie/extensions/parsers/yaml_erb_parser.rb0000644000004100000410000000075012620101230024357 0ustar www-datawww-datarequire 'yaml' require 'erb' module Hashie module Extensions module Parsers class YamlErbParser def initialize(file_path) @content = File.read(file_path) @file_path = file_path end def perform template = ERB.new(@content) template.filename = @file_path YAML.load template.result end def self.perform(file_path) new(file_path).perform end end end end end hashie-3.4.3/lib/hashie/extensions/stringify_keys.rb0000644000004100000410000000356612620101230022613 0ustar www-datawww-datamodule Hashie module Extensions module StringifyKeys # Convert all keys in the hash to strings. # # @example # test = {:abc => 'def'} # test.stringify_keys! # test # => {'abc' => 'def'} def stringify_keys! StringifyKeys.stringify_keys!(self) self end # Return a new hash with all keys converted # to strings. def stringify_keys StringifyKeys.stringify_keys(self) end module ClassMethods # Stringify all keys recursively within nested # hashes and arrays. # @api private def stringify_keys_recursively!(object) case object when self.class stringify_keys!(object) when ::Array object.each do |i| stringify_keys_recursively!(i) end when ::Hash stringify_keys!(object) end end # Convert all keys in the hash to strings. # # @param [::Hash] hash # @example # test = {:abc => 'def'} # test.stringify_keys! # test # => {'abc' => 'def'} def stringify_keys!(hash) hash.extend(Hashie::Extensions::StringifyKeys) unless hash.respond_to?(:stringify_keys!) hash.keys.each do |k| stringify_keys_recursively!(hash[k]) hash[k.to_s] = hash.delete(k) end hash end # Return a copy of hash with all keys converted # to strings. # @param [::Hash] hash def stringify_keys(hash) copy = hash.dup copy.extend(Hashie::Extensions::StringifyKeys) unless copy.respond_to?(:stringify_keys!) copy.tap do |new_hash| stringify_keys!(new_hash) end end end class << self include ClassMethods end end end end hashie-3.4.3/lib/hashie/extensions/dash/0000755000004100000410000000000012620101230020122 5ustar www-datawww-datahashie-3.4.3/lib/hashie/extensions/dash/coercion.rb0000644000004100000410000000134312620101230022251 0ustar www-datawww-datamodule Hashie module Extensions module Dash module Coercion # Extends a Dash with the ability to define coercion for properties. def self.included(base) base.send :include, Hashie::Extensions::Coercion base.extend ClassMethods end module ClassMethods # Defines a property on the Dash. Options are the standard # Hashie::Dash#property options plus: # # * :coerce - The class into which you want the property coerced. def property(property_name, options = {}) super coerce_key property_name, options[:coerce] if options[:coerce] end end end end end end hashie-3.4.3/lib/hashie/extensions/dash/indifferent_access.rb0000644000004100000410000000176612620101230024277 0ustar www-datawww-datamodule Hashie module Extensions module Dash module IndifferentAccess def self.included(base) base.extend ClassMethods base.send :include, Hashie::Extensions::IndifferentAccess end module ClassMethods # Check to see if the specified property has already been # defined. def property?(name) name = name.to_s !!properties.find { |property| property.to_s == name } end def translation_exists?(name) name = name.to_s !!translations.keys.find { |key| key.to_s == name } end def transformed_property(property_name, value) transform = transforms[property_name] || transforms[:"#{property_name}"] transform.call(value) end def transformation_exists?(name) name = name.to_s !!transforms.keys.find { |key| key.to_s == name } end end end end end end hashie-3.4.3/lib/hashie/extensions/dash/property_translation.rb0000644000004100000410000001404412620101230024754 0ustar www-datawww-datamodule Hashie module Extensions module Dash # Extends a Dash with the ability to remap keys from a source hash. # # Property translation is useful when you need to read data from another # application -- such as a Java API -- where the keys are named # differently from Ruby conventions. # # == Example from inconsistent APIs # # class PersonHash < Hashie::Dash # include Hashie::Extensions::Dash::PropertyTranslation # # property :first_name, from :firstName # property :last_name, from: :lastName # property :first_name, from: :f_name # property :last_name, from: :l_name # end # # person = PersonHash.new(firstName: 'Michael', l_name: 'Bleigh') # person[:first_name] #=> 'Michael' # person[:last_name] #=> 'Bleigh' # # You can also use a lambda to translate the value. This is particularly # useful when you want to ensure the type of data you're wrapping. # # == Example using translation lambdas # # class DataModelHash < Hashie::Dash # include Hashie::Extensions::Dash::PropertyTranslation # # property :id, transform_with: ->(value) { value.to_i } # property :created_at, from: :created, with: ->(value) { Time.parse(value) } # end # # model = DataModelHash.new(id: '123', created: '2014-04-25 22:35:28') # model.id.class #=> Fixnum # model.created_at.class #=> Time module PropertyTranslation def self.included(base) base.instance_variable_set(:@transforms, {}) base.instance_variable_set(:@translations_hash, {}) base.extend(ClassMethods) base.send(:include, InstanceMethods) end module ClassMethods attr_reader :transforms, :translations_hash # Ensures that any inheriting classes maintain their translations. # # * :default - The class inheriting the translations. def inherited(klass) super klass.instance_variable_set(:@transforms, transforms.dup) klass.instance_variable_set(:@translations_hash, translations_hash.dup) end def permitted_input_keys @permitted_input_keys ||= properties.map { |property| inverse_translations.fetch property, property } end # Defines a property on the Trash. Options are as follows: # # * :default - Specify a default value for this property, to be # returned before a value is set on the property in a new Dash. # * :from - Specify the original key name that will be write only. # * :with - Specify a lambda to be used to convert value. # * :transform_with - Specify a lambda to be used to convert value # without using the :from option. It transform the property itself. def property(property_name, options = {}) super if options[:from] if property_name == options[:from] fail ArgumentError, "Property name (#{property_name}) and :from option must not be the same" end translations_hash[options[:from]] ||= {} translations_hash[options[:from]][property_name] = options[:with] || options[:transform_with] define_method "#{options[:from]}=" do |val| self.class.translations_hash[options[:from]].each do |name, with| self[name] = with.respond_to?(:call) ? with.call(val) : val end end else if options[:transform_with].respond_to? :call transforms[property_name] = options[:transform_with] end end end def transformed_property(property_name, value) transforms[property_name].call(value) end def transformation_exists?(name) transforms.key? name end def translation_exists?(name) translations_hash.key? name end def translations @translations ||= {}.tap do |h| translations_hash.each do |(property_name, property_translations)| if property_translations.size > 1 h[property_name] = property_translations.keys else h[property_name] = property_translations.keys.first end end end end def inverse_translations @inverse_translations ||= {}.tap do |h| translations_hash.each do |(property_name, property_translations)| property_translations.keys.each do |k| h[k] = property_name end end end end end module InstanceMethods # Sets a value on the Dash in a Hash-like way. # # Note: Only works on pre-existing properties. def []=(property, value) if self.class.translation_exists? property send("#{property}=", value) elsif self.class.transformation_exists? property super property, self.class.transformed_property(property, value) elsif property_exists? property super end end # Deletes any keys that have a translation def initialize_attributes(attributes) return unless attributes attributes_copy = attributes.dup.delete_if do |k, v| if self.class.translations_hash.include?(k) self[k] = v true end end super attributes_copy end # Raises an NoMethodError if the property doesn't exist def property_exists?(property) fail_no_property_error!(property) unless self.class.property?(property) true end end end end end end hashie-3.4.3/lib/hashie/extensions/ignore_undeclared.rb0000644000004100000410000000256112620101230023205 0ustar www-datawww-datamodule Hashie module Extensions # IgnoreUndeclared is a simple mixin that silently ignores # undeclared properties on initialization instead of # raising an error. This is useful when using a Trash to # capture a subset of a larger hash. # # Note that attempting to retrieve or set an undeclared property # will still raise a NoMethodError, even if a value for # that property was provided at initialization. # # @example # class Person < Trash # include Hashie::Extensions::IgnoreUndeclared # # property :first_name # property :last_name # end # # user_data = { # :first_name => 'Freddy', # :last_name => 'Nostrils', # :email => 'freddy@example.com' # } # # p = Person.new(user_data) # 'email' is silently ignored # # p.first_name # => 'Freddy' # p.last_name # => 'Nostrils' # p.email # => NoMethodError module IgnoreUndeclared def initialize_attributes(attributes) attributes.each_pair do |att, value| next unless self.class.property?(att) || (self.class.respond_to?(:translations) && self.class.translations.include?(att.to_sym)) self[att] = value end if attributes end def property_exists?(property) self.class.property?(property) end end end end hashie-3.4.3/lib/hashie/extensions/strict_key_access.rb0000644000004100000410000000344312620101230023235 0ustar www-datawww-datamodule Hashie module Extensions # SRP: This extension will fail an error whenever a key is accessed that does not exist in the hash. # # EXAMPLE: # # class StrictKeyAccessHash < Hash # include Hashie::Extensions::StrictKeyAccess # end # # >> hash = StrictKeyAccessHash[foo: "bar"] # => {:foo=>"bar"} # >> hash[:foo] # => "bar" # >> hash[:cow] # KeyError: key not found: :cow # # NOTE: For googlers coming from Python to Ruby, this extension makes a Hash behave more like a "Dictionary". # module StrictKeyAccess class DefaultError < StandardError def initialize(msg = 'Setting or using a default with Hashie::Extensions::StrictKeyAccess does not make sense', *args) super end end # NOTE: Defaults don't make any sense with a StrictKeyAccess. # NOTE: When key lookup fails a KeyError is raised. # # Normal: # # >> a = Hash.new(123) # => {} # >> a["noes"] # => 123 # # With StrictKeyAccess: # # >> a = StrictKeyAccessHash.new(123) # => {} # >> a["noes"] # KeyError: key not found: "noes" # def [](key) fetch(key) end def default(_ = nil) fail DefaultError end def default=(_) fail DefaultError end def default_proc fail DefaultError end def default_proc=(_) fail DefaultError end def key(value) result = super if result.nil? && (!key?(result) || self[result] != value) fail KeyError, "key not found with value of #{value.inspect}" else result end end end end end hashie-3.4.3/lib/hashie/dash.rb0000644000004100000410000001526712620101230016263 0ustar www-datawww-datarequire 'hashie/hash' require 'set' module Hashie # A Dash is a 'defined' or 'discrete' Hash, that is, a Hash # that has a set of defined keys that are accessible (with # optional defaults) and only those keys may be set or read. # # Dashes are useful when you need to create a very simple # lightweight data object that needs even fewer options and # resources than something like a DataMapper resource. # # It is preferrable to a Struct because of the in-class # API for defining properties as well as per-property defaults. class Dash < Hash include Hashie::Extensions::PrettyInspect alias_method :to_s, :inspect # Defines a property on the Dash. Options are # as follows: # # * :default - Specify a default value for this property, # to be returned before a value is set on the property in a new # Dash. # # * :required - Specify the value as required for this # property, to raise an error if a value is unset in a new or # existing Dash. If a Proc is provided, it will be run in the # context of the Dash instance. If a Symbol is provided, the # property it represents must not be nil. The property is only # required if the value is truthy. # # * :message - Specify custom error message for required property # def self.property(property_name, options = {}) properties << property_name if options.key?(:default) defaults[property_name] = options[:default] elsif defaults.key?(property_name) defaults.delete property_name end unless instance_methods.map(&:to_s).include?("#{property_name}=") define_method(property_name) { |&block| self.[](property_name, &block) } property_assignment = "#{property_name}=".to_sym define_method(property_assignment) { |value| self.[]=(property_name, value) } end if defined? @subclasses @subclasses.each { |klass| klass.property(property_name, options) } end condition = options.delete(:required) if condition message = options.delete(:message) || "is required for #{name}." required_properties[property_name] = { condition: condition, message: message } else fail ArgumentError, 'The :message option should be used with :required option.' if options.key?(:message) end end class << self attr_reader :properties, :defaults attr_reader :required_properties end instance_variable_set('@properties', Set.new) instance_variable_set('@defaults', {}) instance_variable_set('@required_properties', {}) def self.inherited(klass) super (@subclasses ||= Set.new) << klass klass.instance_variable_set('@properties', properties.dup) klass.instance_variable_set('@defaults', defaults.dup) klass.instance_variable_set('@required_properties', required_properties.dup) end # Check to see if the specified property has already been # defined. def self.property?(name) properties.include? name end # Check to see if the specified property is # required. def self.required?(name) required_properties.key? name end # You may initialize a Dash with an attributes hash # just like you would many other kinds of data objects. def initialize(attributes = {}, &block) super(&block) self.class.defaults.each_pair do |prop, value| self[prop] = begin val = value.dup val.is_a?(Proc) && val.arity > 0 ? val.call(self) : val rescue TypeError value end end initialize_attributes(attributes) assert_required_attributes_set! end alias_method :_regular_reader, :[] alias_method :_regular_writer, :[]= private :_regular_reader, :_regular_writer # Retrieve a value from the Dash (will return the # property's default value if it hasn't been set). def [](property) assert_property_exists! property value = super(property) # If the value is a lambda, proc, or whatever answers to call, eval the thing! if value.is_a? Proc self[property] = value.call # Set the result of the call as a value else yield value if block_given? value end end # Set a value on the Dash in a Hash-like way. Only works # on pre-existing properties. def []=(property, value) assert_property_required! property, value assert_property_exists! property super(property, value) end def merge(other_hash) new_dash = dup other_hash.each do |k, v| new_dash[k] = block_given? ? yield(k, self[k], v) : v end new_dash end def merge!(other_hash) other_hash.each do |k, v| self[k] = block_given? ? yield(k, self[k], v) : v end self end def replace(other_hash) other_hash = self.class.defaults.merge(other_hash) (keys - other_hash.keys).each { |key| delete(key) } other_hash.each { |key, value| self[key] = value } self end def update_attributes!(attributes) initialize_attributes(attributes) self.class.defaults.each_pair do |prop, value| self[prop] = begin value.dup rescue TypeError value end if self[prop].nil? end assert_required_attributes_set! end private def initialize_attributes(attributes) attributes.each_pair do |att, value| self[att] = value end if attributes end def assert_property_exists!(property) fail_no_property_error!(property) unless self.class.property?(property) end def assert_required_attributes_set! self.class.required_properties.each_key do |required_property| assert_property_set!(required_property) end end def assert_property_set!(property) fail_property_required_error!(property) if send(property).nil? && required?(property) end def assert_property_required!(property, value) fail_property_required_error!(property) if value.nil? && required?(property) end def fail_property_required_error!(property) fail ArgumentError, "The property '#{property}' #{self.class.required_properties[property][:message]}" end def fail_no_property_error!(property) fail NoMethodError, "The property '#{property}' is not defined for #{self.class.name}." end def required?(property) return false unless self.class.required?(property) condition = self.class.required_properties[property][:condition] case condition when Proc then !!(instance_exec(&condition)) when Symbol then !!(send(condition)) else !!(condition) end end end end hashie-3.4.3/lib/hashie.rb0000644000004100000410000000415312620101230015334 0ustar www-datawww-datarequire 'hashie/version' module Hashie autoload :Clash, 'hashie/clash' autoload :Dash, 'hashie/dash' autoload :Hash, 'hashie/hash' autoload :Mash, 'hashie/mash' autoload :Trash, 'hashie/trash' autoload :Rash, 'hashie/rash' module Extensions autoload :Coercion, 'hashie/extensions/coercion' autoload :DeepMerge, 'hashie/extensions/deep_merge' autoload :IgnoreUndeclared, 'hashie/extensions/ignore_undeclared' autoload :IndifferentAccess, 'hashie/extensions/indifferent_access' autoload :MergeInitializer, 'hashie/extensions/merge_initializer' autoload :MethodAccess, 'hashie/extensions/method_access' autoload :MethodQuery, 'hashie/extensions/method_access' autoload :MethodReader, 'hashie/extensions/method_access' autoload :MethodWriter, 'hashie/extensions/method_access' autoload :StringifyKeys, 'hashie/extensions/stringify_keys' autoload :SymbolizeKeys, 'hashie/extensions/symbolize_keys' autoload :DeepFetch, 'hashie/extensions/deep_fetch' autoload :DeepFind, 'hashie/extensions/deep_find' autoload :DeepLocate, 'hashie/extensions/deep_locate' autoload :PrettyInspect, 'hashie/extensions/pretty_inspect' autoload :KeyConversion, 'hashie/extensions/key_conversion' autoload :MethodAccessWithOverride, 'hashie/extensions/method_access' autoload :StrictKeyAccess, 'hashie/extensions/strict_key_access' module Parsers autoload :YamlErbParser, 'hashie/extensions/parsers/yaml_erb_parser' end module Dash autoload :IndifferentAccess, 'hashie/extensions/dash/indifferent_access' autoload :PropertyTranslation, 'hashie/extensions/dash/property_translation' autoload :Coercion, 'hashie/extensions/dash/coercion' end module Mash autoload :SafeAssignment, 'hashie/extensions/mash/safe_assignment' end end class << self include Hashie::Extensions::StringifyKeys::ClassMethods include Hashie::Extensions::SymbolizeKeys::ClassMethods end end hashie-3.4.3/metadata.yml0000644000004100000410000001267112620101230015307 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: hashie version: !ruby/object:Gem::Version version: 3.4.3 platform: ruby authors: - Michael Bleigh - Jerry Cheung autorequire: bindir: bin cert_chain: [] date: 2015-10-25 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '3.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '3.0' - !ruby/object:Gem::Dependency name: rspec-pending_for requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.1' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.1' description: Hashie is a collection of classes and mixins that make hashes more powerful. email: - michael@intridea.com - jollyjerry@gmail.com executables: [] extensions: [] extra_rdoc_files: [] files: - ".yardopts" - CHANGELOG.md - CONTRIBUTING.md - LICENSE - README.md - Rakefile - UPGRADING.md - hashie.gemspec - lib/hashie.rb - lib/hashie/clash.rb - lib/hashie/dash.rb - lib/hashie/extensions/coercion.rb - lib/hashie/extensions/dash/coercion.rb - lib/hashie/extensions/dash/indifferent_access.rb - lib/hashie/extensions/dash/property_translation.rb - lib/hashie/extensions/deep_fetch.rb - lib/hashie/extensions/deep_find.rb - lib/hashie/extensions/deep_locate.rb - lib/hashie/extensions/deep_merge.rb - lib/hashie/extensions/ignore_undeclared.rb - lib/hashie/extensions/indifferent_access.rb - lib/hashie/extensions/key_conversion.rb - lib/hashie/extensions/mash/safe_assignment.rb - lib/hashie/extensions/merge_initializer.rb - lib/hashie/extensions/method_access.rb - lib/hashie/extensions/parsers/yaml_erb_parser.rb - lib/hashie/extensions/pretty_inspect.rb - lib/hashie/extensions/strict_key_access.rb - lib/hashie/extensions/stringify_keys.rb - lib/hashie/extensions/symbolize_keys.rb - lib/hashie/hash.rb - lib/hashie/mash.rb - lib/hashie/rash.rb - lib/hashie/trash.rb - lib/hashie/version.rb - spec/hashie/clash_spec.rb - spec/hashie/dash_spec.rb - spec/hashie/extensions/autoload_spec.rb - spec/hashie/extensions/coercion_spec.rb - spec/hashie/extensions/dash/coercion_spec.rb - spec/hashie/extensions/dash/indifferent_access_spec.rb - spec/hashie/extensions/deep_fetch_spec.rb - spec/hashie/extensions/deep_find_spec.rb - spec/hashie/extensions/deep_locate_spec.rb - spec/hashie/extensions/deep_merge_spec.rb - spec/hashie/extensions/ignore_undeclared_spec.rb - spec/hashie/extensions/indifferent_access_spec.rb - spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb - spec/hashie/extensions/key_conversion_spec.rb - spec/hashie/extensions/mash/safe_assignment_spec.rb - spec/hashie/extensions/merge_initializer_spec.rb - spec/hashie/extensions/method_access_spec.rb - spec/hashie/extensions/strict_key_access_spec.rb - spec/hashie/extensions/stringify_keys_spec.rb - spec/hashie/extensions/symbolize_keys_spec.rb - spec/hashie/hash_spec.rb - spec/hashie/mash_spec.rb - spec/hashie/parsers/yaml_erb_parser_spec.rb - spec/hashie/rash_spec.rb - spec/hashie/trash_spec.rb - spec/hashie/version_spec.rb - spec/spec_helper.rb - spec/support/module_context.rb homepage: https://github.com/intridea/hashie licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.4.8 signing_key: specification_version: 4 summary: Your friendly neighborhood hash library. test_files: - spec/hashie/clash_spec.rb - spec/hashie/dash_spec.rb - spec/hashie/extensions/autoload_spec.rb - spec/hashie/extensions/coercion_spec.rb - spec/hashie/extensions/dash/coercion_spec.rb - spec/hashie/extensions/dash/indifferent_access_spec.rb - spec/hashie/extensions/deep_fetch_spec.rb - spec/hashie/extensions/deep_find_spec.rb - spec/hashie/extensions/deep_locate_spec.rb - spec/hashie/extensions/deep_merge_spec.rb - spec/hashie/extensions/ignore_undeclared_spec.rb - spec/hashie/extensions/indifferent_access_spec.rb - spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb - spec/hashie/extensions/key_conversion_spec.rb - spec/hashie/extensions/mash/safe_assignment_spec.rb - spec/hashie/extensions/merge_initializer_spec.rb - spec/hashie/extensions/method_access_spec.rb - spec/hashie/extensions/strict_key_access_spec.rb - spec/hashie/extensions/stringify_keys_spec.rb - spec/hashie/extensions/symbolize_keys_spec.rb - spec/hashie/hash_spec.rb - spec/hashie/mash_spec.rb - spec/hashie/parsers/yaml_erb_parser_spec.rb - spec/hashie/rash_spec.rb - spec/hashie/trash_spec.rb - spec/hashie/version_spec.rb - spec/spec_helper.rb - spec/support/module_context.rb hashie-3.4.3/.yardopts0000644000004100000410000000001412620101230014636 0ustar www-datawww-data-m markdown hashie-3.4.3/CONTRIBUTING.md0000644000004100000410000000636712620101230015242 0ustar www-datawww-dataContributing to Hashie ====================== Hashie is work of [many contributors](https://github.com/intridea/hashie/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/intridea/hashie/pulls), [propose features and discuss issues](https://github.com/intridea/hashie/issues). #### Fork the Project Fork the [project on Github](https://github.com/intridea/hashie) and check out your copy. ``` git clone https://github.com/contributor/hashie.git cd hashie git remote add upstream https://github.com/intridea/hashie.git ``` #### Create a Topic Branch Make sure your fork is up-to-date and create a topic branch for your feature or bug fix. ``` git checkout master git pull upstream master git checkout -b my-feature-branch ``` #### Bundle Install and Test Ensure that you can build the project and run tests. ``` bundle install bundle exec rake ``` #### Write Tests Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. Add to [spec/hashie](spec/hashie). We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix. #### Write Code Implement your feature or bug fix. Ruby style is enforced with [Rubocop](https://github.com/bbatsov/rubocop), run `bundle exec rubocop` and fix any style issues highlighted. Make sure that `bundle exec rake` completes without errors. #### Write Documentation Document any external behavior in the [README](README.md). #### Update Changelog Add a line to [CHANGELOG](CHANGELOG.md) under *Next Release*. Make it look like every other line, including your name and link to your Github account. #### Commit Changes Make sure git knows your name and email address: ``` git config --global user.name "Your Name" git config --global user.email "contributor@example.com" ``` Writing good commit logs is important. A commit log should describe what changed and why. ``` git add ... git commit ``` #### Push ``` git push origin my-feature-branch ``` #### Make a Pull Request Go to https://github.com/contributor/hashie and select your feature branch. Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days. #### Rebase If you've been working on a change for a while, rebase with upstream/master. ``` git fetch upstream git rebase upstream/master git push origin my-feature-branch -f ``` #### Update CHANGELOG Again Update the [CHANGELOG](CHANGELOG.md) with the pull request number. A typical entry looks as follows. ``` * [#123](https://github.com/intridea/hashie/pull/123): Reticulated splines - [@contributor](https://github.com/contributor). ``` Amend your previous commit and force push the changes. ``` git commit --amend git push origin my-feature-branch -f ``` #### Check on Your Pull Request Go back to your pull request after a few minutes and see whether it passed muster with Travis-CI. Everything should look green, otherwise fix issues and amend your commit as described above. #### Be Patient It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang on there! #### Thank You Please do know that we really appreciate and value your time and work. We love you, really. hashie-3.4.3/LICENSE0000644000004100000410000000206412620101230014004 0ustar www-datawww-dataCopyright (c) 2009 Intridea, Inc., and Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. hashie-3.4.3/CHANGELOG.md0000644000004100000410000003753612620101230014624 0ustar www-datawww-data## 3.4.2 (10/25/2015) * [#314](https://github.com/intridea/hashie/pull/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](https://github.com/intridea/hashie/pull/304): Ensured compatibility of `Hash` extensions with singleton objects - [@regexident](https://github.com/regexident). * [#306](https://github.com/intridea/hashie/pull/306): Added `Hashie::Extensions::Dash::Coercion` - [@marshall-lee](https://github.com/marshall-lee). * [#310](https://github.com/intridea/hashie/pull/310): Fixed `Hashie::Extensions::SafeAssignment` bug with private methods - [@marshall-lee](https://github.com/marshall-lee). * [#313](https://github.com/intridea/hashie/pull/313): Restrict pending spec to only Ruby versions 2.2.0-2.2.2 - [@pboling](https://github.com/pboling). * [#315](https://github.com/intridea/hashie/pull/315): Default `bin/` scripts: `console` and `setup` - [@pboling](https://github.com/pboling). ## 3.4.2 (6/2/2015) * [#292](https://github.com/intridea/hashie/pull/292): Removed `Mash#id` and `Mash#type` - [@jrochkind](https://github.com/jrochkind). * [#297](https://github.com/intridea/hashie/pull/297): Extracted `Trash`'s behavior into a new `Dash::PropertyTranslation` extension - [@michaelherold](https://github.com/michaelherold). ## 3.4.1 (3/31/2015) * [#269](https://github.com/intridea/hashie/pull/272): Added Hashie::Extensions::DeepLocate - [@msievers](https://github.com/msievers). * [#270](https://github.com/intridea/hashie/pull/277): Fixed ArgumentError raised when using IndifferentAccess and HashWithIndifferentAccess - [@gardenofwine](https://github.com/gardenofwine). * [#281](https://github.com/intridea/hashie/pull/281): Added #reverse_merge to Mash to override ActiveSupport's version - [@mgold](https://github.com/mgold). * [#282](https://github.com/intridea/hashie/pull/282): Fixed coercions in a subclass accumulating in the superclass - [@maxlinc](https://github.com/maxlinc), [@martinstreicher](https://github.com/martinstreicher). ## 3.4.0 (2/02/2015) * [#271](https://github.com/intridea/hashie/pull/271): Added ability to define defaults based on current hash - [@gregory](https://github.com/gregory). * [#247](https://github.com/intridea/hashie/pull/247): Fixed #stringify_keys and #symbolize_keys collision with ActiveSupport - [@bartoszkopinski](https://github.com/bartoszkopinski). * [#249](https://github.com/intridea/hashie/pull/249): SafeAssignment will now also protect hash-style assignments - [@jrochkind](https://github.com/jrochkind). * [#251](https://github.com/intridea/hashie/pull/251): Added block support to indifferent access #fetch - [@jgraichen](https://github.com/jgraichen). * [#252](https://github.com/intridea/hashie/pull/252): Added support for conditionally required Hashie::Dash attributes - [@ccashwell](https://github.com/ccashwell). * [#256](https://github.com/intridea/hashie/pull/256): Inherit key coercions - [@Erol](https://github.com/Erol). * [#259](https://github.com/intridea/hashie/pull/259): Fixed handling of default proc values in Mash - [@Erol](https://github.com/Erol). * [#260](https://github.com/intridea/hashie/pull/260): Added block support to Extensions::DeepMerge - [@galathius](https://github.com/galathius). * [#254](https://github.com/intridea/hashie/pull/254): Added public utility methods for stringify and symbolize keys - [@maxlinc](https://github.com/maxlinc). * [#261](https://github.com/intridea/hashie/pull/261): Fixed bug where Dash.property modifies argument object - [@d-tw](https://github.com/d-tw). * [#264](https://github.com/intridea/hashie/pull/264): Methods such as abc? return true/false with Hashie::Extensions::MethodReader - [@Zloy](https://github.com/Zloy). * [#269](https://github.com/intridea/hashie/pull/269): Add #extractable_options? so ActiveSupport Array#extract_options! can extract it - [@ridiculous](https://github.com/ridiculous). ## 3.3.2 (11/26/2014) * [#233](https://github.com/intridea/hashie/pull/233): Custom error messages for required properties in Hashie::Dash subclasses - [@joss](https://github.com/joss). * [#231](https://github.com/intridea/hashie/pull/231): Added support for coercion on class type that inherit from Hash - [@gregory](https://github.com/gregory). * [#228](https://github.com/intridea/hashie/pull/228): Made Hashie::Extensions::Parsers::YamlErbParser pass template filename to ERB - [@jperville](https://github.com/jperville). * [#224](https://github.com/intridea/hashie/pull/224): Merging Hashie::Mash now correctly only calls the block on duplicate values - [@amysutedja](https://github.com/amysutedja). * [#221](https://github.com/intridea/hashie/pull/221): Reduce amount of allocated objects on calls with suffixes in Hashie::Mash - [@kubum](https://github.com/kubum). * [#245](https://github.com/intridea/hashie/pull/245): Added Hashie::Extensions::MethodAccessWithOverride to autoloads - [@Fritzinger](https://github.com/Fritzinger). ## 3.3.1 (8/26/2014) * [#183](https://github.com/intridea/hashie/pull/183): Added Mash#load with YAML file support - [@gregory](https://github.com/gregory). * [#195](https://github.com/intridea/hashie/pull/195): Ensure that the same object is returned after injecting IndifferentAccess - [@michaelherold](https://github.com/michaelherold). * [#201](https://github.com/intridea/hashie/pull/201): Hashie::Trash transforms can be inherited - [@fobocaster](https://github.com/fobocaster). * [#189](https://github.com/intridea/hashie/pull/189): Added Rash#fetch - [@medcat](https://github.com/medcat). * [#200](https://github.com/intridea/hashie/pull/200): Improved coercion: primitives and error handling - [@maxlinc](https://github.com/maxlinc). * [#204](https://github.com/intridea/hashie/pull/204): Added Hashie::Extensions::MethodOverridingWriter and MethodAccessWithOverride - [@michaelherold](https://github.com/michaelherold). * [#205](http://github.com/intridea/hashie/pull/205): Added Hashie::Extensions::Mash::SafeAssignment - [@michaelherold](https://github.com/michaelherold). * [#206](http://github.com/intridea/hashie/pull/206): Fixed stack overflow from repetitively including coercion in subclasses - [@michaelherold](https://github.com/michaelherold). * [#207](http://github.com/intridea/hashie/pull/207): Fixed inheritance of transformations in Trash - [@fobocaster](https://github.com/fobocaster). * [#209](http://github.com/intridea/hashie/pull/209): Added Hashie::Extensions::DeepFind - [@michaelherold](https://github.com/michaelherold). * [#69](https://github.com/intridea/hashie/pull/69): Fixed regression in assigning multiple properties in Hashie::Trash - [@michaelherold](https://github.com/michaelherold), [@einzige](https://github.com/einzige), [@dblock](https://github.com/dblock). ## 3.2.0 (7/10/2014) * [#164](https://github.com/intridea/hashie/pull/164), [#165](https://github.com/intridea/hashie/pull/165), [#166](https://github.com/intridea/hashie/pull/166): Fixed stack overflow when coercing mashes that contain ActiveSupport::HashWithIndifferentAccess values - [@numinit](https://github.com/numinit), [@kgrz](https://github.com/kgrz). * [#177](https://github.com/intridea/hashie/pull/177): Added support for coercing enumerables and collections - [@gregory](https://github.com/gregory). * [#179](https://github.com/intridea/hashie/pull/179): Mash#values_at will convert each key before doing the lookup - [@nahiluhmot](https://github.com/nahiluhmot). * [#184](https://github.com/intridea/hashie/pull/184): Allow ranges on Rash to match all Numeric types - [@medcat](https://github.com/medcat). * [#187](https://github.com/intridea/hashie/pull/187): Automatically require version - [@medcat](https://github.com/medcat). * [#190](https://github.com/intridea/hashie/issues/190): Fixed `coerce_key` with `from` Trash feature and Coercion extension - [@gregory](https://github.com/gregory). * [#192](https://github.com/intridea/hashie/pull/192): Fixed StringifyKeys#stringify_keys! to recursively stringify keys of embedded ::Hash types - [@dblock](https://github.com/dblock). ## 3.1.0 (6/25/2014) * [#169](https://github.com/intridea/hashie/pull/169): Hash#to_hash will also convert nested objects that implement to_hash - [@gregory](https://github.com/gregory). * [#171](https://github.com/intridea/hashie/pull/171): Include Trash and Dash class name when raising `NoMethodError` - [@gregory](https://github.com/gregory). * [#172](https://github.com/intridea/hashie/pull/172): Added Dash and Trash#update_attributes! - [@gregory](https://github.com/gregory). * [#173](https://github.com/intridea/hashie/pull/173): Auto include Dash::IndifferentAccess when IndiferentAccess is included in Dash - [@gregory](https://github.com/gregory). * [#174](https://github.com/intridea/hashie/pull/174): Fixed `from` and `transform_with` Trash features when IndifferentAccess is included - [@gregory](https://github.com/gregory). ## 3.0.0 (6/3/2014) **Note:** This version introduces several backward incompatible API changes. See [UPGRADING](UPGRADING.md) for details. * [#150](https://github.com/intridea/hashie/pull/159): Handle nil intermediate object on deep fetch - [@stephenaument](https://github.com/stephenaument). * [#146](https://github.com/intridea/hashie/issues/146): Mash#respond_to? inconsistent with #method_missing and does not respond to #permitted? - [@dblock](https://github.com/dblock). * [#152](https://github.com/intridea/hashie/pull/152): Do not convert keys to String in Hashie::Dash and Hashie::Trash, use Hashie::Extensions::Dash::IndifferentAccess to achieve backward compatible behavior - [@dblock](https://github.com/dblock). * [#152](https://github.com/intridea/hashie/pull/152): Do not automatically stringify keys in Hashie::Hash#to_hash, pass `:stringify_keys` to achieve backward compatible behavior - [@dblock](https://github.com/dblock). * [#148](https://github.com/intridea/hashie/pull/148): Consolidated Hashie::Hash#stringify_keys implementation - [@dblock](https://github.com/dblock). * [#149](https://github.com/intridea/hashie/issues/149): Allow IgnoreUndeclared and DeepMerge to be used with undeclared properties - [@jhaesus](https://github.com/jhaesus). ## 2.1.2 (5/12/2014) * [#169](https://github.com/intridea/hashie/pull/169): Hash#to_hash will also convert nested objects that implement `to_hash` - [@gregory](https://github.com/gregory). ## 2.1.1 (4/12/2014) * [#144](https://github.com/intridea/hashie/issues/144): Fixed regression invoking `to_hash` with no parameters - [@mbleigh](https://github.com/mbleigh). ## 2.1.0 (4/6/2014) * [#134](https://github.com/intridea/hashie/pull/134): Add deep_fetch extension for nested access - [@tylerdooling](https://github.com/tylerdooling). * Removed support for Ruby 1.8.7 - [@dblock](https://github.com/dblock). * Ruby style now enforced with Rubocop - [@dblock](https://github.com/dblock). * [#138](https://github.com/intridea/hashie/pull/138): Added Hashie::Rash, a hash whose keys can be regular expressions or ranges - [@epitron](https://github.com/epitron). * [#131](https://github.com/intridea/hashie/pull/131): Added IgnoreUndeclared, an extension to silently ignore undeclared properties at intialization - [@righi](https://github.com/righi). * [#136](https://github.com/intridea/hashie/issues/136): Removed Hashie::Extensions::Structure - [@markiz](https://github.com/markiz). * [#107](https://github.com/intridea/hashie/pull/107): Fixed excessive value conversions, poor performance of deep merge in Hashie::Mash - [@davemitchell](https://github.com/dblock), [@dblock](https://github.com/dblock). * [#69](https://github.com/intridea/hashie/issues/69): Fixed assigning multiple properties in Hashie::Trash - [@einzige](https://github.com/einzige). * [#100](https://github.com/intridea/hashie/pull/100): IndifferentAccess#store will respect indifference - [@jrochkind](https://github.com/jrochkind). * [#103](https://github.com/intridea/hashie/pull/103): Fixed support for Hashie::Dash properties that end in bang - [@thedavemarshall](https://github.com/thedavemarshall). * [89](https://github.com/intridea/hashie/issues/89): Do not respond to every method with suffix in Hashie::Mash, fixes Rails strong_parameters - [@Maxim-Filimonov](https://github.com/Maxim-Filimonov). * [#110](https://github.com/intridea/hashie/pull/110): Correctly use Hash#default from Mash#method_missing - [@ryansouza](https://github.com/ryansouza). * [#120](https://github.com/intridea/hashie/pull/120): Pass options to recursive to_hash calls - [@pwillett](https://github.com/pwillett). * [#113](https://github.com/intridea/hashie/issues/113): Fixed Hash#merge with Hashie::Dash - [@spencer1248](https://github.com/spencer1248). * [#99](https://github.com/intridea/hashie/issues/99): Hash#deep_merge raises errors when it encounters integers - [@defsprite](https://github.com/defsprite). * [#133](https://github.com/intridea/hashie/pull/133): Fixed Hash##to_hash with symbolize_keys - [@mhuggins](https://github.com/mhuggins). * [#130](https://github.com/intridea/hashie/pull/130): IndifferentAccess now works without MergeInitializer - [@npj](https://github.com/npj). * [#111](https://github.com/intridea/hashie/issues/111): Trash#translations correctly maps original to translated names - [@artm](https://github.com/artm). * [#129](https://github.com/intridea/hashie/pull/129): Added Trash#permitted_input_keys and inverse_translations - [@artm](https://github.com/artm). ## 2.0.5 * [#96](https://github.com/intridea/hashie/pull/96): Make coercion work better with non-symbol keys in Hashie::Mash - [@wapcaplet](https://github.com/wapcaplet). ## 2.0.4 * [#04](https://github.com/intridea/hashie/pull/94): Make #fetch method consistent with normal Hash - [@markiz](https://github.com/markiz). * [#90](https://github.com/intridea/hashie/pull/90): Various doc tweaks - [@craiglittle](https://github.com/craiglittle). ## 2.0.3 * [#88](https://github.com/intridea/hashie/pull/88): Hashie::Mash.new(abc: true).respond_to?(:abc?) works - [@7even](https://github.com/7even). * [#68](https://github.com/intridea/hashie/pull/68): Fix #replace - [@jimeh](https://github.com/jimeh). ## 2.0.2 * [#85](https://github.com/intridea/hashie/pull/85): adding symbolize_keys back to to_hash - [@cromulus](https://github.com/cromulus). ## 2.0.1 * [#81](https://github.com/intridea/hashie/pull/81): remove Mash#object_id override - [@matschaffer](https://github.com/matschaffer). * Gem cleanup: removed VERSION, Gemfile.lock [@jch](https://github.com/jch), [@mbleigh](https://github.com/mbleigh). ## 2.0.0 * [#72](https://github.com/intridea/hashie/pull/72): Updated gemspec with license info - [@jordimassaguerpla](https://github.com/jordimassaguerpla). * [#27](https://github.com/intridea/hashie/pull/27): Initialized with merge coerces values - [@mattfawcett](https://github.com/mattfawcett). * [#28](https://github.com/intridea/hashie/pull/28): Hashie::Extensions::Coercion coerce_keys takes arguments - [@mattfawcett](https://github.com/mattfawcett). * [#39](https://github.com/intridea/hashie/pull/39): Trash removes translated values on initialization - [@sleverbor](https://github.com/sleverbor). * [#66](https://github.com/intridea/hashie/pull/66): Mash#fetch works with symbol or string keys - [@arthwood](https://github.com/arthwood). * [#49](https://github.com/intridea/hashie/pull/49): Hashie::Hash inherits from ::Hash to avoid ambiguity - [@meh](https://github.com/meh), [@orend](https://github.com/orend). * [#62](https://github.com/intridea/hashie/pull/62): update respond_to? method signature to match ruby core definition - [@dlupu](https://github.com/dlupu). * [#41](https://github.com/intridea/hashie/pull/41): DeepMerge extension - [@nashby](https://github.com/nashby). * [#63](https://github.com/intridea/hashie/pull/63): Dash defaults are dup'ed before assigned - [@ohrite](https://github.com/ohrite). * [#77](https://github.com/intridea/hashie/pull/77): Remove id, type, and object_id as special allowable keys [@jch](https://github.com/jch). * [#78](https://github.com/intridea/hashie/pull/78): Merge and update accepts a block - [@jch](https://github.com/jch). hashie-3.4.3/README.md0000644000004100000410000005774712620101230014300 0ustar www-datawww-data# Hashie [![Join the chat at https://gitter.im/intridea/hashie](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/intridea/hashie?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Gem Version](http://img.shields.io/gem/v/hashie.svg)](http://badge.fury.io/rb/hashie) [![Build Status](http://img.shields.io/travis/intridea/hashie.svg)](https://travis-ci.org/intridea/hashie) [![Dependency Status](https://gemnasium.com/intridea/hashie.svg)](https://gemnasium.com/intridea/hashie) [![Code Climate](https://codeclimate.com/github/intridea/hashie.svg)](https://codeclimate.com/github/intridea/hashie) [![Coverage Status](https://codeclimate.com/github/intridea/hashie/badges/coverage.svg)](https://codeclimate.com/github/intridea/hashie) Hashie is a growing collection of tools that extend Hashes and make them more useful. ## Installation Hashie is available as a RubyGem: ```bash $ gem install hashie ``` ## Stable Release You're reading the documentation for the stable release [3.4.3](https://github.com/intridea/hashie/blob/v3.4.3/README.md). ## Hash Extensions The library is broken up into a number of atomically includeable Hash extension modules as described below. This provides maximum flexibility for users to mix and match functionality while maintaining feature parity with earlier versions of Hashie. Any of the extensions listed below can be mixed into a class by `include`-ing `Hashie::Extensions::ExtensionName`. ### Coercion Coercions allow you to set up "coercion rules" based either on the key or the value type to massage data as it's being inserted into the Hash. Key coercions might be used, for example, in lightweight data modeling applications such as an API client: ```ruby class Tweet < Hash include Hashie::Extensions::Coercion coerce_key :user, User end user_hash = { name: "Bob" } Tweet.new(user: user_hash) # => automatically calls User.coerce(user_hash) or # User.new(user_hash) if that isn't present. ``` Value coercions, on the other hand, will coerce values based on the type of the value being inserted. This is useful if you are trying to build a Hash-like class that is self-propagating. ```ruby class SpecialHash < Hash include Hashie::Extensions::Coercion coerce_value Hash, SpecialHash def initialize(hash = {}) super hash.each_pair do |k,v| self[k] = v end end end ``` ### Coercing Collections ```ruby class Tweet < Hash include Hashie::Extensions::Coercion coerce_key :mentions, Array[User] coerce_key :friends, Set[User] end user_hash = { name: "Bob" } mentions_hash= [user_hash, user_hash] friends_hash = [user_hash] tweet = Tweet.new(mentions: mentions_hash, friends: friends_hash) # => automatically calls User.coerce(user_hash) or # User.new(user_hash) if that isn't present on each element of the array tweet.mentions.map(&:class) # => [User, User] tweet.friends.class # => Set ``` ### Coercing Hashes ```ruby class Relation def initialize(string) @relation = string end end class Tweet < Hash include Hashie::Extensions::Coercion coerce_key :relations, Hash[User => Relation] end user_hash = { name: "Bob" } relations_hash= { user_hash => "father", user_hash => "friend" } tweet = Tweet.new(relations: relations_hash) tweet.relations.map { |k,v| [k.class, v.class] } # => [[User, Relation], [User, Relation]] tweet.relations.class # => Hash # => automatically calls User.coerce(user_hash) on each key # and Relation.new on each value since Relation doesn't define the `coerce` class method ``` ### Coercing Core Types Hashie handles coercion to the following by using standard conversion methods: | type | method | |----------|----------| | Integer | `#to_i` | | Float | `#to_f` | | Complex | `#to_c` | | Rational | `#to_r` | | String | `#to_s` | | Symbol | `#to_sym`| **Note**: The standard Ruby conversion methods are less strict than you may assume. For example, `:foo.to_i` raises an error but `"foo".to_i` returns 0. You can also use coerce from the following supertypes with `coerce_value`: - Integer - Numeric Hashie does not have built-in support for coercion boolean values, since Ruby does not have a built-in boolean type or standard method for to a boolean. You can coerce to booleans using a custom proc. ### Coercion Proc You can use a custom coercion proc on either `#coerce_key` or `#coerce_value`. This is useful for coercing to booleans or other simple types without creating a new class and `coerce` method. For example: ```ruby class Tweet < Hash include Hashie::Extensions::Coercion coerce_key :retweeted, ->(v) do case v when String return !!(v =~ /^(true|t|yes|y|1)$/i) when Numeric return !v.to_i.zero? else return v == true end end end ``` #### A note on circular coercion Since `coerce_key` is a class-level method, you cannot have circular coercion without the use of a proc. For example: ```ruby class CategoryHash < Hash include Hashie::Extensions::Coercion include Hashie::Extensions::MergeInitializer coerce_key :products, Array[ProductHash] end class ProductHash < Hash include Hashie::Extensions::Coercion include Hashie::Extensions::MergeInitializer coerce_key :categories, Array[CategoriesHash] end ``` This will fail with a `NameError` for `CategoryHash::ProductHash` because `ProductHash` is not defined at the point that `coerce_key` is happening for `CategoryHash`. To work around this, you can use a coercion proc. For example, you could do: ```ruby class CategoryHash < Hash # ... coerce_key :products, ->(value) do return value.map { |v| ProductHash.new(v) } if value.respond_to?(:map) ProductHash.new(value) end end ``` ### KeyConversion The KeyConversion extension gives you the convenience methods of `symbolize_keys` and `stringify_keys` along with their bang counterparts. You can also include just stringify or just symbolize with `Hashie::Extensions::StringifyKeys` or `Hashie::Extensions::SymbolizeKeys`. Hashie also has a utility method for converting keys on a Hash without a mixin: ```ruby Hashie.symbolize_keys! hash # => Symbolizes keys of hash. Hashie.symbolize_keys hash # => Returns a copy of hash with keys symbolized. Hashie.stringify_keys! hash # => Stringifies keys of hash. Hashie.stringify_keys hash # => Returns a copy of hash with keys stringified. ``` ### MergeInitializer The MergeInitializer extension simply makes it possible to initialize a Hash subclass with another Hash, giving you a quick short-hand. ### MethodAccess The MethodAccess extension allows you to quickly build method-based reading, writing, and querying into your Hash descendant. It can also be included as individual modules, i.e. `Hashie::Extensions::MethodReader`, `Hashie::Extensions::MethodWriter` and `Hashie::Extensions::MethodQuery`. ```ruby class MyHash < Hash include Hashie::Extensions::MethodAccess end h = MyHash.new h.abc = 'def' h.abc # => 'def' h.abc? # => true ``` ### MethodAccessWithOverride The MethodAccessWithOverride extension is like the MethodAccess extension, except that it allows you to override Hash methods. It aliases any overridden method with two leading underscores. To include only this overriding functionality, you can include the single module `Hashie::Extensions::MethodOverridingWriter`. ```ruby class MyHash < Hash include Hashie::Extensions::MethodAccess end class MyOverridingHash < Hash include Hashie::Extensions::MethodAccessWithOverride end non_overriding = MyHash.new non_overriding.zip = 'a-dee-doo-dah' non_overriding.zip #=> [[['zip', 'a-dee-doo-dah']]] overriding = MyOverridingHash.new overriding.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 instantly give you indifferent access to your Hash subclass. This works just like the params hash in Rails and other frameworks where whether you provide symbols or strings to access keys, you will get the same results. A unique feature of Hashie's IndifferentAccess mixin is that it will inject itself recursively into subhashes *without* reinitializing the hash in question. This means you can safely merge together indifferent and non-indifferent hashes arbitrarily deeply without worrying about whether you'll be able to `hash[:other][:another]` properly. ### IgnoreUndeclared This extension can be mixed in to silently ignore undeclared properties on initialization instead of raising an error. This is useful when using a Trash to capture a subset of a larger hash. ```ruby class Person < Trash include Hashie::Extensions::IgnoreUndeclared property :first_name property :last_name end user_data = { first_name: 'Freddy', last_name: 'Nostrils', email: 'freddy@example.com' } p = Person.new(user_data) # 'email' is silently ignored p.first_name # => 'Freddy' p.last_name # => 'Nostrils' p.email # => NoMethodError ``` ### DeepMerge This extension allow you to easily include a recursive merging system to any Hash descendant: ```ruby class MyHash < Hash include Hashie::Extensions::DeepMerge end h1 = MyHash[{ x: { y: [4,5,6] }, z: [7,8,9] }] h2 = MyHash[{ x: { y: [7,8,9] }, z: "xyz" }] h1.deep_merge(h2) # => { x: { y: [7, 8, 9] }, z: "xyz" } h2.deep_merge(h1) # => { x: { y: [4, 5, 6] }, z: [7, 8, 9] } ``` Like with Hash#merge in the standard library, a block can be provided to merge values: ```ruby class MyHash < Hash include Hashie::Extensions::DeepMerge end h1 = MyHash[{ a: 100, b: 200, c: { c1: 100 } }] h2 = MyHash[{ b: 250, c: { c1: 200 } }] h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val } # => { a: 100, b: 450, c: { c1: 300 } } ``` ### DeepFetch This extension can be mixed in to provide for safe and concise retrieval of deeply nested hash values. In the event that the requested key does not exist a block can be provided and its value will be returned. Though this is a hash extension, it conveniently allows for arrays to be present in the nested structure. This feature makes the extension particularly useful for working with JSON API responses. ```ruby user = { name: { first: 'Bob', last: 'Boberts' }, groups: [ { name: 'Rubyists' }, { name: 'Open source enthusiasts' } ] } user.extend Hashie::Extensions::DeepFetch user.deep_fetch :name, :first # => 'Bob' user.deep_fetch :name, :middle # => 'KeyError: Could not fetch middle' # using a default block user.deep_fetch(:name, :middle) { |key| 'default' } # => 'default' # a nested array user.deep_fetch :groups, 1, :name # => 'Open source enthusiasts' ``` ### DeepFind This extension can be mixed in to provide for concise searching for keys within a deeply nested hash. It can also search through any Enumerable contained within the hash for objects with the specified key. Note: The searches are depth-first, so it is not guaranteed that a shallowly nested value will be found before a deeply nested value. ```ruby user = { name: { first: 'Bob', last: 'Boberts' }, groups: [ { name: 'Rubyists' }, { name: 'Open source enthusiasts' } ] } user.extend Hashie::Extensions::DeepFind user.deep_find(:name) #=> { first: 'Bob', last: 'Boberts' } user.deep_detect(:name) #=> { first: 'Bob', last: 'Boberts' } user.deep_find_all(:name) #=> [{ first: 'Bob', last: 'Boberts' }, 'Rubyists', 'Open source enthusiasts'] user.deep_select(:name) #=> [{ first: 'Bob', last: 'Boberts' }, 'Rubyists', 'Open source enthusiasts'] ``` ### DeepLocate This extension can be mixed in to provide a depth first search based search for enumerables matching a given comparator callable. It returns all enumerables which contain at least one element, for which the given comparator returns ```true```. Because the container objects are returned, the result elements can be modified in place. This way, one can perform modifications on deeply nested hashes without the need to know the exact paths. ```ruby books = [ { title: "Ruby for beginners", pages: 120 }, { title: "CSS for intermediates", pages: 80 }, { title: "Collection of ruby books", books: [ { title: "Ruby for the rest of us", pages: 576 } ] } ] books.extend(Hashie::Extensions::DeepLocate) # for ruby 1.9 leave *no* space between the lambda rocket and the braces # http://ruby-journal.com/becareful-with-space-in-lambda-hash-rocket-syntax-between-ruby-1-dot-9-and-2-dot-0/ books.deep_locate -> (key, value, object) { key == :title && value.include?("Ruby") } # => [{:title=>"Ruby for beginners", :pages=>120}, {:title=>"Ruby for the rest of us", :pages=>576}] books.deep_locate -> (key, value, object) { key == :pages && value <= 120 } # => [{:title=>"Ruby for beginners", :pages=>120}, {:title=>"CSS for intermediates", :pages=>80}] ``` ## StrictKeyAccess This extension can be mixed in to allow a Hash to raise an error when attempting to extract a value using a non-existent key. ### Example: ```ruby class StrictKeyAccessHash < Hash include Hashie::Extensions::StrictKeyAccess end >> hash = StrictKeyAccessHash[foo: "bar"] => {:foo=>"bar"} >> hash[:foo] => "bar" >> hash[:cow] KeyError: key not found: :cow ``` ## Mash Mash is an extended Hash that gives simple pseudo-object functionality that can be built from hashes and easily extended. It is intended to give the user easier access to the objects within the Mash through a property-like syntax, while still retaining all Hash functionality. ### Example: ```ruby mash = Hashie::Mash.new mash.name? # => false mash.name # => nil mash.name = "My Mash" mash.name # => "My Mash" mash.name? # => true mash.inspect # => mash = Hashie::Mash.new # use bang methods for multi-level assignment mash.author!.name = "Michael Bleigh" mash.author # => mash = Hashie::Mash.new # use under-bang methods for multi-level testing mash.author_.name? # => false mash.inspect # => ``` **Note:** The `?` method will return false if a key has been set to false or nil. In order to check if a key has been set at all, use the `mash.key?('some_key')` method instead. Please note that a Mash will not override methods through the use of the property-like syntax. This can lead to confusion if you expect to be able to access a Mash value through the property-like syntax for a key that conflicts with a method name. However, it protects users of your library from the unexpected behavior of those methods being overridden behind the scenes. ### Example: ```ruby mash = Hashie::Mash.new mash.name = "My Mash" mash.zip = "Method Override?" mash.zip # => [[["name", "My Mash"]], [["zip", "Method Override?"]]] ``` Mash allows you also to transform any files into a Mash objects. ### Example: ```yml #/etc/config/settings/twitter.yml development: api_key: 'api_key' production: api_key: <%= ENV['API_KEY'] %> #let's say that ENV['API_KEY'] is set to 'abcd' ``` ```ruby mash = Mash.load('settings/twitter.yml') mash.development.api_key # => 'localhost' mash.development.api_key = "foo" # => <# RuntimeError can't modify frozen ...> mash.development.api_key? # => true ``` You can access a Mash from another class: ```ruby mash = Mash.load('settings/twitter.yml')[ENV['RACK_ENV']] Twitter.extend mash.to_module # NOTE: if you want another name than settings, call: to_module('my_settings') Twitter.settings.api_key # => 'abcd' ``` You can use another parser (by default: YamlErbParser): ``` #/etc/data/user.csv id | name | lastname ---|------------- | ------------- 1 |John | Doe 2 |Laurent | Garnier ``` ```ruby mash = Mash.load('data/user.csv', parser: MyCustomCsvParser) # => { 1 => { name: 'John', lastname: 'Doe'}, 2 => { name: 'Laurent', lastname: 'Garnier' } } mash[1] #=> { name: 'John', lastname: 'Doe' } ``` ### Mash Extension: SafeAssignment This extension can be mixed into a Mash to guard the attempted overwriting of methods by property setters. When mixed in, the Mash will raise an `ArgumentError` if you attempt to write a property with the same name as an existing method. #### Example: ```ruby class SafeMash < ::Hashie::Mash include Hashie::Extensions::Mash::SafeAssignment end safe_mash = SafeMash.new safe_mash.zip = 'Test' # => ArgumentError safe_mash[:zip] = 'test' # => still ArgumentError ``` ## Dash Dash is an extended Hash that has a discrete set of defined properties and only those properties may be set on the hash. Additionally, you can set defaults for each property. You can also flag a property as required. Required properties will raise an exception if unset. Another option is message for required properties, which allow you to add custom messages for required property. You can also conditionally require certain properties by passing a Proc or Symbol. If a Proc is provided, it will be run in the context of the Dash instance. If a Symbol is provided, the value returned for the property or method of the same name will be evaluated. The property will be required if the result of the conditional is truthy. ### Example: ```ruby class Person < Hashie::Dash property :name, required: true property :age, required: true, message: 'must be set.' property :email property :phone, required: -> { email.nil? }, message: 'is required if email is not set.' property :pants, required: :weekday?, message: 'are only required on weekdays.' property :occupation, default: 'Rubyist' def weekday? [ Time.now.saturday?, Time.now.sunday? ].none? end end p = Person.new # => ArgumentError: The property 'name' is required for this Dash. p = Person.new(name: 'Bob') # => ArgumentError: The property 'age' must be set. p = Person.new(name: "Bob", age: 18) p.name # => 'Bob' p.name = nil # => ArgumentError: The property 'name' is required for this Dash. p.age # => 18 p.age = nil # => ArgumentError: The property 'age' must be set. p.email = 'abc@def.com' p.occupation # => 'Rubyist' p.email # => 'abc@def.com' p[:awesome] # => NoMethodError p[:occupation] # => 'Rubyist' p.update_attributes!(name: 'Trudy', occupation: 'Evil') p.occupation # => 'Evil' p.name # => 'Trudy' p.update_attributes!(occupation: nil) p.occupation # => 'Rubyist' ``` Properties defined as symbols are not the same thing as properties defined as strings. ### Example: ```ruby class Tricky < Hashie::Dash property :trick property 'trick' end p = Tricky.new(trick: 'one', 'trick' => 'two') p.trick # => 'one', always symbol version p[:trick] # => 'one' p['trick'] # => 'two' ``` Note that accessing a property as a method always uses the symbol version. ```ruby class Tricky < Hashie::Dash property 'trick' end p = Tricky.new('trick' => 'two') p.trick # => NoMethodError ``` ### Dash Extension: PropertyTranslation The `Hashie::Extensions::Dash::PropertyTranslation` mixin extends a Dash with the ability to remap keys from a source hash. ### Example from inconsistent APIs Property translation is useful when you need to read data from another application -- such as a Java API -- where the keys are named differently from Ruby conventions. ```ruby class PersonHash < Hashie::Dash include Hashie::Extensions::Dash::PropertyTranslation property :first_name, from: :firstName property :last_name, from: :lastName property :first_name, from: :f_name property :last_name, from: :l_name end person = PersonHash.new(firstName: 'Michael', l_name: 'Bleigh') person[:first_name] #=> 'Michael' person[:last_name] #=> 'Bleigh ``` ### Example using translation lambdas You can also use a lambda to translate the value. This is particularly useful when you want to ensure the type of data you're wrapping. ```ruby class DataModelHash < Hashie::Dash include Hashie::Extensions::Dash::PropertyTranslation property :id, transform_with: ->(value) { value.to_i } property :created_at, from: :created, with: ->(value) { Time.parse(value) } end model = DataModelHash.new(id: '123', created: '2014-04-25 22:35:28') model.id.class #=> Fixnum model.created_at.class #=> Time ``` ### Mash and Rails 4 Strong Parameters To enable compatibility with Rails 4 use the [hashie-forbidden_attributes](https://github.com/Maxim-Filimonov/hashie-forbidden_attributes) gem. ### Dash Extension: Coercion. If you want to use `Hashie::Extensions::Coercion` together with `Dash` then you may probably want to use `Hashie::Extensions::Dash::Coercion` instead. This extension automatically includes `Hashie::Extensions::Coercion` and also adds a convenient `:coerce` option to `property` so you can define coercion in one line instead of using `property` and `coerce_key` separate: ```ruby class UserHash < Hashie::Dash include Hashie::Extensions::Coercion property :id property :posts coerce_key :posts, Array[PostHash] end ``` This is the same as: ```ruby class UserHash < Hashie::Dash include Hashie::Extensions::Dash::Coercion property :id property :posts, coerce: Array[PostHash] end ``` ## Trash A Trash is a Dash that allows you to translate keys on initialization. It mixes in the PropertyTranslation mixin by default and is used like so: ```ruby class Person < Hashie::Trash property :first_name, from: :firstName end ``` This will automatically translate the firstName key to first_name when it is initialized using a hash such as through: ```ruby Person.new(firstName: 'Bob') ``` Trash also supports translations using lambda, this could be useful when dealing with external API's. You can use it in this way: ```ruby class Result < Hashie::Trash property :id, transform_with: lambda { |v| v.to_i } property :created_at, from: :creation_date, with: lambda { |v| Time.parse(v) } end ``` this will produce the following ```ruby result = Result.new(id: '123', creation_date: '2012-03-30 17:23:28') result.id.class # => Fixnum result.created_at.class # => Time ``` ## Clash Clash is a Chainable Lazy Hash that allows you to easily construct complex hashes using method notation chaining. This will allow you to use a more action-oriented approach to building options hashes. Essentially, a Clash is a generalized way to provide much of the same kind of "chainability" that libraries like Arel or Rails 2.x's named_scopes provide. ### Example: ```ruby c = Hashie::Clash.new c.where(abc: 'def').order(:created_at) c # => { where: { abc: 'def' }, order: :created_at } # You can also use bang notation to chain into sub-hashes, # jumping back up the chain with _end! c = Hashie::Clash.new c.where!.abc('def').ghi(123)._end!.order(:created_at) c # => { where: { abc: 'def', ghi: 123 }, order: :created_at } # Multiple hashes are merged automatically c = Hashie::Clash.new c.where(abc: 'def').where(hgi: 123) c # => { where: { abc: 'def', hgi: 123 } } ``` ## Rash Rash is a Hash whose keys can be Regexps or Ranges, which will map many input keys to a value. A good use case for the Rash is an URL router for a web framework, where URLs need to be mapped to actions; the Rash's keys match URL patterns, while the values call the action which handles the URL. If the Rash's value is a `proc`, the `proc` will be automatically called with the regexp's MatchData (matched groups) as a block argument. ### Example: ```ruby # Mapping names to appropriate greetings greeting = Hashie::Rash.new( /^Mr./ => "Hello sir!", /^Mrs./ => "Evening, madame." ) greeting["Mr. Steve Austin"] # => "Hello sir!" greeting["Mrs. Steve Austin"] # => "Evening, madame." # Mapping statements to saucy retorts mapper = Hashie::Rash.new( /I like (.+)/ => proc { |m| "Who DOESN'T like #{m[1]}?!" }, /Get off my (.+)!/ => proc { |m| "Forget your #{m[1]}, old man!" } ) mapper["I like traffic lights"] # => "Who DOESN'T like traffic lights?!" mapper["Get off my lawn!"] # => "Forget your lawn, old man!" ``` ### Auto-optimized **Note:** The Rash is automatically optimized every 500 accesses (which means that it sorts the list of Regexps, putting the most frequently matched ones at the beginning). If this value is too low or too high for your needs, you can tune it by setting: `rash.optimize_every = n`. ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) ## Copyright Copyright (c) 2009-2014 Intridea, Inc. (http://intridea.com/) and [contributors](https://github.com/intridea/hashie/graphs/contributors). MIT License. See [LICENSE](LICENSE) for details. hashie-3.4.3/hashie.gemspec0000644000004100000410000000172412620101230015607 0ustar www-datawww-datarequire File.expand_path('../lib/hashie/version', __FILE__) Gem::Specification.new do |gem| gem.name = 'hashie' gem.version = Hashie::VERSION gem.authors = ['Michael Bleigh', 'Jerry Cheung'] gem.email = ['michael@intridea.com', 'jollyjerry@gmail.com'] gem.description = 'Hashie is a collection of classes and mixins that make hashes more powerful.' gem.summary = 'Your friendly neighborhood hash library.' gem.homepage = 'https://github.com/intridea/hashie' gem.license = 'MIT' gem.require_paths = ['lib'] gem.files = %w(.yardopts CHANGELOG.md CONTRIBUTING.md LICENSE README.md UPGRADING.md Rakefile hashie.gemspec) gem.files += Dir['lib/**/*.rb'] gem.files += Dir['spec/**/*.rb'] gem.test_files = Dir['spec/**/*.rb'] gem.add_development_dependency 'rake' gem.add_development_dependency 'rspec', '~> 3.0' gem.add_development_dependency 'rspec-pending_for', '~> 0.1' end