diff options
-rw-r--r-- | Gemfile.lock | 1 | ||||
-rw-r--r-- | README.markdown | 23 | ||||
-rw-r--r-- | hashie.gemspec | 1 | ||||
-rw-r--r-- | lib/hashie/dash.rb | 14 | ||||
-rw-r--r-- | lib/hashie/extensions/coercion.rb | 12 | ||||
-rw-r--r-- | lib/hashie/extensions/indifferent_access.rb | 8 | ||||
-rw-r--r-- | lib/hashie/extensions/key_conversion.rb | 42 | ||||
-rw-r--r-- | lib/hashie/extensions/merge_initializer.rb | 4 | ||||
-rw-r--r-- | lib/hashie/extensions/method_access.rb | 6 | ||||
-rw-r--r-- | lib/hashie/hash.rb | 6 | ||||
-rw-r--r-- | lib/hashie/mash.rb | 43 | ||||
-rw-r--r-- | lib/hashie/trash.rb | 46 | ||||
-rw-r--r-- | spec/hashie/extensions/coercion_spec.rb | 21 | ||||
-rw-r--r-- | spec/hashie/extensions/indifferent_access_spec.rb | 10 | ||||
-rw-r--r-- | spec/hashie/extensions/key_conversion_spec.rb | 42 | ||||
-rw-r--r-- | spec/hashie/mash_spec.rb | 104 | ||||
-rw-r--r-- | spec/hashie/trash_spec.rb | 75 |
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 |