hashie-3.5.5/ 0000755 0000041 0000041 00000000000 13115743107 013017 5 ustar www-data www-data hashie-3.5.5/Rakefile 0000644 0000041 0000041 00000001423 13115743107 014464 0 ustar www-data www-data require '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'
spec.exclude_pattern = 'spec/integration/**/*_spec.rb'
end
require 'rubocop/rake_task'
RuboCop::RakeTask.new(:rubocop)
require_relative 'spec/support/integration_specs'
task :integration_specs do
next if ENV['CI']
status_codes = []
handler = lambda do |status_code|
status_codes << status_code unless status_code.zero?
end
run_all_integration_specs(handler: handler, logger: ->(msg) { puts msg })
if status_codes.any?
$stderr.puts "#{status_codes.size} integration test(s) failed"
exit status_codes.last
end
end
task default: [:rubocop, :spec, :integration_specs]
hashie-3.5.5/UPGRADING.md 0000644 0000041 0000041 00000012121 13115743107 014656 0 ustar www-data www-data Upgrading Hashie
================
### Upgrading to 3.5.2
#### Disable logging in Mash subclasses
If you subclass `Hashie::Mash`, you can now disable the logging we do about
overriding existing methods with keys. This looks like:
```ruby
class MyMash < Hashie::Mash
disable_warnings
end
```
### Upgrading to 3.4.7
#### Procs as default values for Dash
```ruby
class MyHash < Hashie::Dash
property :time, default: -> { Time.now }
end
```
In versions < 3.4.7 `Time.now` will be evaluated when `time` property is accessed directly first time.
In version >= 3.4.7 `Time.now` is evaluated in time of object initialization.
### Upgrading to 3.4.4
#### Mash subclasses and reverse_merge
```ruby
class MyMash < Hashie::Mash
end
```
In versions >= 3.4.4 `MyMash#reverse_merge` returns an instance of `MyMash` but in previous versions it was a `Hashie::Mash` instance.
### 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.5.5/spec/ 0000755 0000041 0000041 00000000000 13115743107 013751 5 ustar www-data www-data hashie-3.5.5/spec/spec_helper.rb 0000644 0000041 0000041 00000000632 13115743107 016570 0 ustar www-data www-data if ENV['CI']
require 'simplecov'
SimpleCov.start
end
require 'pry'
require 'rspec'
require 'hashie'
require 'rspec/pending_for'
require './spec/support/ruby_version_check'
require './spec/support/logger'
require 'active_support'
require 'active_support/core_ext'
RSpec.configure do |config|
config.extend RubyVersionCheck
config.expect_with :rspec do |expect|
expect.syntax = :expect
end
end
hashie-3.5.5/spec/integration/ 0000755 0000041 0000041 00000000000 13115743107 016274 5 ustar www-data www-data hashie-3.5.5/spec/integration/omniauth/ 0000755 0000041 0000041 00000000000 13115743107 020120 5 ustar www-data www-data hashie-3.5.5/spec/integration/omniauth/app.rb 0000644 0000041 0000041 00000000332 13115743107 021223 0 ustar www-data www-data require 'sinatra'
require 'omniauth'
class MyApplication < Sinatra::Base
use Rack::Session::Cookie, secret: 'hashie integration tests'
use OmniAuth::Strategies::Developer
get '/' do
'Hello World'
end
end
hashie-3.5.5/spec/integration/omniauth/integration_spec.rb 0000644 0000041 0000041 00000001262 13115743107 024003 0 ustar www-data www-data ENV['RACK_ENV'] = 'test'
require 'rspec/core'
require 'rack/test'
RSpec.configure do |config|
config.expect_with :rspec do |expect|
expect.syntax = :expect
end
end
RSpec.describe 'omniauth' do
include Rack::Test::Methods
def app
MyApplication
end
let(:stdout) { StringIO.new }
around(:each) do |example|
original_stdout = $stdout
$stdout = stdout
require_relative 'app'
example.run
$stdout = original_stdout
end
it 'does not log anything to STDOUT when initializing' do
expect(stdout.string).to eq('')
end
it 'works' do
get '/'
expect(last_response).to be_ok
expect(last_response.body).to eq 'Hello World'
end
end
hashie-3.5.5/spec/integration/rails-without-dependency/ 0000755 0000041 0000041 00000000000 13115743107 023223 5 ustar www-data www-data hashie-3.5.5/spec/integration/rails-without-dependency/integration_spec.rb 0000644 0000041 0000041 00000000537 13115743107 027112 0 ustar www-data www-data require 'rspec/core'
RSpec.describe 'partial-rails' do
context 'when Rails constant is present but the railties are not' do
before(:all) do
class Rails
# A class about railways
end
end
it 'does not raise an exception when we require hashie' do
expect { require 'hashie' }.not_to raise_error
end
end
end
hashie-3.5.5/spec/integration/rails/ 0000755 0000041 0000041 00000000000 13115743107 017406 5 ustar www-data www-data hashie-3.5.5/spec/integration/rails/app.rb 0000644 0000041 0000041 00000001624 13115743107 020516 0 ustar www-data www-data require 'action_controller/railtie'
require 'action_view/railtie'
require 'action_view/testing/resolvers'
require 'rails/test_unit/railtie'
module RailsApp
class Application < ::Rails::Application
config.eager_load = false
config.secret_key_base = 'hashieintegrationtest'
routes.append do
get '/' => 'application#index'
end
end
end
LAYOUT = <<-HTML
TestApp
<%= csrf_meta_tags %>
<%= yield %>
HTML
INDEX = 'Hello, world!
'
class ApplicationController < ActionController::Base
include Rails.application.routes.url_helpers
layout 'application'
self.view_paths = [ActionView::FixtureResolver.new(
'layouts/application.html.erb' => LAYOUT,
'application/index.html.erb' => INDEX
)]
def index
end
end
Bundler.require(:default, Rails.env)
RailsApp::Application.initialize!
hashie-3.5.5/spec/integration/rails/integration_spec.rb 0000644 0000041 0000041 00000001110 13115743107 023261 0 ustar www-data www-data ENV['RAILS_ENV'] = 'test'
require 'rspec/core'
RSpec.describe 'rails', type: :request do
let(:stdout) { StringIO.new }
around(:each) do |example|
original_stdout = $stdout
$stdout = stdout
require_relative 'app'
require 'rspec/rails'
example.run
$stdout = original_stdout
end
it 'does not log anything to STDOUT when initializing and sets the Hashie logger to the Rails logger' do
expect(stdout.string).to eq('')
expect(Hashie.logger).to eq(Rails.logger)
end
it 'works' do
get '/'
assert_select 'h1', 'Hello, world!'
end
end
hashie-3.5.5/spec/integration/omniauth-oauth2/ 0000755 0000041 0000041 00000000000 13115743107 021320 5 ustar www-data www-data hashie-3.5.5/spec/integration/omniauth-oauth2/some_site.rb 0000644 0000041 0000041 00000001714 13115743107 023637 0 ustar www-data www-data require 'omniauth-oauth2'
module OmniAuth
module Strategies
class SomeSite < OmniAuth::Strategies::OAuth2
# Give your strategy a name.
option :name, 'some_site'
# This is where you pass the options you would pass when
# initializing your consumer from the OAuth gem.
option :client_options, site: 'https://api.somesite.com'
# These are called after authentication has succeeded. If
# possible, you should try to set the UID without making
# additional calls (if the user id is returned with the token
# or as a URI parameter). This may not be possible with all
# providers.
uid { raw_info['id'] }
info do
{
:name => raw_info['name'],
:email => raw_info['email']
}
end
extra do
{
'raw_info' => raw_info
}
end
def raw_info
@raw_info ||= access_token.get('/me').parsed
end
end
end
end
hashie-3.5.5/spec/integration/omniauth-oauth2/app.rb 0000644 0000041 0000041 00000002003 13115743107 022420 0 ustar www-data www-data require 'action_controller/railtie'
require 'action_view/railtie'
require 'action_view/testing/resolvers'
require 'rails/test_unit/railtie'
require_relative 'some_site'
module RailsApp
class Application < ::Rails::Application
config.eager_load = false
config.secret_key_base = 'hashieintegrationtest'
config.middleware.use OmniAuth::Builder do
provider :some_site
end
routes.append do
get '/' => 'application#index'
end
end
end
LAYOUT = <<-HTML
TestApp
<%= csrf_meta_tags %>
<%= yield %>
HTML
INDEX = 'Hello, world!
'
class ApplicationController < ActionController::Base
include Rails.application.routes.url_helpers
layout 'application'
self.view_paths = [ActionView::FixtureResolver.new(
'layouts/application.html.erb' => LAYOUT,
'application/index.html.erb' => INDEX
)]
def index
end
end
Bundler.require(:default, Rails.env)
RailsApp::Application.initialize!
hashie-3.5.5/spec/integration/omniauth-oauth2/integration_spec.rb 0000644 0000041 0000041 00000001132 13115743107 025177 0 ustar www-data www-data ENV['RAILS_ENV'] = 'test'
require 'rspec/core'
RSpec.describe 'omniauth-oauth2 inside of rails', type: :request do
let(:stdout) { StringIO.new }
around(:each) do |example|
original_stdout = $stdout
$stdout = stdout
require_relative 'app'
require 'rspec/rails'
example.run
$stdout = original_stdout
end
it 'does not log anything to STDOUT when initializing a Rails app and is set to Rails logger' do
expect(stdout.string).to eq('')
expect(Hashie.logger).to eq(Rails.logger)
end
it 'works' do
get '/'
assert_select 'h1', 'Hello, world!'
end
end
hashie-3.5.5/spec/hashie/ 0000755 0000041 0000041 00000000000 13115743107 015212 5 ustar www-data www-data hashie-3.5.5/spec/hashie/rash_spec.rb 0000644 0000041 0000041 00000004152 13115743107 017510 0 ustar www-data www-data require '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.5.5/spec/hashie/trash_spec.rb 0000644 0000041 0000041 00000020705 13115743107 017676 0 ustar www-data www-data require '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.5.5/spec/hashie/mash_spec.rb 0000644 0000041 0000041 00000057111 13115743107 017506 0 ustar www-data www-data require 'spec_helper'
describe Hashie::Mash do
subject { Hashie::Mash.new }
include_context 'with a logger'
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
include_context 'with a logger' do
it 'logs a warning when overriding built-in methods' do
Hashie::Mash.new('trust' => { 'two' => 2 })
expect(logger_output).to match('Hashie::Mash#trust')
end
it 'can set keys more than once and does not warn when doing so' do
mash = Hashie::Mash.new
mash[:test_key] = 'Test value'
expect { mash[:test_key] = 'A new value' }.not_to raise_error
expect(logger_output).to be_blank
end
it 'does not write to the logger when warnings are disabled' do
mash_class = Class.new(Hashie::Mash) do
disable_warnings
end
mash_class.new('trust' => { 'two' => 2 })
expect(logger_output).to be_blank
end
it 'cannot disable logging on the base Mash' do
expect { Hashie::Mash.disable_warnings }.to raise_error(Hashie::Mash::CannotDisableMashWarnings)
end
it 'carries over the disable for warnings on grandchild classes' do
child_class = Class.new(Hashie::Mash) do
disable_warnings
end
grandchild_class = Class.new(child_class)
grandchild_class.new('trust' => { 'two' => 2 })
expect(logger_output).to be_blank
end
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
context 'if the file is passed as Pathname' do
require 'pathname'
let(:path) { Pathname.new('database.yml') }
before do
expect(File).to receive(:file?).with(path).and_return(true)
expect(parser).to receive(:perform).with(path).and_return(config)
end
it 'return a Mash from a file' do
expect(subject.production.foo).to eq config['production']['foo']
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
context 'when using with subclass' do
let(:subclass) { Class.new(Hashie::Mash) }
subject { subclass.new(a: 1) }
it 'creates an instance of subclass' do
expect(subject.reverse_merge(a: 5)).to be_kind_of(subclass)
end
end
end
with_minimum_ruby('2.3.0') do
describe '#dig' do
subject { described_class.new(a: { b: 1 }) }
it 'accepts both string and symbol as key' do
expect(subject.dig(:a, :b)).to eq(1)
expect(subject.dig('a', 'b')).to eq(1)
end
context 'with numeric key' do
subject { described_class.new('1' => { b: 1 }) }
it 'accepts a numeric value as key' do
expect(subject.dig(1, :b)).to eq(1)
expect(subject.dig('1', :b)).to eq(1)
end
end
end
end
end
hashie-3.5.5/spec/hashie/version_spec.rb 0000644 0000041 0000041 00000000166 13115743107 020241 0 ustar www-data www-data require 'spec_helper'
describe Hashie do
it 'has a version' do
expect(Hashie::VERSION).not_to be_nil
end
end
hashie-3.5.5/spec/hashie/parsers/ 0000755 0000041 0000041 00000000000 13115743107 016671 5 ustar www-data www-data hashie-3.5.5/spec/hashie/parsers/yaml_erb_parser_spec.rb 0000644 0000041 0000041 00000002061 13115743107 023375 0 ustar www-data www-data require 'spec_helper'
describe Hashie::Extensions::Parsers::YamlErbParser do
describe '.perform' do
context 'a file' 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
context 'Pathname' do
let(:tempfile) do
file = Tempfile.new(['foo', '.yml'])
file.write("---\nfoo: hello\n")
file.rewind
file
end
subject { described_class.new(Pathname tempfile.path) }
it '"#perform" can be done in case of path is a Pathname object.' do
expect(subject.perform).to eq 'foo' => 'hello'
end
end
end
end
hashie-3.5.5/spec/hashie/utils_spec.rb 0000644 0000041 0000041 00000001161 13115743107 017710 0 ustar www-data www-data require 'spec_helper'
def a_method_to_match_against
'Hello world!'
end
RSpec.describe Hashie::Utils do
describe '.method_information' do
it 'states the module or class that a native method was defined in' do
bound_method = method(:trust)
message = Hashie::Utils.method_information(bound_method)
expect(message).to match('Kernel')
end
it 'states the line a Ruby method was defined at' do
bound_method = method(:a_method_to_match_against)
message = Hashie::Utils.method_information(bound_method)
expect(message).to match('spec/hashie/utils_spec.rb')
end
end
end
hashie-3.5.5/spec/hashie/clash_spec.rb 0000644 0000041 0000041 00000004062 13115743107 017645 0 ustar www-data www-data require 'spec_helper'
describe Hashie::Clash do
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
it 'merges multiple bang notation calls' do
subject.where!.foo(123)
subject.where!.bar(321)
expect(subject).to eq(where: { foo: 123, bar: 321 })
end
it 'raises an exception when method is missing' do
expect { subject.boo }.to raise_error(NoMethodError)
end
describe 'when inherited' do
subject { Class.new(described_class).new }
it 'bang nodes are instances of a subclass' do
subject.where!.foo(123)
expect(subject[:where]).to be_instance_of(subject.class)
end
it 'merged nodes are instances of a subclass' do
subject.where(abc: 'def').where(hgi: 123)
expect(subject[:where]).to be_instance_of(subject.class)
end
end
end
hashie-3.5.5/spec/hashie/extensions/ 0000755 0000041 0000041 00000000000 13115743107 017411 5 ustar www-data www-data hashie-3.5.5/spec/hashie/extensions/indifferent_access_spec.rb 0000644 0000041 0000041 00000017115 13115743107 024573 0 ustar www-data www-data 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 IndifferentHashWithDash < Hashie::Dash
include Hashie::Extensions::IndifferentAccess
property :foo
end
class IndifferentHashWithIgnoreUndeclaredAndPropertyTranslation < Hashie::Dash
include Hashie::Extensions::IgnoreUndeclared
include Hashie::Extensions::Dash::PropertyTranslation
include Hashie::Extensions::IndifferentAccess
property :foo, from: :bar
end
describe '#merge' do
it 'indifferently merges in a hash' do
indifferent_hash = Class.new(::Hash) do
include Hashie::Extensions::IndifferentAccess
end.new
merged_hash = indifferent_hash.merge(:cat => 'meow')
expect(merged_hash[:cat]).to eq('meow')
expect(merged_hash['cat']).to eq('meow')
end
end
describe '#merge!' do
it 'indifferently merges in a hash' do
indifferent_hash = Class.new(::Hash) do
include Hashie::Extensions::IndifferentAccess
end.new
indifferent_hash.merge!(:cat => 'meow')
expect(indifferent_hash[:cat]).to eq('meow')
expect(indifferent_hash['cat']).to eq('meow')
end
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
describe 'when translating properties and ignoring undeclared' do
let(:value) { 'baz' }
subject { IndifferentHashWithIgnoreUndeclaredAndPropertyTranslation.new(params) }
context 'and the hash keys are strings' do
let(:params) { { 'bar' => value } }
it 'sets the property' do
expect(subject[:foo]).to eq value
end
end
context 'and the hash keys are symbols' do
let(:params) { { bar: 'baz' } }
it 'sets the property' do
expect(subject[:foo]).to eq value
end
end
context 'and there are undeclared keys' do
let(:params) { { 'bar' => 'baz', 'fail' => false } }
it 'sets the property' do
expect(subject[:foo]).to eq value
end
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 = {}
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 = []
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 = {}
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 = []
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({}).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.5.5/spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb 0000644 0000041 0000041 00000014066 13115743107 030032 0 ustar www-data www-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({}).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.5.5/spec/hashie/extensions/ignore_undeclared_spec.rb 0000644 0000041 0000041 00000003063 13115743107 024423 0 ustar www-data www-data require '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
property :str_state, from: 'str_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('str_provence' => 'Ontario').str_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.5.5/spec/hashie/extensions/symbolize_keys_spec.rb 0000644 0000041 0000041 00000006353 13115743107 024027 0 ustar www-data www-data require '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.5.5/spec/hashie/extensions/autoload_spec.rb 0000644 0000041 0000041 00000002134 13115743107 022560 0 ustar www-data www-data require '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.5.5/spec/hashie/extensions/mash/ 0000755 0000041 0000041 00000000000 13115743107 020341 5 ustar www-data www-data hashie-3.5.5/spec/hashie/extensions/mash/symbolize_keys_spec.rb 0000644 0000041 0000041 00000002157 13115743107 024755 0 ustar www-data www-data require 'spec_helper'
RSpec.describe Hashie::Extensions::Mash::SymbolizeKeys do
it 'raises an error when included in a class that is not a Mash' do
expect do
Class.new do
include Hashie::Extensions::Mash::SymbolizeKeys
end
end.to raise_error(ArgumentError)
end
it 'symbolizes all keys in the Mash' do
my_mash = Class.new(Hashie::Mash) do
include Hashie::Extensions::Mash::SymbolizeKeys
end
expect(my_mash.new('test' => 'value').to_h).to eq(test: 'value')
end
context 'implicit to_hash on double splat' do
let(:destructure) { ->(**opts) { opts } }
let(:my_mash) do
Class.new(Hashie::Mash) do
include Hashie::Extensions::Mash::SymbolizeKeys
end
end
let(:instance) { my_mash.new('outer' => { 'inner' => 42 }, 'testing' => [1, 2, 3]) }
subject { destructure.call(instance) }
it 'is converted on method calls' do
expect(subject).to eq(outer: { inner: 42 }, testing: [1, 2, 3])
end
it 'is converted on explicit operator call' do
expect(**instance).to eq(outer: { inner: 42 }, testing: [1, 2, 3])
end
end
end
hashie-3.5.5/spec/hashie/extensions/mash/safe_assignment_spec.rb 0000644 0000041 0000041 00000002445 13115743107 025053 0 ustar www-data www-data require '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.5.5/spec/hashie/extensions/mash/keep_original_keys_spec.rb 0000644 0000041 0000041 00000002520 13115743107 025542 0 ustar www-data www-data require 'spec_helper'
RSpec.describe Hashie::Extensions::Mash::KeepOriginalKeys do
let(:keeping_mash) do
Class.new(Hashie::Mash) do
include Hashie::Extensions::Mash::KeepOriginalKeys
end
end
it 'keeps the keys in the resulting hash identical to the original' do
original = { :a => 'apple', 'b' => 'bottle' }
mash = keeping_mash.new(original)
expect(mash.to_hash).to eq(original)
end
it 'indifferently responds to keys' do
original = { :a => 'apple', 'b' => 'bottle' }
mash = keeping_mash.new(original)
expect(mash['a']).to eq(mash[:a])
expect(mash['b']).to eq(mash[:b])
end
it 'responds to all method accessors like a Mash' do
original = { :a => 'apple', 'b' => 'bottle' }
mash = keeping_mash.new(original)
expect(mash.a).to eq('apple')
expect(mash.a?).to eq(true)
expect(mash.b).to eq('bottle')
expect(mash.b?).to eq(true)
expect(mash.underbang_).to be_a(keeping_mash)
expect(mash.bang!).to be_a(keeping_mash)
expect(mash.predicate?).to eq(false)
end
it 'keeps the keys that are directly passed without converting them' do
original = { :a => 'apple', 'b' => 'bottle' }
mash = keeping_mash.new(original)
mash[:c] = 'cat'
mash['d'] = 'dog'
expect(mash.to_hash).to eq(:a => 'apple', 'b' => 'bottle', :c => 'cat', 'd' => 'dog')
end
end
hashie-3.5.5/spec/hashie/extensions/deep_locate_spec.rb 0000644 0000041 0000041 00000006610 13115743107 023217 0 ustar www-data www-data require 'spec_helper'
require 'active_support/core_ext/hash/indifferent_access'
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
context 'on an ActiveSupport::HashWithIndifferentAccess' do
let(:instance) { hash.dup.with_indifferent_access }
it 'can locate symbolic keys' do
expect(described_class.deep_locate(:lsr10, instance)).to eq ['lsr10' => { 'gte' => 2014 }]
end
it 'can locate string keys' do
expect(described_class.deep_locate('lsr10', instance)).to eq ['lsr10' => { 'gte' => 2014 }]
end
end
end
hashie-3.5.5/spec/hashie/extensions/coercion_spec.rb 0000644 0000041 0000041 00000045025 13115743107 022557 0 ustar www-data www-data require '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
type = if Hashie::Extensions::RubyVersion.new(RUBY_VERSION) >= Hashie::Extensions::RubyVersion.new('2.4.0')
Integer
else
Fixnum
end
subject.coerce_value type, Symbol
expect { instance[:hi] = 1 }.to raise_error(Hashie::CoercionError, /Cannot coerce property :hi from #{type} 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.5.5/spec/hashie/extensions/merge_initializer_spec.rb 0000644 0000041 0000041 00000001035 13115743107 024451 0 ustar www-data www-data require '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.5.5/spec/hashie/extensions/dash/ 0000755 0000041 0000041 00000000000 13115743107 020330 5 ustar www-data www-data hashie-3.5.5/spec/hashie/extensions/dash/indifferent_access_spec.rb 0000644 0000041 0000041 00000005736 13115743107 025520 0 ustar www-data www-data require '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.5.5/spec/hashie/extensions/dash/coercion_spec.rb 0000644 0000041 0000041 00000000510 13115743107 023464 0 ustar www-data www-data require '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.5.5/spec/hashie/extensions/method_access_spec.rb 0000644 0000041 0000041 00000012233 13115743107 023552 0 ustar www-data www-data require '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).abc?).to eq true
end
it 'is true for non-nil symbol key values' do
expect(subject.new(abc: 123).abc?).to eq true
end
it 'is false for false key values' do
expect(subject.new(abc: false).abc?).to eq false
end
it 'is false for nil key values' do
expect(subject.new(abc: nil).abc?).to eq false
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.5.5/spec/hashie/extensions/deep_merge_spec.rb 0000644 0000041 0000041 00000004247 13115743107 023053 0 ustar www-data www-data require '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' } }, e: { e1: 1 } } }
let(:expected_hash) { { a: 1, a1: 1, b: 'b', c: { c1: 2, c2: 'c2', c3: { d1: 'd1', d2: 'd2' } }, e: { e1: 1 } } }
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
it 'merges new nested hash entries by value, not by reference' do
h1.deep_merge!(h2)
expect { h1[:e][:e1] = 'changed' }.not_to change { h2[:e][:e1] }
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.5.5/spec/hashie/extensions/deep_fetch_spec.rb 0000644 0000041 0000041 00000006125 13115743107 023042 0 ustar www-data www-data require '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.5.5/spec/hashie/extensions/stringify_keys_spec.rb 0000644 0000041 0000041 00000006147 13115743107 024031 0 ustar www-data www-data require '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.5.5/spec/hashie/extensions/key_conversion_spec.rb 0000644 0000041 0000041 00000000507 13115743107 024007 0 ustar www-data www-data require '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.5.5/spec/hashie/extensions/deep_find_spec.rb 0000644 0000041 0000041 00000011044 13115743107 022665 0 ustar www-data www-data require 'spec_helper'
require 'active_support/core_ext/hash/indifferent_access'
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
context 'when match value is hash itself' do
let(:hash) do
{
title: {
type: :string
},
library: {
books: [
{ title: 'Call of the Wild' },
{ title: 'Moby Dick' }
],
shelves: nil,
location: {
address: '123 Library St.',
title: 'Main Library'
}
}
}
end
it 'detects all values from a nested hash' do
expect(instance.deep_find_all(:title)).to eq([{ type: :string }, 'Call of the Wild', 'Moby Dick', 'Main Library'])
end
end
end
context 'on an ActiveSupport::HashWithIndifferentAccess' do
subject(:instance) { hash.with_indifferent_access.extend(Hashie::Extensions::DeepFind) }
describe '#deep_find' do
it 'indifferently detects a value from a nested hash' do
expect(instance.deep_find(:address)).to eq('123 Library St.')
expect(instance.deep_find('address')).to eq('123 Library St.')
end
it 'indifferently detects a value from a nested array' do
expect(instance.deep_find(:title)).to eq('Call of the Wild')
expect(instance.deep_find('title')).to eq('Call of the Wild')
end
it 'indifferently returns nil if it does not find a match' do
expect(instance.deep_find(:wahoo)).to be_nil
expect(instance.deep_find('wahoo')).to be_nil
end
end
describe '#deep_find_all' do
it 'indifferently detects all values from a nested hash' do
expect(instance.deep_find_all(:title)).to eq(['Call of the Wild', 'Moby Dick', 'Main Library'])
expect(instance.deep_find_all('title')).to eq(['Call of the Wild', 'Moby Dick', 'Main Library'])
end
it 'indifferently returns nil if it does not find any matches' do
expect(instance.deep_find_all(:wahoo)).to be_nil
expect(instance.deep_find_all('wahoo')).to be_nil
end
end
end
context 'on a Hash including Hashie::Extensions::IndifferentAccess' do
let(:klass) { Class.new(Hash) { include Hashie::Extensions::IndifferentAccess } }
subject(:instance) { klass[hash.dup].extend(Hashie::Extensions::DeepFind) }
describe '#deep_find' do
it 'indifferently detects a value from a nested hash' do
expect(instance.deep_find(:address)).to eq('123 Library St.')
expect(instance.deep_find('address')).to eq('123 Library St.')
end
it 'indifferently detects a value from a nested array' do
expect(instance.deep_find(:title)).to eq('Call of the Wild')
expect(instance.deep_find('title')).to eq('Call of the Wild')
end
it 'indifferently returns nil if it does not find a match' do
expect(instance.deep_find(:wahoo)).to be_nil
expect(instance.deep_find('wahoo')).to be_nil
end
end
describe '#deep_find_all' do
it 'indifferently detects all values from a nested hash' do
expect(instance.deep_find_all(:title)).to eq(['Call of the Wild', 'Moby Dick', 'Main Library'])
expect(instance.deep_find_all('title')).to eq(['Call of the Wild', 'Moby Dick', 'Main Library'])
end
it 'indifferently returns nil if it does not find any matches' do
expect(instance.deep_find_all(:wahoo)).to be_nil
expect(instance.deep_find_all('wahoo')).to be_nil
end
end
end
end
hashie-3.5.5/spec/hashie/extensions/strict_key_access_spec.rb 0000644 0000041 0000041 00000010520 13115743107 024447 0 ustar www-data www-data require '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.5.5/spec/hashie/hash_spec.rb 0000644 0000041 0000041 00000006772 13115743107 017510 0 ustar www-data www-data require '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.5.5/spec/hashie/dash_spec.rb 0000644 0000041 0000041 00000042567 13115743107 017506 0 ustar www-data www-data require '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 DashTestDefaultProc < Hashie::Dash
property :fields, default: -> { [] }
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 DashTestDefaultProc do
it 'as_json behaves correctly with default proc' do
object = described_class.new
expect(object.as_json).to be == { 'fields' => [] }
end
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
context 'converting from a Mash' do
class ConvertingFromMash < Hashie::Dash
property :property, required: true
end
context 'without keeping the original keys' do
let(:mash) { Hashie::Mash.new(property: 'test') }
it 'does not pick up the property from the stringified key' do
expect { ConvertingFromMash.new(mash) }.to raise_error(NoMethodError)
end
end
context 'when keeping the original keys' do
class KeepingMash < Hashie::Mash
include Hashie::Extensions::Mash::KeepOriginalKeys
end
let(:mash) { KeepingMash.new(property: 'test') }
it 'picks up the property from the original key' do
expect { ConvertingFromMash.new(mash) }.not_to raise_error
end
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
context 'with method access' do
class DashWithMethodAccess < Hashie::Dash
include Hashie::Extensions::IndifferentAccess
include Hashie::Extensions::MethodQuery
property :test
end
subject(:dash) { DashWithMethodAccess.new(test: 'value') }
describe '#test' do
subject { dash.test }
it { is_expected.to eq('value') }
end
describe '#test?' do
subject { dash.test? }
it { is_expected.to eq true }
end
end
hashie-3.5.5/spec/hashie/array_spec.rb 0000644 0000041 0000041 00000001333 13115743107 017667 0 ustar www-data www-data require 'spec_helper'
describe Array do
with_minimum_ruby('2.3.0') do
describe '#dig' do
let(:array) { Hashie::Array.new([:a, :b, :c]) }
it 'works with a string index' do
expect(array.dig('0')).to eq(:a)
end
it 'works with a numeric index' do
expect(array.dig(1)).to eq(:b)
end
context 'when array is empty' do
let(:array) { Hashie::Array.new([]) }
it 'works with a first numeric and next string index' do
expect(array.dig(0, 'hello')).to eq(nil)
end
it 'throws an error with first string and next numeric index' do
expect { array.dig('hello', 0) }.to raise_error(TypeError)
end
end
end
end
end
hashie-3.5.5/spec/hashie_spec.rb 0000644 0000041 0000041 00000000411 13115743107 016545 0 ustar www-data www-data require 'spec_helper'
RSpec.describe Hashie do
describe '.logger' do
include_context 'with a logger'
it 'is available via an accessor' do
Hashie.logger.info('Fee fi fo fum')
expect(logger_output).to match('Fee fi fo fum')
end
end
end
hashie-3.5.5/spec/support/ 0000755 0000041 0000041 00000000000 13115743107 015465 5 ustar www-data www-data hashie-3.5.5/spec/support/ruby_version_check.rb 0000644 0000041 0000041 00000000277 13115743107 021703 0 ustar www-data www-data module RubyVersionCheck
def with_minimum_ruby(version)
yield if Hashie::Extensions::RubyVersion.new(RUBY_VERSION) >=
Hashie::Extensions::RubyVersion.new(version)
end
end
hashie-3.5.5/spec/support/logger.rb 0000644 0000041 0000041 00000001161 13115743107 017270 0 ustar www-data www-data # A shared context that allows you to check the output of Hashie's logger.
#
# @example
# include_context 'with a logger'
#
# it 'logs info message' do
# Hashie.logger.info 'What is happening in here?!'
#
# expect(logger_output).to match('What is happening in here?!')
# end
RSpec.shared_context 'with a logger' do
# @private
let(:log) { StringIO.new }
# The output string from the logger
let(:logger_output) { log.rewind && log.string }
around(:each) do |example|
original_logger = Hashie.logger
Hashie.logger = Logger.new(log)
example.run
Hashie.logger = original_logger
end
end
hashie-3.5.5/spec/support/integration_specs.rb 0000644 0000041 0000041 00000002312 13115743107 021530 0 ustar www-data www-data # Generates the bundle command for running an integration test
#
# @param [String] integration the integration folder to run
# @param [String] command the command to run
# @return [String]
def integration_command(integration, command)
"#{integration_gemfile(integration)} #{command}"
end
# Generates the Gemfile for an integration
#
# @param [String] integration the integration test name
# @return [String]
def integration_gemfile(integration)
"BUNDLE_GEMFILE=#{integration_path(integration)}/Gemfile"
end
# Generates the path to the integration
#
# @param [String] integration the integration test name
# @return [String]
def integration_path(integration)
"spec/integration/#{integration}"
end
# Runs all integration specs in their own environment
def run_all_integration_specs(handler: ->(_code) {}, logger: ->(_msg) {})
Dir['spec/integration/*']
.map { |directory| directory.split('/').last }
.each do |integration|
logger.call(%(Running "#{integration}" integration spec))
system(integration_command(integration, 'bundle --quiet'))
system(integration_command(integration, "bundle exec rspec #{integration_path(integration)}"))
handler.call($CHILD_STATUS.exitstatus)
end
end
hashie-3.5.5/spec/support/module_context.rb 0000644 0000041 0000041 00000000304 13115743107 021040 0 ustar www-data www-data shared_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.5.5/lib/ 0000755 0000041 0000041 00000000000 13115743107 013565 5 ustar www-data www-data hashie-3.5.5/lib/hashie/ 0000755 0000041 0000041 00000000000 13115743107 015026 5 ustar www-data www-data hashie-3.5.5/lib/hashie/rash.rb 0000644 0000041 0000041 00000006677 13115743107 016330 0 ustar www-data www-data module 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.5.5/lib/hashie/utils.rb 0000644 0000041 0000041 00000000724 13115743107 016516 0 ustar www-data www-data module Hashie
# A collection of helper methods that can be used throughout the gem.
module Utils
# Describes a method by where it was defined.
#
# @param bound_method [Method] The method to describe.
# @return [String]
def self.method_information(bound_method)
if bound_method.source_location
"defined at #{bound_method.source_location.join(':')}"
else
"defined in #{bound_method.owner}"
end
end
end
end
hashie-3.5.5/lib/hashie/hash.rb 0000644 0000041 0000041 00000003151 13115743107 016276 0 ustar www-data www-data require '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.5.5/lib/hashie/mash.rb 0000644 0000041 0000041 00000024074 13115743107 016312 0 ustar www-data www-data require 'hashie/hash'
require 'hashie/array'
require 'hashie/utils'
require 'hashie/logger'
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
include Hashie::Extensions::RubyVersionCheck
ALLOWED_SUFFIXES = %w(? ! = _)
class CannotDisableMashWarnings < StandardError
def initialize(message = 'You cannot disable warnings on the base Mash class. Please subclass the Mash and disable it in the subclass.')
super(message)
end
end
# Disable the logging of warnings based on keys conflicting keys/methods
#
# @api semipublic
# @return [void]
def self.disable_warnings
fail CannotDisableMashWarnings if self == Hashie::Mash
@disable_warnings = true
end
# Checks whether this class disables warnings for conflicting keys/methods
#
# @api semipublic
# @return [Boolean]
def self.disable_warnings?
!!@disable_warnings
end
# Inheritance hook that sets class configuration when inherited.
#
# @api semipublic
# @return [void]
def self.inherited(subclass)
super
subclass.disable_warnings if disable_warnings?
end
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:
key_as_symbol = (key = convert_key(key)).to_sym
log_built_in_message(key_as_symbol) if log_collision?(key_as_symbol)
regular_writer(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
alias_method :regular_key?, :key?
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)
self.class.new(other_hash).merge(self)
end
with_minimum_ruby('2.3.0') do
def dig(*keys)
super(*keys.map { |key| convert_key(key) })
end
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) }
when ::Array
Array.new(val.map { |e| convert_value(e) })
else
val
end
end
private
def log_built_in_message(method_key)
return if self.class.disable_warnings?
method_information = Hashie::Utils.method_information(method(method_key))
Hashie.logger.warn(
'You are setting a key that conflicts with a built-in method ' \
"#{self.class}##{method_key} #{method_information}. " \
'This can cause unexpected behavior when accessing the key via as a ' \
'property. You can still access the key via the #[] method.'
)
end
def log_collision?(method_key)
respond_to?(method_key) && !self.class.disable_warnings? &&
!(regular_key?(method_key) || regular_key?(method_key.to_s))
end
end
end
hashie-3.5.5/lib/hashie/trash.rb 0000644 0000041 0000041 00000000672 13115743107 016501 0 ustar www-data www-data require '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.5.5/lib/hashie/logger.rb 0000644 0000041 0000041 00000000570 13115743107 016634 0 ustar www-data www-data require 'logger'
module Hashie
# The logger that Hashie uses for reporting errors.
#
# @return [Logger]
def self.logger
@logger ||= Logger.new(STDOUT)
end
# Sets the logger that Hashie uses for reporting errors.
#
# @param logger [Logger] The logger to set as Hashie's logger.
# @return [void]
def self.logger=(logger)
@logger = logger
end
end
hashie-3.5.5/lib/hashie/version.rb 0000644 0000041 0000041 00000000046 13115743107 017040 0 ustar www-data www-data module Hashie
VERSION = '3.5.5'
end
hashie-3.5.5/lib/hashie/array.rb 0000644 0000041 0000041 00000001010 13115743107 016461 0 ustar www-data www-data require 'hashie/extensions/array/pretty_inspect'
require 'hashie/extensions/ruby_version_check'
module Hashie
class Array < ::Array
include Hashie::Extensions::Array::PrettyInspect
include Hashie::Extensions::RubyVersionCheck
with_minimum_ruby('2.3.0') do
def dig(*indexes)
converted_indexes = indexes.map do |idx|
begin
Integer(idx)
rescue ArgumentError
idx
end
end
super(*converted_indexes)
end
end
end
end
hashie-3.5.5/lib/hashie/clash.rb 0000644 0000041 0000041 00000005042 13115743107 016446 0 ustar www-data www-data require '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.class.new(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:
if args.empty? && name.to_s.end_with?('!')
key = name[0...-1].to_sym
case self[key]
when NilClass
self[key] = self.class.new({}, self)
when Clash
self[key]
when Hash
self[key] = self.class.new(self[key], self)
else
fail ChainError, 'Tried to chain into a non-hash key.'
end
elsif args.any?
merge_store(name, *args)
else
super
end
end
end
end
hashie-3.5.5/lib/hashie/extensions/ 0000755 0000041 0000041 00000000000 13115743107 017225 5 ustar www-data www-data hashie-3.5.5/lib/hashie/extensions/symbolize_keys.rb 0000644 0000041 0000041 00000003571 13115743107 022630 0 ustar www-data www-data module 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.5.5/lib/hashie/extensions/method_access.rb 0000644 0000041 0000041 00000015006 13115743107 022355 0 ustar www-data www-data module 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)
if query_method?(name) && indifferent_key?(key_from_query_method(name))
true
else
super
end
end
def method_missing(name, *args)
return super unless args.empty?
if query_method?(name)
key = key_from_query_method(name)
if indifferent_key?(key)
!!(self[key] || self[key.to_sym])
else
super
end
else
super
end
end
private
def indifferent_key?(name)
name = name.to_s
key?(name) || key?(name.to_sym)
end
def key_from_query_method(query_method)
query_method.to_s[0..-2]
end
def query_method?(name)
name.to_s.end_with?('?')
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.5.5/lib/hashie/extensions/coercion.rb 0000644 0000041 0000041 00000016124 13115743107 021357 0 ustar www-data www-data module 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 = if RubyVersion.new(RUBY_VERSION) >= RubyVersion.new('2.4.0')
{ Numeric => [Integer, Float, Complex, Rational] }
else
{
Integer => [Fixnum, Bignum],
Numeric => [Fixnum, Bignum, Float, Complex, Rational]
}
end
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
value_type = type.first
type = type.class
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.5.5/lib/hashie/extensions/indifferent_access.rb 0000644 0000041 0000041 00000010742 13115743107 023374 0 ustar www-data www-data module 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
def merge(*)
super.convert!
end
def merge!(*)
super.convert!
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.5.5/lib/hashie/extensions/deep_fetch.rb 0000644 0000041 0000041 00000002053 13115743107 021640 0 ustar www-data www-data module 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.5.5/lib/hashie/extensions/merge_initializer.rb 0000644 0000041 0000041 00000001351 13115743107 023254 0 ustar www-data www-data module 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.5.5/lib/hashie/extensions/deep_find.rb 0000644 0000041 0000041 00000003670 13115743107 021475 0 ustar www-data www-data module 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.5.5/lib/hashie/extensions/mash/ 0000755 0000041 0000041 00000000000 13115743107 020155 5 ustar www-data www-data hashie-3.5.5/lib/hashie/extensions/mash/symbolize_keys.rb 0000644 0000041 0000041 00000001777 13115743107 023566 0 ustar www-data www-data module Hashie
module Extensions
module Mash
# Overrides Mash's default behavior of converting keys to strings
#
# @example
# class LazyResponse < Hashie::Mash
# include Hashie::Extensions::Mash::SymbolizedKeys
# end
#
# response = LazyResponse.new("id" => 123, "name" => "Rey").to_h
# #=> {id: 123, name: "Rey"}
#
# @api public
module SymbolizeKeys
# Hook for being included in a class
#
# @api private
# @return [void]
# @raise [ArgumentError] when the base class isn't a Mash
def self.included(base)
fail ArgumentError, "#{base} must descent from Hashie::Mash" unless base <= Hashie::Mash
end
private
# Converts a key to a symbol
#
# @api private
# @param [String, Symbol] key the key to convert to a symbol
# @return [void]
def convert_key(key)
key.to_sym
end
end
end
end
end
hashie-3.5.5/lib/hashie/extensions/mash/safe_assignment.rb 0000644 0000041 0000041 00000000565 13115743107 023656 0 ustar www-data www-data module 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.5.5/lib/hashie/extensions/mash/keep_original_keys.rb 0000644 0000041 0000041 00000003270 13115743107 024347 0 ustar www-data www-data module Hashie
module Extensions
module Mash
# Overrides the indifferent access of a Mash to keep keys in the
# original format given to the Mash.
#
# @example
# class KeepingMash < Hashie::Mash
# include Hashie::Extensions::Mash::KeepOriginalKeys
# end
#
# mash = KeepingMash.new(:symbol_key => :symbol, 'string_key' => 'string')
# mash.to_hash #=> { :symbol_key => :symbol, 'string_key' => 'string' }
# mash['string_key'] == mash[:string_key] #=> true
# mash[:symbol_key] == mash['symbol_key'] #=> true
module KeepOriginalKeys
private
def self.included(descendant)
unless descendant <= Hashie::Mash
fail ArgumentError, "#{descendant} is not a kind of Hashie::Mash"
end
end
# Converts the key when necessary to access the correct Mash key.
#
# @param [Object, String, Symbol] key the key to access.
# @return [Object] the value assigned to the key.
def convert_key(key)
if regular_key?(key)
key
elsif (converted_key = __convert(key)) && regular_key?(converted_key)
converted_key
else
key
end
end
# Converts symbol/string keys to their alternative formats, but leaves
# other keys alone.
#
# @param [Object, String, Symbol] key the key to convert.
# @return [Object, String, Symbol] the converted key.
def __convert(key)
case key
when Symbol then key.to_s
when String then key.to_sym
else key
end
end
end
end
end
end
hashie-3.5.5/lib/hashie/extensions/ruby_version_check.rb 0000644 0000041 0000041 00000000550 13115743107 023435 0 ustar www-data www-data require 'hashie/extensions/ruby_version'
module Hashie
module Extensions
module RubyVersionCheck
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def with_minimum_ruby(version)
yield if RubyVersion.new(RUBY_VERSION) >= RubyVersion.new(version)
end
end
end
end
end
hashie-3.5.5/lib/hashie/extensions/deep_locate.rb 0000644 0000041 0000041 00000006555 13115743107 022031 0 ustar www-data www-data module 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)
comparator = _construct_key_comparator(comparator, object) unless comparator.respond_to?(:call)
_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._construct_key_comparator(search_key, object)
search_key = search_key.to_s if defined?(::ActiveSupport::HashWithIndifferentAccess) && object.is_a?(::ActiveSupport::HashWithIndifferentAccess)
search_key = search_key.to_s if object.respond_to?(:indifferent_access?) && object.indifferent_access?
lambda do |non_callable_object|
->(key, _, _) { key == non_callable_object }
end.call(search_key)
end
def self._deep_locate(comparator, object, result = [])
if object.is_a?(::Enumerable)
if object.any? { |value| _match_comparator?(value, comparator, object) }
result.push object
end
(object.respond_to?(:values) ? object.values : object.entries).each do |value|
_deep_locate(comparator, value, result)
end
end
result
end
def self._match_comparator?(value, comparator, object)
if object.is_a?(::Hash)
key, value = value
else
key = nil
end
comparator.call(key, value, object)
end
end
end
end
hashie-3.5.5/lib/hashie/extensions/deep_merge.rb 0000644 0000041 0000041 00000002243 13115743107 021647 0 ustar www-data www-data module 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.respond_to?(:deep_dup) ? v.deep_dup : v
end
end
end
hash
end
end
end
end
hashie-3.5.5/lib/hashie/extensions/key_conversion.rb 0000644 0000041 0000041 00000000311 13115743107 022602 0 ustar www-data www-data module Hashie
module Extensions
module KeyConversion
def self.included(base)
base.send :include, SymbolizeKeys
base.send :include, StringifyKeys
end
end
end
end
hashie-3.5.5/lib/hashie/extensions/array/ 0000755 0000041 0000041 00000000000 13115743107 020343 5 ustar www-data www-data hashie-3.5.5/lib/hashie/extensions/array/pretty_inspect.rb 0000644 0000041 0000041 00000000657 13115743107 023754 0 ustar www-data www-data module Hashie
module Extensions
module Array
module PrettyInspect
def self.included(base)
base.send :alias_method, :array_inspect, :inspect
base.send :alias_method, :inspect, :hashie_inspect
end
def hashie_inspect
ret = "#<#{self.class} ["
ret << to_a.map(&:inspect).join(', ')
ret << ']>'
ret
end
end
end
end
end
hashie-3.5.5/lib/hashie/extensions/pretty_inspect.rb 0000644 0000041 0000041 00000000663 13115743107 022633 0 ustar www-data www-data module 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.5.5/lib/hashie/extensions/parsers/ 0000755 0000041 0000041 00000000000 13115743107 020704 5 ustar www-data www-data hashie-3.5.5/lib/hashie/extensions/parsers/yaml_erb_parser.rb 0000644 0000041 0000041 00000001025 13115743107 024375 0 ustar www-data www-data require 'yaml'
require 'erb'
module Hashie
module Extensions
module Parsers
class YamlErbParser
def initialize(file_path)
@content = File.read(file_path)
@file_path = file_path.is_a?(Pathname) ? file_path.to_s : 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.5.5/lib/hashie/extensions/stringify_keys.rb 0000644 0000041 0000041 00000003566 13115743107 022635 0 ustar www-data www-data module 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.5.5/lib/hashie/extensions/dash/ 0000755 0000041 0000041 00000000000 13115743107 020144 5 ustar www-data www-data hashie-3.5.5/lib/hashie/extensions/dash/coercion.rb 0000644 0000041 0000041 00000001343 13115743107 022273 0 ustar www-data www-data module 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.5.5/lib/hashie/extensions/dash/indifferent_access.rb 0000644 0000041 0000041 00000002215 13115743107 024307 0 ustar www-data www-data module 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 = translations[name.to_sym] if included_modules.include?(Hashie::Extensions::Dash::PropertyTranslation) && translation_exists?(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.to_sym]
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.5.5/lib/hashie/extensions/dash/property_translation.rb 0000644 0000041 0000041 00000014044 13115743107 024776 0 ustar www-data www-data module 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.5.5/lib/hashie/extensions/ignore_undeclared.rb 0000644 0000041 0000041 00000002675 13115743107 023235 0 ustar www-data www-data module 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)
return unless attributes
klass = self.class
translations = klass.respond_to?(:translations) && klass.translations
attributes.each_pair do |att, value|
next unless klass.property?(att) || (translations && translations.include?(att))
self[att] = value
end
end
def property_exists?(property)
self.class.property?(property)
end
end
end
end
hashie-3.5.5/lib/hashie/extensions/ruby_version.rb 0000644 0000041 0000041 00000003213 13115743107 022277 0 ustar www-data www-data # Copyright (c) Chad Fowler, Rich Kilmer, Jim Weirich and others.
# Portions copyright (c) Engine Yard and Andre Arko
#
# 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.
module Hashie
module Extensions
class RubyVersion
include Comparable
attr_accessor :segments
def initialize(version)
@segments = split_to_segments(version)
end
def <=>(other)
lhsegments = segments
rhsegments = other.segments
lhsize = lhsegments.size
rhsize = rhsegments.size
limit = (lhsize > rhsize ? lhsize : rhsize) - 1
i = 0
while i <= limit
lhs = lhsegments[i] || 0
rhs = rhsegments[i] || 0
i += 1
next if lhs == rhs
return -1 if lhs.is_a?(String) && rhs.is_a?(Numeric)
return 1 if lhs.is_a?(Numeric) && rhs.is_a?(String)
return lhs <=> rhs
end
0
end
private
def split_to_segments(version)
version.scan(/[0-9]+|[a-z]+/i).map do |segment|
/^\d+$/ =~ segment ? segment.to_i : segment
end.freeze
end
end
end
end
hashie-3.5.5/lib/hashie/extensions/strict_key_access.rb 0000644 0000041 0000041 00000003443 13115743107 023257 0 ustar www-data www-data module 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.5.5/lib/hashie/railtie.rb 0000644 0000041 0000041 00000000576 13115743107 017014 0 ustar www-data www-data begin
require 'rails/railtie'
module Hashie
class Railtie < Rails::Railtie
# Set the Hashie.logger to use Rails.logger when used with rails.
initializer 'hashie.configure_logger', after: 'initialize_logger' do
Hashie.logger = Rails.logger
end
end
end
rescue LoadError => e
Hashie.logger.info("Hashie skipping railtie as #{e.message}")
end
hashie-3.5.5/lib/hashie/dash.rb 0000644 0000041 0000041 00000015366 13115743107 016305 0 ustar www-data www-data require '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
if val.is_a?(Proc)
val.arity == 1 ? val.call(self) : val.call
else
val
end
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.5.5/lib/hashie.rb 0000644 0000041 0000041 00000005225 13115743107 015357 0 ustar www-data www-data require 'hashie/logger'
require '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'
autoload :Array, 'hashie/array'
autoload :Utils, 'hashie/utils'
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'
autoload :RubyVersion, 'hashie/extensions/ruby_version'
autoload :RubyVersionCheck, 'hashie/extensions/ruby_version_check'
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 :KeepOriginalKeys, 'hashie/extensions/mash/keep_original_keys'
autoload :SafeAssignment, 'hashie/extensions/mash/safe_assignment'
autoload :SymbolizeKeys, 'hashie/extensions/mash/symbolize_keys'
end
module Array
autoload :PrettyInspect, 'hashie/extensions/array/pretty_inspect'
end
end
class << self
include Hashie::Extensions::StringifyKeys::ClassMethods
include Hashie::Extensions::SymbolizeKeys::ClassMethods
end
require 'hashie/railtie' if defined?(::Rails)
end
hashie-3.5.5/.yardopts 0000644 0000041 0000041 00000000014 13115743107 014660 0 ustar www-data www-data -m markdown
hashie-3.5.5/CONTRIBUTING.md 0000644 0000041 0000041 00000011527 13115743107 015256 0 ustar www-data www-data Contributing 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 *Unreleased*. Make it look like every other line, including your name and link to your Github account.
There are several categorizations of changes that you can choose from. Add your line to the appropriate section, following these conventions:
* **Added** - When you add a new behavior to any class or module (or add a new extension) that does not break backwards compatibility, you should mark it as "added". This is generally a fully new behavior that does not touch any pre-existing public API. Changes here require a MINOR version bump, following the Semantic Versioning specification.
* **Changed** - You should mark any change to the behavior of a public API on any class or module as "changed". Changes here require a MAJOR version bump, following the Semantic Versioning specification.
* **Deprecated** - Any time you deprecate part of the public API on any class or module you should mark the change as "deprecated". Deprecated behavior will be removed in the next MAJOR version bump, but should be left in until then. Changes here require a MINOR version bump, following the Semantic Versioning specification.
* **Removed** - You should mark any behavior that you removed from a public API on any class or module as "removed". Changes here require a MAJOR version bump, following the Semantic Versioning specification.
* **Fixed** - Any time you fix a bug you should mark as "fixed". Changes here require a PATCH version bump.
* **Security** - You should mark any security issue that you fix as "security". Changes here require a PATCH version bump.
* **Miscellaneous** - Mark any other changes you make (i.e. documentation updates, test harness changes, etc.) as "miscellaneous". Changes here require a PATCH version bump.
#### 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.5.5/LICENSE 0000644 0000041 0000041 00000002064 13115743107 014026 0 ustar www-data www-data Copyright (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.5.5/CHANGELOG.md 0000644 0000041 0000041 00000062476 13115743107 014647 0 ustar www-data www-data # Change Log
All notable changes to this project will be documented in this file. This
project adheres to [Semantic Versioning 2.0.0][semver]. Any violations of this
scheme are considered to be bugs.
[semver]: http://semver.org/spec/v2.0.0.html
## [3.5.5] - 2017-02-24
[3.5.5]: https://github.com/intridea/hashie/compare/v3.5.4...v3.5.5
### Added
* [#326](https://github.com/intridea/hashie/pull/326): Added `Hashie::Extensions::Mash::KeepOriginalKeys` to give Mashes the ability to keep the original structure given to it - [@michaelherold](https://github.com/michaelherold).
### Fixed
* [#415](https://github.com/intridea/hashie/pull/415): Fixed Mash logging keys multiple times which lead to a bad user experience or, in some cases, errors - [@michaelherold](https://github.com/michaelherold).
## [3.5.4] - 2017-02-22
[3.5.4]: https://github.com/intridea/hashie/compare/v3.5.3...v3.5.4
### Added
* [#412](https://github.com/intridea/hashie/pull/412): Added a Hashie::Extensions::Mash::SymbolizeKeys extension that overrides the default stringification behavior for keys - [@michaelherold](https://github.com/michaelherold).
### Fixed
* [#409](https://github.com/intridea/hashie/pull/409): Fixed Railtie detection for projects where Rails is defined but Railties are not availble - [@CallumD](https://github.com/callumd).
* [#411](https://github.com/intridea/hashie/pull/411): Fixed a performance regression from 3.4.3 that caused a 10x slowdown in OmniAuth - [@michaelherold](https://github.com/michaelherold).
## [3.5.3] - 2017-02-11
[3.5.3]: https://github.com/intridea/hashie/compare/v3.5.2...v3.5.3
### Fixed
* [#402](https://github.com/intridea/hashie/pull/402): Use a Railtie to set Hashie.logger on rails boot - [@matthewrudy](https://github.com/matthewrudy).
* [#406](https://github.com/intridea/hashie/pull/406): Ensure that subclasses that disable warnings propagate that setting to grandchild classes - [@michaelherold](https://github.com/michaelherold).
* Your contribution here.
## [3.5.2] - 2017-02-10
[3.5.2]: https://github.com/intridea/hashie/compare/v3.5.1...v3.5.2
### Added
* [#395](https://github.com/intridea/hashie/pull/395): Add the ability to disable warnings in Mash subclasses - [@michaelherold](https://github.com/michaelherold).
* [#400](https://github.com/intridea/hashie/pull/400): Fix Hashie.logger load and set the Hashie logger to the Rails logger in a Rails environment - [@michaelherold](https://github.com/michaelherold).
### Fixed
* [#396](https://github.com/intridea/hashie/pull/396): Fix for specs in #381: Incorrect use of shared context meant example was not being run - [@biinari](https://github.com/biinari).
* [#399](https://github.com/intridea/hashie/pull/399): Fix passing Pathname object to Hashie::Mesh.load() - [@albb0920](https://github.com/albb0920).
### Miscellanous
* [#397](https://github.com/intridea/hashie/pull/397): Add the integration specs harness into the main test tasks - [@michaelherold](https://github.com/michaelherold).
## [3.5.1] - 2017-01-31
* [#392](https://github.com/intridea/hashie/pull/392): Fix for #391: Require all dependencies of Hashie::Mash - [@dblock](https://github.com/dblock).
[3.5.1]: https://github.com/intridea/hashie/compare/v3.5.0...v3.5.1
## [3.5.0] - 2017-01-31
* [#386](https://github.com/intridea/hashie/pull/386): Fix for #385: Make `deep_merge` always `deep_dup` nested hashes before merging them in so that there are no shared references between the two hashes being merged. - [@mltsy](https://github.com/mltsy).
* [#389](https://github.com/intridea/hashie/pull/389): Support Ruby 2.4.0 - [@camelmasa](https://github.com/camelmasa).
[3.5.0]: https://github.com/intridea/hashie/compare/v3.4.6...v3.5.0
### Added
* [#381](https://github.com/intridea/hashie/pull/381): Add a logging layer that lets us report potential issues to our users. As the first logged issue, report when a `Hashie::Mash` is attempting to overwrite a built-in method, since that is one of our number one questions - [@michaelherold](https://github.com/michaelherold).
### Changed
* [#384](https://github.com/intridea/hashie/pull/384): Updated to CodeClimate 1.x - [@boffbowsh](https://github.com/boffbowsh).
### Fixed
* [#369](https://github.com/intridea/hashie/pull/369): If a translation for a property exists when using IndifferentAccess and IgnoreUndeclared, use the translation to find the property - [@whitethunder](https://github.com/whitethunder).
* [#376](https://github.com/intridea/hashie/pull/376): Leave string index unchanged if it can't be converted to integer for Array#dig - [@sazor](https://github.com/sazor).
* [#377](https://github.com/intridea/hashie/pull/377): Dont use Rubygems to check ruby version - [@sazor](https://github.com/sazor).
* [#378](https://github.com/intridea/hashie/pull/378): Deep find all searches inside all nested hashes - [@sazor](https://github.com/sazor).
* [#380](https://github.com/intridea/hashie/pull/380): Evaluate procs default values of Dash in object initialization - [@sazor](https://github.com/sazor).
### Miscellanous
* [#387](https://github.com/intridea/hashie/pull/387): Fix builds failing due to Rake 11 having a breaking change - [@michaelherold](https://github.com/michaelherold).
## [3.4.6] - 2016-09-16
[3.4.6]: https://github.com/intridea/hashie/compare/v3.4.5...v3.4.6
### Fixed
* [#368](https://github.com/intridea/hashie/pull/368): Since `hashie/mash` can be required alone, require its dependencies - [@jrafanie](https://github.com/jrafanie).
## [3.4.5] - 2016-09-16
[3.4.5]: https://github.com/intridea/hashie/compare/v3.4.4...v3.4.5
### Added
* [#337](https://github.com/intridea/hashie/pull/337), [#331](https://github.com/intridea/hashie/issues/331): `Hashie::Mash#load` accepts a `Pathname` object - [@gipcompany](https://github.com/gipcompany).
### Deprecated
* [#366](https://github.com/intridea/hashie/pull/366): Hashie is no longer tested on Ruby < 2 - [@dblock](https://github.com/dblock).
### Fixed
* [#358](https://github.com/intridea/hashie/pull/358): Fixed support for Array#dig - [@modosc](https://github.com/modosc).
* [#365](https://github.com/intridea/hashie/pull/365): Ensured ActiveSupport::HashWithIndifferentAccess is defined before use in #deep_locate - [@mikejarema](https://github.com/mikejarema).
### Miscellanous
* [#366](https://github.com/intridea/hashie/pull/366): Added Danger, PR linter - [@dblock](https://github.com/dblock).
## [3.4.4] - 2016-04-29
[3.4.4]: https://github.com/intridea/hashie/compare/v3.4.3...v3.4.4
### Added
* [#349](https://github.com/intridea/hashie/pull/349): Convert `Hashie::Mash#dig` arguments for Ruby 2.3.0 - [@k0kubun](https://github.com/k0kubun).
### Fixed
* [#240](https://github.com/intridea/hashie/pull/240): Fixed nesting twice with Clash keys - [@bartoszkopinski](https://github.com/bartoszkopinski).
* [#317](https://github.com/intridea/hashie/pull/317): Ensured `Hashie::Extensions::MethodQuery` methods return boolean values - [@michaelherold](https://github.com/michaelherold).
* [#319](https://github.com/intridea/hashie/pull/319): Fixed a regression from 3.4.1 where `Hashie::Extensions::DeepFind` is no longer indifference-aware - [@michaelherold](https://github.com/michaelherold).
* [#322](https://github.com/intridea/hashie/pull/322): Fixed `reverse_merge` issue with `Mash` subclasses - [@marshall-lee](https://github.com/marshall-lee).
* [#346](https://github.com/intridea/hashie/pull/346): Fixed `merge` breaking indifferent access - [@docwhat](https://github.com/docwhat), [@michaelherold](https://github.com/michaelherold).
* [#350](https://github.com/intridea/hashie/pull/350): Fixed from string translations used with `IgnoreUndeclared` - [@marshall-lee](https://github.com/marshall-lee).
## [3.4.3] - 2015-10-25
[3.4.3]: https://github.com/intridea/hashie/compare/v3.4.2...v3.4.3
### Added
* [#306](https://github.com/intridea/hashie/pull/306): Added `Hashie::Extensions::Dash::Coercion` - [@marshall-lee](https://github.com/marshall-lee).
* [#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).
### Fixed
* [#304](https://github.com/intridea/hashie/pull/304): Ensured compatibility of `Hash` extensions with singleton objects - [@regexident](https://github.com/regexident).
* [#310](https://github.com/intridea/hashie/pull/310): Fixed `Hashie::Extensions::SafeAssignment` bug with private methods - [@marshall-lee](https://github.com/marshall-lee).
### Miscellaneous
* [#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] - 2015-06-02
[3.4.2]: https://github.com/intridea/hashie/compare/v3.4.1...v3.4.2
### Added
* [#297](https://github.com/intridea/hashie/pull/297): Extracted `Trash`'s behavior into a new `Dash::PropertyTranslation` extension - [@michaelherold](https://github.com/michaelherold).
### Removed
* [#292](https://github.com/intridea/hashie/pull/292): Removed `Mash#id` and `Mash#type` - [@jrochkind](https://github.com/jrochkind).
## [3.4.1] - 2015-03-31
[3.4.1]: https://github.com/intridea/hashie/compare/v3.4.0...v3.4.1
### Added
* [#269](https://github.com/intridea/hashie/pull/272): Added Hashie::Extensions::DeepLocate - [@msievers](https://github.com/msievers).
* [#281](https://github.com/intridea/hashie/pull/281): Added #reverse_merge to Mash to override ActiveSupport's version - [@mgold](https://github.com/mgold).
### Fixed
* [#270](https://github.com/intridea/hashie/pull/277): Fixed ArgumentError raised when using IndifferentAccess and HashWithIndifferentAccess - [@gardenofwine](https://github.com/gardenofwine).
* [#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] - 2015-02-02
[3.4.0]: https://github.com/intridea/hashie/compare/v3.3.2...v3.4.0
### Added
* [#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).
* [#254](https://github.com/intridea/hashie/pull/254): Added public utility methods for stringify and symbolize keys - [@maxlinc](https://github.com/maxlinc).
* [#260](https://github.com/intridea/hashie/pull/260): Added block support to Extensions::DeepMerge - [@galathius](https://github.com/galathius).
* [#271](https://github.com/intridea/hashie/pull/271): Added ability to define defaults based on current hash - [@gregory](https://github.com/gregory).
### Changed
* [#249](https://github.com/intridea/hashie/pull/249): SafeAssignment will now also protect hash-style assignments - [@jrochkind](https://github.com/jrochkind).
* [#264](https://github.com/intridea/hashie/pull/264): Methods such as abc? return true/false with Hashie::Extensions::MethodReader - [@Zloy](https://github.com/Zloy).
### Fixed
* [#247](https://github.com/intridea/hashie/pull/247): Fixed #stringify_keys and #symbolize_keys collision with ActiveSupport - [@bartoszkopinski](https://github.com/bartoszkopinski).
* [#256](https://github.com/intridea/hashie/pull/256): Inherited 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).
* [#261](https://github.com/intridea/hashie/pull/261): Fixed bug where Dash.property modifies argument object - [@d-tw](https://github.com/d-tw).
* [#269](https://github.com/intridea/hashie/pull/269): Added #extractable_options? so ActiveSupport Array#extract_options! can extract it - [@ridiculous](https://github.com/ridiculous).
## [3.3.2] - 2014-11-26
[3.3.2]: https://github.com/intridea/hashie/compare/v3.3.1...v3.3.2
### Added
* [#231](https://github.com/intridea/hashie/pull/231): Added support for coercion on class type that inherit from Hash - [@gregory](https://github.com/gregory).
* [#233](https://github.com/intridea/hashie/pull/233): Custom error messages for required properties in Hashie::Dash subclasses - [@joss](https://github.com/joss).
* [#245](https://github.com/intridea/hashie/pull/245): Added Hashie::Extensions::MethodAccessWithOverride to autoloads - [@Fritzinger](https://github.com/Fritzinger).
### Fixed
* [#221](https://github.com/intridea/hashie/pull/221): Reduced amount of allocated objects on calls with suffixes in Hashie::Mash - [@kubum](https://github.com/kubum).
* [#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).
* [#228](https://github.com/intridea/hashie/pull/228): Made Hashie::Extensions::Parsers::YamlErbParser pass template filename to ERB - [@jperville](https://github.com/jperville).
## [3.3.1] - 2014-08-26
[3.3.1]: https://github.com/intridea/hashie/compare/v3.3.0...v3.3.1
### Added
* [#183](https://github.com/intridea/hashie/pull/183): Added Mash#load with YAML file support - [@gregory](https://github.com/gregory).
* [#189](https://github.com/intridea/hashie/pull/189): Added Rash#fetch - [@medcat](https://github.com/medcat).
* [#204](https://github.com/intridea/hashie/pull/204): Added Hashie::Extensions::MethodOverridingWriter and MethodAccessWithOverride - [@michaelherold](https://github.com/michaelherold).
* [#205](https://github.com/intridea/hashie/pull/205): Added Hashie::Extensions::Mash::SafeAssignment - [@michaelherold](https://github.com/michaelherold).
* [#209](https://github.com/intridea/hashie/pull/209): Added Hashie::Extensions::DeepFind - [@michaelherold](https://github.com/michaelherold).
### Fixed
* [#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).
* [#195](https://github.com/intridea/hashie/pull/195): Ensured 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).
* [#200](https://github.com/intridea/hashie/pull/200): Improved coercion: primitives and error handling - [@maxlinc](https://github.com/maxlinc).
* [#206](https://github.com/intridea/hashie/pull/206): Fixed stack overflow from repetitively including coercion in subclasses - [@michaelherold](https://github.com/michaelherold).
* [#207](https://github.com/intridea/hashie/pull/207): Fixed inheritance of transformations in Trash - [@fobocaster](https://github.com/fobocaster).
## [3.2.0] - 2014-07-10
[3.2.0]: https://github.com/intridea/hashie/compare/v3.1.0...v3.2.0
### Added
* [#177](https://github.com/intridea/hashie/pull/177): Added support for coercing enumerables and collections - [@gregory](https://github.com/gregory).
### Changed
* [#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).
### Fixed
* [#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).
* [#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] - 2014-06-25
[3.1.0]: https://github.com/intridea/hashie/compare/v3.0.0...v3.1.0
### Added
* [#172](https://github.com/intridea/hashie/pull/172): Added Dash and Trash#update_attributes! - [@gregory](https://github.com/gregory).
### Changed
* [#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).
* [#173](https://github.com/intridea/hashie/pull/173): Auto include Dash::IndifferentAccess when IndifferentAccess is included in Dash - [@gregory](https://github.com/gregory).
### Fixed
* [#171](https://github.com/intridea/hashie/pull/171): Include Trash and Dash class name when raising `NoMethodError` - [@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] - 2014-06-03
[3.0.0]: https://github.com/intridea/hashie/compare/v2.1.2...v3.0.0
Note: This version introduces several backward incompatible API changes. See [UPGRADING](UPGRADING.md) for details.
### Added
* [#149](https://github.com/intridea/hashie/issues/149): Allow IgnoreUndeclared and DeepMerge to be used with undeclared properties - [@jhaesus](https://github.com/jhaesus).
### Changed
* [#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).
### Fixed
* [#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).
* [#148](https://github.com/intridea/hashie/pull/148): Consolidated Hashie::Hash#stringify_keys implementation - [@dblock](https://github.com/dblock).
* [#159](https://github.com/intridea/hashie/pull/159): Handle nil intermediate object on deep fetch - [@stephenaument](https://github.com/stephenaument).
## [2.1.2] - 2014-05-12
[2.1.2]: https://github.com/intridea/hashie/compare/v2.1.1...v2.1.2
### Changed
* [#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] - 2014-04-12
[2.1.1]: https://github.com/intridea/hashie/compare/v2.1.0...v2.1.1
### Fixed
* [#131](https://github.com/intridea/hashie/pull/131): Added IgnoreUndeclared, an extension to silently ignore undeclared properties at intialization - [@righi](https://github.com/righi).
* [#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).
* [#144](https://github.com/intridea/hashie/issues/144): Fixed regression invoking `to_hash` with no parameters - [@mbleigh](https://github.com/mbleigh).
## [2.1.0] - 2014-04-06
[2.1.0]: https://github.com/intridea/hashie/compare/v2.0.5...v2.1.0
### Added
* [#134](https://github.com/intridea/hashie/pull/134): Added deep_fetch extension for nested access - [@tylerdooling](https://github.com/tylerdooling).
### Changed
* [#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).
### Removed
* Removed support for Ruby 1.8.7 - [@dblock](https://github.com/dblock).
* [#136](https://github.com/intridea/hashie/issues/136): Removed Hashie::Extensions::Structure - [@markiz](https://github.com/markiz).
### Fixed
* [#69](https://github.com/intridea/hashie/issues/69): Fixed assigning multiple properties in Hashie::Trash - [@einzige](https://github.com/einzige).
* [#99](https://github.com/intridea/hashie/issues/99): Hash#deep_merge raises errors when it encounters integers - [@defsprite](https://github.com/defsprite).
* [#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).
* [#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).
* [#110](https://github.com/intridea/hashie/pull/110): Correctly use Hash#default from Mash#method_missing - [@ryansouza](https://github.com/ryansouza).
* [#111](https://github.com/intridea/hashie/issues/111): Trash#translations correctly maps original to translated names - [@artm](https://github.com/artm).
* [#113](https://github.com/intridea/hashie/issues/113): Fixed Hash#merge with Hashie::Dash - [@spencer1248](https://github.com/spencer1248).
* [#120](https://github.com/intridea/hashie/pull/120): Pass options to recursive to_hash calls - [@pwillett](https://github.com/pwillett).
* [#129](https://github.com/intridea/hashie/pull/129): Added Trash#permitted_input_keys and inverse_translations - [@artm](https://github.com/artm).
* [#130](https://github.com/intridea/hashie/pull/130): IndifferentAccess now works without MergeInitializer - [@npj](https://github.com/npj).
* [#133](https://github.com/intridea/hashie/pull/133): Fixed Hash##to_hash with symbolize_keys - [@mhuggins](https://github.com/mhuggins).
### Miscellaneous
* Ruby style now enforced with Rubocop - [@dblock](https://github.com/dblock).
## [2.0.5] - 2013-05-10
[2.0.5]: https://github.com/intridea/hashie/compare/v2.0.4...v2.0.5
### Fixed
* [#96](https://github.com/intridea/hashie/pull/96): Made coercion work better with non-symbol keys in Hashie::Mash - [@wapcaplet](https://github.com/wapcaplet).
## [2.0.4] - 2013-04-24
[2.0.4]: https://github.com/intridea/hashie/compare/v2.0.3...v2.0.4
### Fixed
* [#94](https://github.com/intridea/hashie/pull/94): Made #fetch method consistent with normal Hash - [@markiz](https://github.com/markiz).
### Miscellaneous
* [#90](https://github.com/intridea/hashie/pull/90): Various doc tweaks - [@craiglittle](https://github.com/craiglittle).
## [2.0.3] - 2013-03-18
[2.0.3]: https://github.com/intridea/hashie/compare/v2.0.2...v2.0.3
### Fixed
* [#68](https://github.com/intridea/hashie/pull/68): Fixed #replace - [@jimeh](https://github.com/jimeh).
* [#88](https://github.com/intridea/hashie/pull/88): Hashie::Mash.new(abc: true).respond_to?(:abc?) works - [@7even](https://github.com/7even).
## [2.0.2] - 2013-02-26
[2.0.2]: https://github.com/intridea/hashie/compare/v2.0.1...v2.0.2
### Fixed
* [#85](https://github.com/intridea/hashie/pull/85): Added symbolize_keys back to to_hash - [@cromulus](https://github.com/cromulus).
## [2.0.1] - 2013-02-26
[2.0.1]: https://github.com/intridea/hashie/compare/v2.0.0...v2.0.1
### Removed
* [#81](https://github.com/intridea/hashie/pull/81): Removed Mash#object_id override - [@matschaffer](https://github.com/matschaffer).
### Miscellaneous
* Gem cleanup: removed VERSION, Gemfile.lock - [@jch](https://github.com/jch), [@mbleigh](https://github.com/mbleigh).
## [2.0.0] - 2013-02-16
[2.0.0]: https://github.com/intridea/hashie/compare/v1.2.0...v2.0.0
### Added
* [#41](https://github.com/intridea/hashie/pull/41): DeepMerge extension - [@nashby](https://github.com/nashby).
* [#78](https://github.com/intridea/hashie/pull/78): Merge and update accepts a block - [@jch](https://github.com/jch).
### Changed
* [#28](https://github.com/intridea/hashie/pull/28): Hashie::Extensions::Coercion coerce_keys takes arguments - [@mattfawcett](https://github.com/mattfawcett).
* [#77](https://github.com/intridea/hashie/pull/77): Removed id, type, and object_id as special allowable keys - [@jch](https://github.com/jch).
### Fixed
* [#27](https://github.com/intridea/hashie/pull/27): Initialize with merge coerces values - [@mattfawcett](https://github.com/mattfawcett).
* [#39](https://github.com/intridea/hashie/pull/39): Trash removes translated values on initialization - [@sleverbor](https://github.com/sleverbor).
* [#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): Updated respond_to? method signature to match ruby core definition - [@dlupu](https://github.com/dlupu).
* [#63](https://github.com/intridea/hashie/pull/63): Dash defaults are dup'ed before assigned - [@ohrite](https://github.com/ohrite).
* [#66](https://github.com/intridea/hashie/pull/66): Mash#fetch works with symbol or string keys - [@arthwood](https://github.com/arthwood).
### Miscellaneous
* [#72](https://github.com/intridea/hashie/pull/72): Updated gemspec with license info - [@jordimassaguerpla](https://github.com/jordimassaguerpla).
hashie-3.5.5/README.md 0000644 0000041 0000041 00000065766 13115743107 014322 0 ustar www-data www-data # Hashie
[](https://gitter.im/intridea/hashie?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[](http://badge.fury.io/rb/hashie)
[](https://travis-ci.org/intridea/hashie)
[](https://gemnasium.com/intridea/hashie)
[](https://codeclimate.com/github/intridea/hashie)
[](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
```
## Upgrading
You're reading the documentation for the stable release of Hashie, 3.5.5. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version.
## Hash Extensions
The library is broken up into a number of atomically includable 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`.
## Logging
Hashie has a built-in logger that you can override. By default, it logs to `STDOUT` but can be replaced by any `Logger` class. The logger is accessible on the Hashie module, as shown below:
```ruby
# Set the logger to the Rails logger
Hashie.logger = Rails.logger
```
### 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
include Hashie::Extensions::MergeInitializer
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
!!(v =~ /\A(true|t|yes|y|1)\z/i)
when Numeric
!v.to_i.zero?
else
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 your Hash subclass to allow you to use Strings or Symbols interchangeably as keys; similar to the `params` hash in Rails.
In addition, IndifferentAccess will also inject itself into sub-hashes so they behave the same.
Example:
```ruby
class MyHash < Hash
include Hashie::Extensions::MergeInitializer
include Hashie::Extensions::IndifferentAccess
end
myhash = MyHash.new(:cat => 'meow', 'dog' => 'woof')
myhash['cat'] # => "meow"
myhash[:cat] # => "meow"
myhash[:dog] # => "woof"
myhash['dog'] # => "woof"
# Auto-Injecting into sub-hashes.
myhash['fishes'] = {}
myhash['fishes'].class # => Hash
myhash['fishes'][:food] = 'flakes'
myhash['fishes']['food'] # => "flakes"
```
### 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 also load with a `Pathname` object:
```ruby
mash = Mash.load(Pathname 'settings/twitter.yml')
mash.development.api_key # => 'localhost'
```
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' }
```
Since Mash gives you the ability to set arbitrary keys that then act as methods, Hashie logs when there is a conflict between a key and a pre-existing method. You can set the logger that this logs message to via the global Hashie logger:
```ruby
Hashie.logger = Rails.logger
```
You can also disable the logging in subclasses of Mash:
```ruby
class Response < Hashie::Mash
disable_warnings
end
```
### Mash Extension: KeepOriginalKeys
This extension can be mixed into a Mash to keep the form of any keys passed directly into the Mash. By default, Mash converts keys to strings to give indifferent access. This extension still allows indifferent access, but keeps the form of the keys to eliminate confusion when you're not expecting the keys to change.
```ruby
class KeepingMash < ::Hashie::Mash
include Hashie::Extensions::Mash::KeepOriginalKeys
end
mash = KeepingMash.new(:symbol_key => :symbol, 'string_key' => 'string')
mash.to_hash == { :symbol_key => :symbol, 'string_key' => 'string' } #=> true
mash.symbol_key #=> :symbol
mash[:symbol_key] #=> :symbol
mash['symbol_key'] #=> :symbol
mash.string_key #=> 'string'
mash['string_key'] #=> 'string'
mash[:string_key] #=> 'string'
```
### 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
```
### Mash Extension:: SymbolizeKeys
This extension can be mixed into a Mash to change the default behavior of converting keys to strings. After mixing this extension into a Mash, the Mash will convert all keys to symbols.
```ruby
class SymbolizedMash < ::Hashie::Mash
include Hashie::Extensions::Mash::SymbolizeKeys
end
symbol_mash = SymbolizedMash.new
symbol_mash['test'] = 'value'
symbol_mash.test #=> 'value'
symbol_mash.to_h #=> {test: 'value'}
```
There is a major benefit and coupled with a major trade-off to this decision (at least on older Rubies). As a benefit, by using symbols as keys, you will be able to use the implicit conversion of a Mash via the `#to_hash` method to destructure (or splat) the contents of a Mash out to a block. This can be handy for doing iterations through the Mash's keys and values, as follows:
```ruby
symbol_mash = SymbolizedMash.new(id: 123, name: 'Rey')
symbol_mash.each do |key, value|
# key is :id, then :name
# value is 123, then 'Rey'
end
```
However, on Rubies less than 2.0, this means that every key you send to the Mash will generate a symbol. Since symbols are not garbage-collected on older versions of Ruby, this can cause a slow memory leak when using a symbolized Mash with data generated from user input.
## 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.5.5/hashie.gemspec 0000644 0000041 0000041 00000001711 13115743107 015625 0 ustar www-data www-data require 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', '< 11'
gem.add_development_dependency 'rspec', '~> 3.0'
gem.add_development_dependency 'rspec-pending_for', '~> 0.1'
end