Adds first extension, coercion, road to 2.0 begins.

This commit is contained in:
Michael Bleigh 2011-07-28 23:44:50 -05:00
parent 1a27b990cf
commit f7b538fe28
6 changed files with 185 additions and 7 deletions

View File

@ -7,6 +7,7 @@ GEM
remote: http://rubygems.org/
specs:
diff-lcs (1.1.2)
growl (1.0.3)
guard (0.5.1)
thor (~> 0.14.6)
guard-rspec (0.4.0)
@ -27,6 +28,7 @@ PLATFORMS
ruby
DEPENDENCIES
growl
guard
guard-rspec
hashie!

View File

@ -18,4 +18,5 @@ Gem::Specification.new do |gem|
gem.add_development_dependency 'rspec', '~> 2.5'
gem.add_development_dependency 'guard'
gem.add_development_dependency 'guard-rspec'
gem.add_development_dependency 'growl'
end

View File

@ -1,9 +1,13 @@
module Hashie
autoload :Clash, 'hashie/clash'
autoload :Dash, 'hashie/dash'
autoload :Hash, 'hashie/hash'
autoload :HashExtensions, 'hashie/hash_extensions'
autoload :PrettyInspect, 'hashie/hash_extensions'
autoload :Hash, 'hashie/hash'
autoload :Trash, 'hashie/trash'
autoload :Mash, 'hashie/mash'
autoload :Dash, 'hashie/dash'
autoload :Clash, 'hashie/clash'
autoload :Mash, 'hashie/mash'
autoload :PrettyInspect, 'hashie/hash_extensions'
autoload :Trash, 'hashie/trash'
module Extensions
autoload :Coercion, 'hashie/extensions/coercion'
end
end

View File

@ -0,0 +1,101 @@
module Hashie
module Extensions
module Coercion
def self.included(base)
base.send :extend, ClassMethods
base.send :include, InstanceMethods
end
module InstanceMethods
def []=(key, value)
into = self.class.key_coercion(key) || self.class.value_coercion(value)
if value && into
if into.respond_to?(:coerce)
value = into.coerce(value)
else
value = into.new(value)
end
end
super(key, value)
end
end
module ClassMethods
# 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 you would like to be coerced.
# @param [Class] into the class into which you want the key 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
end
# 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]
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 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; @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
end
end
end
end

View File

@ -1,3 +1,3 @@
module Hashie
VERSION = '1.1.0'
VERSION = '2.0.0.beta'
end

View File

@ -0,0 +1,70 @@
require 'spec_helper'
describe Hashie::Extensions::Coercion do
class Initializable
def initialize(obj, coerced = false)
@coerced = coerced
@value = obj.class.to_s
end
def coerced?; @coerced end
attr_reader :value
end
class Coercable < Initializable
def self.coerce(obj)
new(obj, true)
end
end
before(:each) do
class ExampleCoercableHash < Hash; include Hashie::Extensions::Coercion end
end
subject { ExampleCoercableHash }
let(:instance){ subject.new }
describe '.coerce_key' do
it { subject.should be_respond_to(:coerce_key) }
it 'should run through coerce on a specified key' do
subject.coerce_key :foo, Coercable
instance[:foo] = "bar"
instance[:foo].should be_coerced
end
it 'should just call #new if no coerce method is available' do
subject.coerce_key :foo, Initializable
instance[:foo] = "bar"
instance[:foo].value.should == "String"
instance[:foo].should_not be_coerced
end
end
describe '.coerce_value' do
context 'with :strict => true' do
it 'should coerce any value of the exact right class' do
subject.coerce_value String, Coercable
instance[:foo] = "bar"
instance[:bar] = "bax"
instance[:foo].should be_coerced
instance[:bar].should be_coerced
end
it 'should not coerce superclasses' do
klass = Class.new(String)
subject.coerce_value klass, Coercable
instance[:foo] = "bar"
instance[:foo].should_not be_kind_of(Coercable)
instance[:foo] = klass.new
instance[:foo].should be_kind_of(Coercable)
end
end
end
after(:each) do
Object.send(:remove_const, :ExampleCoercableHash)
end
end