summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gemfile.lock1
-rw-r--r--README.markdown23
-rw-r--r--hashie.gemspec1
-rw-r--r--lib/hashie/dash.rb14
-rw-r--r--lib/hashie/extensions/coercion.rb12
-rw-r--r--lib/hashie/extensions/indifferent_access.rb8
-rw-r--r--lib/hashie/extensions/key_conversion.rb42
-rw-r--r--lib/hashie/extensions/merge_initializer.rb4
-rw-r--r--lib/hashie/extensions/method_access.rb6
-rw-r--r--lib/hashie/hash.rb6
-rw-r--r--lib/hashie/mash.rb43
-rw-r--r--lib/hashie/trash.rb46
-rw-r--r--spec/hashie/extensions/coercion_spec.rb21
-rw-r--r--spec/hashie/extensions/indifferent_access_spec.rb10
-rw-r--r--spec/hashie/extensions/key_conversion_spec.rb42
-rw-r--r--spec/hashie/mash_spec.rb104
-rw-r--r--spec/hashie/trash_spec.rb75
17 files changed, 414 insertions, 44 deletions
diff --git a/Gemfile.lock b/Gemfile.lock
index 5bf93d6..05efea4 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -26,6 +26,7 @@ GEM
PLATFORMS
java
ruby
+ x86-mingw32
DEPENDENCIES
growl
diff --git a/README.markdown b/README.markdown
index 80061c4..2402dcf 100644
--- a/README.markdown
+++ b/README.markdown
@@ -137,6 +137,11 @@ to JSON and XML parsed hashes.
mash.author!.name = "Michael Bleigh"
mash.author # => <Hashie::Mash name="Michael Bleigh">
+ mash = Mash.new
+ # use under-bang methods for multi-level testing
+ mash.author_.name? # => false
+ 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.
@@ -146,7 +151,7 @@ to false or nil. In order to check if a key has been set at all, use the
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 execption if unset.
+required. Required properties will raise an exception if unset.
### Example:
@@ -181,6 +186,20 @@ when it is initialized using a hash such as through:
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:
+
+ 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
+
+ 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
@@ -195,7 +214,7 @@ provide.
c = Hashie::Clash.new
c.where(:abc => 'def').order(:created_at)
- 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!
diff --git a/hashie.gemspec b/hashie.gemspec
index 5f935b8..f3fd184 100644
--- a/hashie.gemspec
+++ b/hashie.gemspec
@@ -13,6 +13,7 @@ Gem::Specification.new do |gem|
gem.name = "hashie"
gem.require_paths = ['lib']
gem.version = Hashie::VERSION
+ gem.license = "MIT"
gem.add_development_dependency 'rake', '~> 0.9.2'
gem.add_development_dependency 'rspec', '~> 2.5'
diff --git a/lib/hashie/dash.rb b/lib/hashie/dash.rb
index cbfc1f5..34228c7 100644
--- a/lib/hashie/dash.rb
+++ b/lib/hashie/dash.rb
@@ -12,8 +12,8 @@ module Hashie
#
# It is preferrable to a Struct because of the in-class
# API for defining properties as well as per-property defaults.
- class Dash < Hashie::Hash
- include Hashie::PrettyInspect
+ class Dash < Hash
+ include PrettyInspect
alias_method :to_s, :inspect
# Defines a property on the Dash. Options are
@@ -93,9 +93,7 @@ module Hashie
self[prop] = value
end
- attributes.each_pair do |att, value|
- self[att] = value
- end if attributes
+ initialize_attributes(attributes)
assert_required_properties_set!
end
@@ -122,6 +120,12 @@ module Hashie
private
+ def initialize_attributes(attributes)
+ attributes.each_pair do |att, value|
+ self[att] = value
+ end if attributes
+ end
+
def assert_property_exists!(property)
unless self.class.property?(property)
raise NoMethodError, "The property '#{property}' is not defined for this Dash."
diff --git a/lib/hashie/extensions/coercion.rb b/lib/hashie/extensions/coercion.rb
index 848abe1..67ad607 100644
--- a/lib/hashie/extensions/coercion.rb
+++ b/lib/hashie/extensions/coercion.rb
@@ -29,18 +29,22 @@ module Hashie
# and then by calling Class.new with the value as an argument
# in either case.
#
- # @param [Object] key the key you would like to be coerced.
- # @param [Class] into the class into which you want the key coerced.
+ # @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(key, into)
- (@key_coercions ||= {})[key] = into
+ def coerce_key(*attrs)
+ @key_coercions ||= {}
+ into = attrs.pop
+ attrs.each { |key| @key_coercions[key] = into }
end
+ alias :coerce_keys :coerce_key
+
# Returns a hash of any existing key coercions.
def key_coercions
@key_coercions || {}
diff --git a/lib/hashie/extensions/indifferent_access.rb b/lib/hashie/extensions/indifferent_access.rb
index d4c0238..56af066 100644
--- a/lib/hashie/extensions/indifferent_access.rb
+++ b/lib/hashie/extensions/indifferent_access.rb
@@ -31,6 +31,10 @@ module Hashie
alias_method "regular_#{m}", m
alias_method m, "indifferent_#{m}"
end
+
+ %w(include? member? has_key?).each do |key_alias|
+ alias_method key_alias, :indifferent_key?
+ end
end
end
@@ -38,7 +42,7 @@ module Hashie
# 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, Hashie::Extensions::IndifferentAccess
+ (class << hash; self; end).send :include, IndifferentAccess
hash.convert!
end
@@ -64,7 +68,7 @@ module Hashie
def convert_value(value)
if hash_lacking_indifference?(value)
- Hashie::Extensions::IndifferentAccess.inject(value.dup)
+ IndifferentAccess.inject(value.dup)
elsif value.is_a?(::Array)
value.dup.replace(value.map { |e| convert_value(e) })
else
diff --git a/lib/hashie/extensions/key_conversion.rb b/lib/hashie/extensions/key_conversion.rb
index a98e79d..00d20df 100644
--- a/lib/hashie/extensions/key_conversion.rb
+++ b/lib/hashie/extensions/key_conversion.rb
@@ -9,6 +9,7 @@ module Hashie
# test # => {'abc' => 'def'}
def stringify_keys!
keys.each do |k|
+ stringify_keys_recursively!(self[k])
self[k.to_s] = self.delete(k)
end
self
@@ -19,10 +20,29 @@ module Hashie
def stringify_keys
dup.stringify_keys!
end
+
+ protected
+
+ # Stringify all keys recursively within nested
+ # hashes and arrays.
+ def stringify_keys_recursively!(object)
+ if self.class === object
+ object.stringify_keys!
+ elsif ::Array === object
+ object.each do |i|
+ stringify_keys_recursively!(i)
+ end
+ object
+ elsif object.respond_to?(:stringify_keys!)
+ object.stringify_keys!
+ else
+ object
+ end
+ end
end
module SymbolizeKeys
- # Convert all keys in the hash to strings.
+ # Convert all keys in the hash to symbols.
#
# @example
# test = {'abc' => 'def'}
@@ -30,6 +50,7 @@ module Hashie
# test # => {:abc => 'def'}
def symbolize_keys!
keys.each do |k|
+ symbolize_keys_recursively!(self[k])
self[k.to_sym] = self.delete(k)
end
self
@@ -40,6 +61,25 @@ module Hashie
def symbolize_keys
dup.symbolize_keys!
end
+
+ protected
+
+ # Symbolize all keys recursively within nested
+ # hashes and arrays.
+ def symbolize_keys_recursively!(object)
+ if self.class === object
+ object.symbolize_keys!
+ elsif ::Array === object
+ object.each do |i|
+ symbolize_keys_recursively!(i)
+ end
+ object
+ elsif object.respond_to?(:symbolize_keys!)
+ object.symbolize_keys!
+ else
+ object
+ end
+ end
end
module KeyConversion
diff --git a/lib/hashie/extensions/merge_initializer.rb b/lib/hashie/extensions/merge_initializer.rb
index f9ccc2e..19a9d28 100644
--- a/lib/hashie/extensions/merge_initializer.rb
+++ b/lib/hashie/extensions/merge_initializer.rb
@@ -17,7 +17,9 @@ module Hashie
module MergeInitializer
def initialize(hash = {}, default = nil, &block)
default ? super(default) : super(&block)
- update(hash)
+ hash.each do |key, value|
+ self[key] = value
+ end
end
end
end
diff --git a/lib/hashie/extensions/method_access.rb b/lib/hashie/extensions/method_access.rb
index 50ee46c..39539a5 100644
--- a/lib/hashie/extensions/method_access.rb
+++ b/lib/hashie/extensions/method_access.rb
@@ -27,7 +27,7 @@ module Hashie
#
# user.not_declared # => NoMethodError
module MethodReader
- def respond_to?(name)
+ def respond_to?(name, include_private = false)
return true if key?(name.to_s) || key?(name.to_sym)
super
end
@@ -57,7 +57,7 @@ module Hashie
# h['awesome'] # => 'sauce'
#
module MethodWriter
- def respond_to?(name)
+ def respond_to?(name, include_private = false)
return true if name.to_s =~ /=$/
super
end
@@ -96,7 +96,7 @@ module Hashie
# h.def? # => false
# h.hji? # => NoMethodError
module MethodQuery
- def respond_to?(name)
+ def respond_to?(name, include_private = false)
return true if name.to_s =~ /(.*)\?$/ && (key?($1) || key?($1.to_sym))
super
end
diff --git a/lib/hashie/hash.rb b/lib/hashie/hash.rb
index 53af9e4..3a6ad52 100644
--- a/lib/hashie/hash.rb
+++ b/lib/hashie/hash.rb
@@ -5,7 +5,7 @@ module Hashie
# functions baked in such as stringify_keys that may
# not be available in all libraries.
class Hash < ::Hash
- include Hashie::HashExtensions
+ include HashExtensions
# Converts a mash back to a hash (with stringified keys)
def to_hash
@@ -14,10 +14,10 @@ module Hashie
if self[k].is_a?(Array)
out[k] ||= []
self[k].each do |array_object|
- out[k] << (Hashie::Hash === array_object ? array_object.to_hash : array_object)
+ out[k] << (Hash === array_object ? array_object.to_hash : array_object)
end
else
- out[k] = Hashie::Hash === self[k] ? self[k].to_hash : self[k]
+ out[k] = Hash === self[k] ? self[k].to_hash : self[k]
end
end
out
diff --git a/lib/hashie/mash.rb b/lib/hashie/mash.rb
index 673e210..3c4e6c7 100644
--- a/lib/hashie/mash.rb
+++ b/lib/hashie/mash.rb
@@ -14,6 +14,7 @@ module Hashie
# * Assignment (<tt>=</tt>): Sets the attribute of the given method name.
# * Existence (<tt>?</tt>): Returns true or false depending on whether that key has been set.
# * Bang (<tt>!</tt>): Forces the existence of this key, used for deep Mashes. Think of it as "touch" for mashes.
+ # * Under Bang (<tt>_</tt>): Like Bang, but returns a new Mash rather than creating a key. Used to test existance in deep Mashes.
#
# == Basic Example
#
@@ -42,7 +43,18 @@ module Hashie
# mash.author!.name = "Michael Bleigh"
# mash.author # => <Mash name="Michael Bleigh">
#
- class Mash < Hashie::Hash
+ # == Under Bang Example
+ #
+ # mash = Mash.new
+ # mash.author # => nil
+ # mash.author_ # => <Mash>
+ # mash.author_.name # => nil
+ #
+ # mash = Mash.new
+ # mash.author_.name = "Michael Bleigh" (assigned to temp object)
+ # mash.author # => <Mash>
+ #
+ class Mash < Hash
include Hashie::PrettyInspect
alias_method :to_s, :inspect
@@ -58,11 +70,15 @@ module Hashie
class << self; alias [] new; end
def id #:nodoc:
- key?("id") ? self["id"] : super
+ self["id"]
end
def type #:nodoc:
- key?("type") ? self["type"] : super
+ self["type"]
+ end
+
+ def object_id #:nodoc:
+ self["object_id"]
end
alias_method :regular_reader, :[]
@@ -91,6 +107,21 @@ module Hashie
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, default_value = nil)
+ self[key] || block_given? && yield(key) || default_value || super(key)
+ end
+
def delete(key)
super(convert_key(key))
end
@@ -155,7 +186,7 @@ module Hashie
def method_missing(method_name, *args, &blk)
return self.[](method_name, &blk) if key?(method_name)
- match = method_name.to_s.match(/(.*?)([?=!]?)$/)
+ match = method_name.to_s.match(/(.*?)([?=!_]?)$/)
case match[2]
when "="
self[match[1]] = args.first
@@ -163,6 +194,8 @@ module Hashie
!!self[match[1]]
when "!"
initializing_reader(match[1])
+ when "_"
+ underbang_reader(match[1])
else
default(method_name, *args, &blk)
end
@@ -178,6 +211,8 @@ module Hashie
case val
when self.class
val.dup
+ when Hash
+ duping ? val.dup : val
when ::Hash
val = val.dup if duping
self.class.new(val)
diff --git a/lib/hashie/trash.rb b/lib/hashie/trash.rb
index 7babcca..e34765f 100644
--- a/lib/hashie/trash.rb
+++ b/lib/hashie/trash.rb
@@ -7,23 +7,39 @@ module Hashie
# 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 < Hashie::Dash
+ class Trash < Dash
# Defines a property on the Trash. Options are as follows:
#
# * <tt>:default</tt> - Specify a default value for this property, to be
# returned before a value is set on the property in a new Dash.
# * <tt>:from</tt> - Specify the original key name that will be write only.
+ # * <tt>:with</tt> - Specify a lambda to be used to convert value.
+ # * <tt>:transform_with</tt> - Specify a lambda to be used to convert value
+ # without using the :from option. It transform the property itself.
def self.property(property_name, options = {})
super
if options[:from]
+ if property_name.to_sym == options[:from].to_sym
+ raise ArgumentError, "Property name (#{property_name}) and :from option must not be the same"
+ end
translations << options[:from].to_sym
- class_eval <<-RUBY
- def #{options[:from]}=(val)
- self[:#{property_name}] = val
+ if options[:with].respond_to? :call
+ class_eval do
+ define_method "#{options[:from]}=" do |val|
+ self[property_name.to_sym] = options[:with].call(val)
+ end
end
- RUBY
+ else
+ class_eval <<-RUBY
+ def #{options[:from]}=(val)
+ self[:#{property_name}] = val
+ end
+ RUBY
+ end
+ elsif options[:transform_with].respond_to? :call
+ transforms[property_name.to_sym] = options[:transform_with]
end
end
@@ -32,6 +48,8 @@ module Hashie
def []=(property, value)
if self.class.translations.include? property.to_sym
send("#{property}=", value)
+ elsif self.class.transforms.key? property.to_sym
+ super property, self.class.transforms[property.to_sym].call(value)
elsif property_exists? property
super
end
@@ -43,6 +61,10 @@ module Hashie
@translations ||= []
end
+ def self.transforms
+ @transforms ||= {}
+ end
+
# Raises an NoMethodError if the property doesn't exist
#
def property_exists?(property)
@@ -51,5 +73,19 @@ module Hashie
end
true
end
+
+ private
+
+ # 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.include?(k.to_sym)
+ self[k] = v
+ true
+ end
+ end
+ super attributes_copy
+ end
end
end
diff --git a/spec/hashie/extensions/coercion_spec.rb b/spec/hashie/extensions/coercion_spec.rb
index c5d932a..8dcf6fd 100644
--- a/spec/hashie/extensions/coercion_spec.rb
+++ b/spec/hashie/extensions/coercion_spec.rb
@@ -17,7 +17,10 @@ describe Hashie::Extensions::Coercion do
end
before(:each) do
- class ExampleCoercableHash < Hash; include Hashie::Extensions::Coercion end
+ class ExampleCoercableHash < Hash
+ include Hashie::Extensions::Coercion
+ include Hashie::Extensions::MergeInitializer
+ end
end
subject { ExampleCoercableHash }
let(:instance){ subject.new }
@@ -32,6 +35,15 @@ describe Hashie::Extensions::Coercion do
instance[:foo].should be_coerced
end
+ it "should support an array of keys" do
+ subject.coerce_keys :foo, :bar, Coercable
+
+ instance[:foo] = "bar"
+ instance[:bar] = "bax"
+ instance[:foo].should be_coerced
+ instance[:bar].should be_coerced
+ end
+
it 'should just call #new if no coerce method is available' do
subject.coerce_key :foo, Initializable
@@ -39,6 +51,13 @@ describe Hashie::Extensions::Coercion do
instance[:foo].value.should == "String"
instance[:foo].should_not be_coerced
end
+
+ it "should coerce when the merge initializer is used" do
+ subject.coerce_key :foo, Coercable
+ instance = subject.new(:foo => "bar")
+
+ instance[:foo].should be_coerced
+ end
end
describe '.coerce_value' do
diff --git a/spec/hashie/extensions/indifferent_access_spec.rb b/spec/hashie/extensions/indifferent_access_spec.rb
index 7c421cb..382d930 100644
--- a/spec/hashie/extensions/indifferent_access_spec.rb
+++ b/spec/hashie/extensions/indifferent_access_spec.rb
@@ -38,11 +38,19 @@ describe Hashie::Extensions::IndifferentAccess do
end
describe '#key?' do
+ let(:h) { subject.new(:foo => 'bar') }
+
it 'should find it indifferently' do
- h = subject.new(:foo => 'bar')
h.should be_key(:foo)
h.should be_key('foo')
end
+
+ %w(include? member? has_key?).each do |key_alias|
+ it "should be aliased as #{key_alias}" do
+ h.send(key_alias.to_sym, :foo).should be(true)
+ h.send(key_alias.to_sym, 'foo').should be(true)
+ end
+ end
end
describe '#update' do
diff --git a/spec/hashie/extensions/key_conversion_spec.rb b/spec/hashie/extensions/key_conversion_spec.rb
index 72e1614..4edb022 100644
--- a/spec/hashie/extensions/key_conversion_spec.rb
+++ b/spec/hashie/extensions/key_conversion_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Hashie::Extensions::KeyConversion do
subject do
- klass = Class.new(Hash)
+ klass = Class.new(::Hash)
klass.send :include, Hashie::Extensions::KeyConversion
klass
end
@@ -16,6 +16,24 @@ describe Hashie::Extensions::KeyConversion do
(instance.keys & %w(abc 123)).size.should == 2
end
+ it 'should do deep conversion within nested hashes' do
+ instance[:ab] = subject.new
+ instance[:ab][:cd] = subject.new
+ instance[:ab][:cd][:ef] = 'abcdef'
+ instance.stringify_keys!
+ instance.should == {'ab' => {'cd' => {'ef' => 'abcdef'}}}
+ end
+
+ it 'should do deep conversion within nested arrays' do
+ instance[:ab] = []
+ instance[:ab] << subject.new
+ instance[:ab] << subject.new
+ instance[:ab][0][:cd] = 'abcd'
+ instance[:ab][1][:ef] = 'abef'
+ instance.stringify_keys!
+ instance.should == {'ab' => [{'cd' => 'abcd'}, {'ef' => 'abef'}]}
+ end
+
it 'should return itself' do
instance.stringify_keys!.should == instance
end
@@ -44,13 +62,31 @@ describe Hashie::Extensions::KeyConversion do
(instance.keys & [:abc, :def]).size.should == 2
end
+ it 'should do deep conversion within nested hashes' do
+ instance['ab'] = subject.new
+ instance['ab']['cd'] = subject.new
+ instance['ab']['cd']['ef'] = 'abcdef'
+ instance.symbolize_keys!
+ instance.should == {:ab => {:cd => {:ef => 'abcdef'}}}
+ end
+
+ it 'should do deep conversion within nested arrays' do
+ instance['ab'] = []
+ instance['ab'] << subject.new
+ instance['ab'] << subject.new
+ instance['ab'][0]['cd'] = 'abcd'
+ instance['ab'][1]['ef'] = 'abef'
+ instance.symbolize_keys!
+ instance.should == {:ab => [{:cd => 'abcd'}, {:ef => 'abef'}]}
+ end
+
it 'should return itself' do
instance.symbolize_keys!.should == instance
end
end
- describe '#stringify_keys' do
- it 'should convert keys to strings' do
+ describe '#symbolize_keys' do
+ it 'should convert keys to symbols' do
instance['abc'] = 'def'
copy = instance.symbolize_keys
copy[:abc].should == 'def'
diff --git a/spec/hashie/mash_spec.rb b/spec/hashie/mash_spec.rb
index f5c559f..95821f5 100644
--- a/spec/hashie/mash_spec.rb
+++ b/spec/hashie/mash_spec.rb
@@ -71,6 +71,15 @@ describe Hashie::Mash do
@mash.name!.should == "Bob"
end
+ it "should return a Hashie::Mash when passed an under bang method to a non-existenct key" do
+ @mash.abc_.is_a?(Hashie::Mash).should be_true
+ end
+
+ it "should return the existing value when passed an under bang method for an existing key" do
+ @mash.name = "Bob"
+ @mash.name_.should == "Bob"
+ end
+
it "#initializing_reader should return a Hashie::Mash when passed a non-existent key" do
@mash.initializing_reader(:abc).is_a?(Hashie::Mash).should be_true
end
@@ -82,9 +91,33 @@ describe Hashie::Mash do
@mash.author.website.should == Hashie::Mash.new(:url => "http://www.mbleigh.com/")
end
- # it "should call super if type is not a key" do
- # @mash.type.should == Hashie::Mash
- # end
+ it "should allow for multi-level under bang testing" do
+ @mash.author_.website_.url.should be_nil
+ @mash.author_.website_.url?.should == false
+ @mash.author.should be_nil
+ end
+
+ it "should not call super if object_id is not a key" do
+ @mash.object_id.should == nil
+ end
+
+ it "should return the value if object_id is a key" do
+ @mash.object_id = "Steve"
+ @mash.object_id.should == "Steve"
+ end
+
+ it "should not call super if id is not a key" do
+ @mash.id.should == nil
+ end
+
+ it "should return the value if id is a key" do
+ @mash.id = "Steve"
+ @mash.id.should == "Steve"
+ end
+
+ it "should not call super if type is not a key" do
+ @mash.type.should == nil
+ end
it "should return the value if type is a key" do
@mash.type = "Steve"
@@ -204,20 +237,35 @@ describe Hashie::Mash do
son.non_existent!.should be_kind_of(SubMash)
end
+ it "should respect the class when passed an under bang method for a non-existent key" do
+ record = Hashie::Mash.new
+ record.non_existent_.should be_kind_of(Hashie::Mash)
+
+ class SubMash < Hashie::Mash
+ end
+
+ son = SubMash.new
+ son.non_existent_.should be_kind_of(SubMash)
+ end
+
it "should respect the class when converting the value" do
record = Hashie::Mash.new
record.details = Hashie::Mash.new({:email => "randy@asf.com"})
record.details.should be_kind_of(Hashie::Mash)
+ end
+
+ it "should respect another subclass when converting the value" do
+ record = Hashie::Mash.new
class SubMash < Hashie::Mash
end
- son = SubMash.new
- son.details = Hashie::Mash.new({:email => "randyjr@asf.com"})
- son.details.should be_kind_of(SubMash)
+ son = SubMash.new({:email => "foo@bar.com"})
+ record.details = son
+ record.details.should be_kind_of(SubMash)
end
- describe '#respond_to?' do
+ describe "#respond_to?" do
it 'should respond to a normal method' do
Hashie::Mash.new.should be_respond_to(:key?)
end
@@ -251,11 +299,11 @@ describe Hashie::Mash do
initial = Hashie::Mash.new(:name => 'randy', :address => {:state => 'TX'})
copy = Hashie::Mash.new(initial)
initial.name.should == copy.name
- initial.object_id.should_not == copy.object_id
+ initial.__id__.should_not == copy.__id__
copy.address.state.should == 'TX'
copy.address.state = 'MI'
initial.address.state.should == 'TX'
- copy.address.object_id.should_not == initial.address.object_id
+ copy.address.__id__.should_not == initial.address.__id__
end
it "should accept a default block" do
@@ -274,4 +322,42 @@ describe Hashie::Mash do
converted.to_hash["a"].first["c"].first.is_a?(Hashie::Mash).should be_false
end
end
+
+ describe "#fetch" do
+ let(:hash) { {:one => 1} }
+ let(:mash) { Hashie::Mash.new(hash) }
+
+ context "when key exists" do
+ it "returns the value" do
+ mash.fetch(:one).should eql(1)
+ end
+
+ context "when key has other than original but acceptable type" do
+ it "returns the value" do
+ mash.fetch('one').should eql(1)
+ end
+ end
+ end
+
+ context "when key does not exist" do
+ it "should raise 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
+ mash.fetch(:two, 8).should eql(8)
+ end
+ end
+
+ context "with block given" do
+ it "returns default value" do
+ mash.fetch(:two) {|key|
+ "block default value"
+ }.should eql("block default value")
+ end
+ end
+ end
+ end
end
diff --git a/spec/hashie/trash_spec.rb b/spec/hashie/trash_spec.rb
index 4c1e2a7..40d27c2 100644
--- a/spec/hashie/trash_spec.rb
+++ b/spec/hashie/trash_spec.rb
@@ -63,8 +63,83 @@ describe Hashie::Trash do
TrashTest.new(:first_name => 'Michael').first_name.should == 'Michael'
end
+ context "with both the translated property and the property" do
+ it 'sets the desired properties' do
+ TrashTest.new(:first_name => 'Michael', :firstName=>'Maeve').first_name.should == 'Michael'
+ end
+ end
+
it 'sets the translated properties' do
TrashTest.new(:firstName => 'Michael').first_name.should == 'Michael'
end
end
+
+ describe 'translating properties using a proc' do
+ class TrashLambdaTest < Hashie::Trash
+ property :first_name, :from => :firstName, :with => lambda { |value| value.reverse }
+ end
+
+ let(:lambda_trash) { TrashLambdaTest.new }
+
+ it 'should translate the value given on initialization with the given lambda' do
+ TrashLambdaTest.new(:firstName => 'Michael').first_name.should == 'Michael'.reverse
+ end
+
+ it 'should not translate the value if given with the right property' do
+ TrashTest.new(:first_name => 'Michael').first_name.should == 'Michael'
+ end
+
+ it 'should translate the value given as property with the given lambda' do
+ lambda_trash.firstName = 'Michael'
+ lambda_trash.first_name.should == 'Michael'.reverse
+ end
+
+ it 'should not translate the value given as right property' do
+ lambda_trash.first_name = 'Michael'
+ lambda_trash.first_name.should == 'Michael'
+ end
+ end
+
+ describe 'translating properties without from option using a proc' do
+
+ class TrashLambdaTest2 < Hashie::Trash
+ property :first_name, :transform_with => lambda { |value| value.reverse }
+ end
+
+ let(:lambda_trash) { TrashLambdaTest2.new }
+
+ it 'should translate the value given as property with the given lambda' do
+ lambda_trash.first_name = 'Michael'
+ lambda_trash.first_name.should == 'Michael'.reverse
+ end
+
+ it 'should transform the value when given in constructor' do
+ TrashLambdaTest2.new(:first_name => 'Michael').first_name.should == 'Michael'.reverse
+ end
+
+ context "when :from option is given" do
+ class TrashLambdaTest3 < Hashie::Trash
+ property :first_name, :from => :firstName, :transform_with => lambda { |value| value.reverse }
+ end
+
+ it 'should not override the :from option in the constructor' do
+ TrashLambdaTest3.new(:first_name => 'Michael').first_name.should == 'Michael'
+ end
+
+ it 'should not override the :from option when given as property' do
+ t = TrashLambdaTest3.new
+ t.first_name = 'Michael'
+ t.first_name.should == 'Michael'
+ end
+
+ end
+ end
+
+ it "should raise an error when :from have the same value as property" do
+ expect {
+ class WrongTrash < Hashie::Trash
+ property :first_name, :from => :first_name
+ end
+ }.to raise_error(ArgumentError)
+ end
end