Added Mash#load with YAML file support.

This commit is contained in:
gregory 2014-07-14 07:31:34 -04:00 committed by dB
parent bff2a8908c
commit 59069f13ea
6 changed files with 180 additions and 0 deletions

View File

@ -1,5 +1,6 @@
## 3.2.1 (Next)
* [#183](https://github.com/intridea/hashie/pull/183) Added Mash#load with YAML file support - [@gregory](https://github.com/gregory).
* Your contribution here.
## 3.2.0 (7/10/2014)

View File

@ -224,6 +224,49 @@ mash.inspect # => <Hashie::Mash>
**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.
Mash allows you also to transform any files into a Mash objects.
### Example:
```yml
#/etc/config/settings/twitter.yml
development:
api_key: 'api_key'
production:
api_key: <%= ENV['API_KEY'] %> #let's say that ENV['API_KEY'] is set to 'abcd'
```
```ruby
mash = Mash.load('settings/twitter.yml')
mash.development.api_key # => 'localhost'
mash.development.api_key = "foo" # => <# RuntimeError can't modify frozen ...>
mash.development.api_key? # => true
```
You can access a Mash from another class:
```ruby
mash = Mash.load('settings/twitter.yml')[ENV['RACK_ENV']]
Twitter.extend mash.to_module # NOTE: if you want another name than settings, call: to_module('my_settings')
Twitter.settings.api_key # => 'abcd'
```
You can use another parser (by default: YamlErbParser):
```
#/etc/data/user.csv
id | name | lastname
---|------------- | -------------
1 |John | Doe
2 |Laurent | Garnier
```
```ruby
mash = Mash.load('data/user.csv', parser: MyCustomCsvParser)
# => { 1 => { name: 'John', lastname: 'Doe'}, 2 => { name: 'Laurent', lastname: 'Garnier' } }
mash[1] #=> { name: 'John', lastname: 'Doe' }
```
## 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.

View File

@ -24,6 +24,10 @@ module Hashie
autoload :PrettyInspect, 'hashie/extensions/pretty_inspect'
autoload :KeyConversion, 'hashie/extensions/key_conversion'
module Parsers
autoload :YamlErbParser, 'hashie/extensions/parsers/yaml_erb_parser'
end
module Dash
autoload :IndifferentAccess, 'hashie/extensions/dash/indifferent_access'
end

View File

@ -0,0 +1,21 @@
require 'yaml'
require 'erb'
module Hashie
module Extensions
module Parsers
class YamlErbParser
def initialize(file_path)
@content = File.read(file_path)
end
def perform
YAML.load ERB.new(@content).result
end
def self.perform(file_path)
new(file_path).perform
end
end
end
end
end

View File

@ -59,6 +59,25 @@ module Hashie
ALLOWED_SUFFIXES = %w(? ! = _)
def self.load(path, options = {})
@_mashes ||= new do |h, file_path|
fail ArgumentError, "The following file doesn't exist: #{file_path}" unless File.file?(file_path)
parser = options.fetch(:parser) { Hashie::Extensions::Parsers::YamlErbParser }
h[file_path] = new(parser.perform(file_path)).freeze
end
@_mashes[path]
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

View File

@ -499,4 +499,96 @@ describe Hashie::Mash do
end
end
end
describe '.load(filename, options = {})' do
let(:config) do
{
'production' => {
'foo' => 'production_foo'
}
}
end
let(:path) { 'database.yml' }
let(:parser) { double(:parser) }
subject { described_class.load(path, parser: parser) }
before do |ex|
unless ex.metadata == :test_cache
described_class.instance_variable_set('@_mashes', nil) # clean the cached mashes
end
end
context 'if the file exists' do
before do
expect(File).to receive(:file?).with(path).and_return(true)
expect(parser).to receive(:perform).with(path).and_return(config)
end
it { is_expected.to be_a(Hashie::Mash) }
it 'return a Mash from a file' do
expect(subject.production).not_to be_nil
expect(subject.production.keys).to eq config['production'].keys
expect(subject.production.foo).to eq config['production']['foo']
end
it 'freeze the attribtues' do
expect { subject.production = {} }.to raise_exception(RuntimeError, /can't modify frozen/)
end
end
context 'if the fils does not exists' do
before do
expect(File).to receive(:file?).with(path).and_return(false)
end
it 'raise an ArgumentError' do
expect { subject }.to raise_exception(ArgumentError)
end
end
describe 'results are cached' do
let(:parser) { double(:parser) }
subject { described_class.load(path, parser: parser) }
before do
expect(File).to receive(:file?).with(path).and_return(true)
expect(File).to receive(:file?).with("#{path}+1").and_return(true)
expect(parser).to receive(:perform).once.with(path).and_return(config)
expect(parser).to receive(:perform).once.with("#{path}+1").and_return(config)
end
it 'cache the loaded yml file', :test_cache do
2.times do
expect(subject).to be_a(described_class)
expect(described_class.load("#{path}+1", parser: parser)).to be_a(described_class)
end
expect(subject.object_id).to eq subject.object_id
end
end
end
describe '#to_module(mash_method_name)' do
let(:mash) { described_class.new }
subject { Class.new.extend mash.to_module }
it 'defines a settings method on the klass class that extends the module' do
expect(subject).to respond_to(:settings)
expect(subject.settings).to eq mash
end
context 'when a settings_method_name is set' do
let(:mash_method_name) { 'config' }
subject { Class.new.extend mash.to_module(mash_method_name) }
it 'defines a settings method on the klass class that extends the module' do
expect(subject).to respond_to(mash_method_name.to_sym)
expect(subject.send(mash_method_name.to_sym)).to eq mash
end
end
end
end