hashie 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.1
1
+ 0.4.0
data/hashie.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{hashie}
8
- s.version = "0.3.1"
8
+ s.version = "0.4.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Michael Bleigh"]
12
- s.date = %q{2010-08-19}
12
+ s.date = %q{2010-08-31}
13
13
  s.description = %q{Hashie is a small collection of tools that make hashes more powerful. Currently includes Mash (Mocking Hash) and Dash (Discrete Hash).}
14
14
  s.email = %q{michael@intridea.com}
15
15
  s.extra_rdoc_files = [
data/lib/hashie/dash.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'hashie/hash'
2
+ require 'set'
2
3
 
3
4
  module Hashie
4
- include Hashie::PrettyInspect
5
5
  # A Dash is a 'defined' or 'discrete' Hash, that is, a Hash
6
6
  # that has a set of defined keys that are accessible (with
7
7
  # optional defaults) and only those keys may be set or read.
@@ -26,56 +26,55 @@ module Hashie
26
26
  def self.property(property_name, options = {})
27
27
  property_name = property_name.to_sym
28
28
 
29
- (@properties ||= []) << property_name
30
- (@defaults ||= {})[property_name] = options.delete(:default)
29
+ self.properties << property_name
31
30
 
32
- class_eval <<-RUBY
33
- def #{property_name}
34
- self[:#{property_name}]
35
- end
31
+ if options[:default] or self.defaults[property_name]
32
+ self.defaults[property_name] = options[:default]
33
+ end
36
34
 
37
- def #{property_name}=(val)
38
- self[:#{property_name}] = val
39
- end
40
- RUBY
41
- end
35
+ unless instance_methods.map { |m| m.to_s }.include?("#{property_name}=")
36
+ class_eval <<-ACCESSORS
37
+ def #{property_name}
38
+ _regular_reader(#{property_name.to_s.inspect})
39
+ end
42
40
 
43
- # Get a String array of the currently defined
44
- # properties on this Dash.
45
- def self.properties
46
- properties = []
47
- ancestors.each do |elder|
48
- if elder.instance_variable_defined?("@properties")
49
- properties << elder.instance_variable_get("@properties")
50
- end
41
+ def #{property_name}=(value)
42
+ _regular_writer(#{property_name.to_s.inspect}, value)
43
+ end
44
+ ACCESSORS
51
45
  end
52
46
 
53
- properties.flatten.map{|p| p.to_s}
47
+ if defined? @subclasses
48
+ @subclasses.each { |klass| klass.property(property_name, options) }
49
+ end
54
50
  end
55
51
 
56
- # Check to see if the specified property has already been
57
- # defined.
58
- def self.property?(prop)
59
- properties.include?(prop.to_s)
52
+ class << self
53
+ attr_reader :properties, :defaults
60
54
  end
55
+ instance_variable_set('@properties', Set.new)
56
+ instance_variable_set('@defaults', {})
61
57
 
62
- # The default values that have been set for this Dash
63
- def self.defaults
64
- properties = {}
65
- ancestors.each do |elder|
66
- if elder.instance_variable_defined?("@defaults")
67
- properties.merge! elder.instance_variable_get("@defaults")
68
- end
69
- end
58
+ def self.inherited(klass)
59
+ super
60
+ (@subclasses ||= Set.new) << klass
61
+ klass.instance_variable_set('@properties', self.properties.dup)
62
+ klass.instance_variable_set('@defaults', self.defaults.dup)
63
+ end
70
64
 
71
- properties
65
+ # Check to see if the specified property has already been
66
+ # defined.
67
+ def self.property?(name)
68
+ properties.include? name.to_sym
72
69
  end
73
70
 
74
71
  # You may initialize a Dash with an attributes hash
75
72
  # just like you would many other kinds of data objects.
76
- def initialize(attributes = {})
77
- self.class.properties.each do |prop|
78
- self.send("#{prop}=", self.class.defaults[prop.to_sym])
73
+ def initialize(attributes = {}, &block)
74
+ super(&block)
75
+
76
+ self.class.defaults.each_pair do |prop, value|
77
+ self.send("#{prop}=", value)
79
78
  end
80
79
 
81
80
  attributes.each_pair do |att, value|
@@ -83,26 +82,30 @@ module Hashie
83
82
  end if attributes
84
83
  end
85
84
 
85
+ alias_method :_regular_reader, :[]
86
+ alias_method :_regular_writer, :[]=
87
+ private :_regular_reader, :_regular_writer
88
+
86
89
  # Retrieve a value from the Dash (will return the
87
90
  # property's default value if it hasn't been set).
88
91
  def [](property)
89
- super(property.to_sym) if property_exists? property
92
+ assert_property_exists! property
93
+ super(property.to_s)
90
94
  end
91
95
 
92
96
  # Set a value on the Dash in a Hash-like way. Only works
93
97
  # on pre-existing properties.
94
98
  def []=(property, value)
95
- super if property_exists? property
99
+ assert_property_exists! property
100
+ super(property.to_s, value)
96
101
  end
97
102
 
98
103
  private
99
- # Raises an NoMethodError if the property doesn't exist
100
- #
101
- def property_exists?(property)
102
- unless self.class.property?(property.to_sym)
104
+
105
+ def assert_property_exists!(property)
106
+ unless self.class.property?(property)
103
107
  raise NoMethodError, "The property '#{property}' is not defined for this Dash."
104
108
  end
105
- true
106
109
  end
107
110
  end
108
111
  end
data/lib/hashie/hash.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'hashie/hash_extensions'
2
+
1
3
  module Hashie
2
4
  # A Hashie Hash is simply a Hash that has convenience
3
5
  # functions baked in such as stringify_keys that may
data/lib/hashie/mash.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'hashie/hash'
2
+
1
3
  module Hashie
2
4
  # Mash allows you to create pseudo-objects that have method-like
3
5
  # accessors for hash keys. This is useful for such implementations
@@ -79,7 +81,7 @@ module Hashie
79
81
  # if there isn't a value already assigned to the key requested.
80
82
  def initializing_reader(key)
81
83
  ck = convert_key(key)
82
- regular_writer(ck, Hashie::Mash.new) unless key?(ck)
84
+ regular_writer(ck, self.class.new) unless key?(ck)
83
85
  regular_reader(ck)
84
86
  end
85
87
 
@@ -96,14 +98,20 @@ module Hashie
96
98
  # Performs a deep_update on a duplicate of the
97
99
  # current mash.
98
100
  def deep_merge(other_hash)
99
- dup.deep_merge!(other_hash)
101
+ dup.deep_update(other_hash)
100
102
  end
103
+ alias_method :merge, :deep_merge
101
104
 
102
105
  # Recursively merges this mash with the passed
103
106
  # in hash, merging each hash in the hierarchy.
104
107
  def deep_update(other_hash)
105
108
  other_hash.each_pair do |k,v|
106
- regular_writer(convert_key(k), convert_value(other_hash[k], true))
109
+ key = convert_key(k)
110
+ if regular_reader(key).is_a?(Mash) and v.is_a?(::Hash)
111
+ regular_reader(key).deep_update(v)
112
+ else
113
+ regular_writer(key, convert_value(v, true))
114
+ end
107
115
  end
108
116
  self
109
117
  end
@@ -111,6 +119,20 @@ module Hashie
111
119
  alias_method :update, :deep_update
112
120
  alias_method :merge!, :update
113
121
 
122
+ # Performs a shallow_update on a duplicate of the current mash
123
+ def shallow_merge(other_hash)
124
+ dup.shallow_update(other_hash)
125
+ end
126
+
127
+ # Merges (non-recursively) the hash from the argument,
128
+ # changing the receiving hash
129
+ def shallow_update(other_hash)
130
+ other_hash.each_pair do |k,v|
131
+ regular_writer(convert_key(k), convert_value(v, true))
132
+ end
133
+ self
134
+ end
135
+
114
136
  # Will return true if the Mash has had a key
115
137
  # set in addition to normal respond_to? functionality.
116
138
  def respond_to?(method_name)
@@ -141,7 +163,7 @@ module Hashie
141
163
 
142
164
  def convert_value(val, duping=false) #:nodoc:
143
165
  case val
144
- when ::Hashie::Mash
166
+ when self.class
145
167
  val.dup
146
168
  when ::Hash
147
169
  val = val.dup if duping
@@ -1,5 +1,11 @@
1
1
  require 'spec_helper'
2
2
 
3
+ Hashie::Hash.class_eval do
4
+ def self.inherited(klass)
5
+ klass.instance_variable_set('@inheritance_test', true)
6
+ end
7
+ end
8
+
3
9
  class DashTest < Hashie::Dash
4
10
  property :first_name
5
11
  property :email
@@ -10,100 +16,141 @@ class Subclassed < DashTest
10
16
  property :last_name
11
17
  end
12
18
 
13
- describe Hashie::Dash do
14
- it 'should be a subclass of Hashie::Hash' do
15
- (Hashie::Dash < Hash).should be_true
19
+ describe DashTest do
20
+
21
+ it('subclasses Hashie::Hash') { should respond_to(:to_mash) }
22
+
23
+ its(:to_s) { should == '<#DashTest count=0>' }
24
+
25
+ it 'lists all set properties in inspect' do
26
+ subject.first_name = 'Bob'
27
+ subject.email = 'bob@example.com'
28
+ subject.inspect.should == '<#DashTest count=0 email="bob@example.com" first_name="Bob">'
16
29
  end
17
-
18
- it '#inspect should be ok!' do
19
- dash = DashTest.new
20
- dash.email = "abd@abc.com"
21
- dash.inspect.should == "<#DashTest count=0 email=\"abd@abc.com\" first_name=nil>"
30
+
31
+ its(:count) { should be_zero }
32
+
33
+ it { should respond_to(:first_name) }
34
+ it { should respond_to(:first_name=) }
35
+ it { should_not respond_to(:nonexistent) }
36
+
37
+ it 'errors out for a non-existent property' do
38
+ lambda { subject['nonexistent'] }.should raise_error(NoMethodError)
22
39
  end
23
40
 
24
- describe ' creating properties' do
25
- it 'should add the property to the list' do
26
- DashTest.property :not_an_att
27
- DashTest.properties.include?('not_an_att').should be_true
41
+ context 'writing to properties' do
42
+ it 'fails writing to a non-existent property using []=' do
43
+ lambda { subject['nonexistent'] = 123 }.should raise_error(NoMethodError)
28
44
  end
29
45
 
30
- it 'should create a method for reading the property' do
31
- DashTest.new.respond_to?(:first_name).should be_true
46
+ it 'works for an existing property using []=' do
47
+ subject['first_name'] = 'Bob'
48
+ subject['first_name'].should == 'Bob'
49
+ subject[:first_name].should == 'Bob'
32
50
  end
33
51
 
34
- it 'should create a method for writing the property' do
35
- DashTest.new.respond_to?(:first_name=).should be_true
52
+ it 'works for an existing property using a method call' do
53
+ subject.first_name = 'Franklin'
54
+ subject.first_name.should == 'Franklin'
36
55
  end
37
56
  end
38
57
 
39
- describe 'reading properties' do
40
- it 'should raise an error when reading a non-existent property' do
41
- lambda{@dash['abc']}.should raise_error(NoMethodError)
42
- end
43
- end
44
-
45
- describe ' writing to properties' do
46
- before do
47
- @dash = DashTest.new
58
+ describe '.new' do
59
+ it 'fails with non-existent properties' do
60
+ lambda { described_class.new(:bork => '') }.should raise_error(NoMethodError)
48
61
  end
49
62
 
50
- it 'should not be able to write to a non-existent property using []=' do
51
- lambda{@dash['abc'] = 123}.should raise_error(NoMethodError)
63
+ it 'should set properties that it is able to' do
64
+ obj = described_class.new :first_name => 'Michael'
65
+ obj.first_name.should == 'Michael'
52
66
  end
53
-
54
- it 'should be able to write to an existing property using []=' do
55
- lambda{@dash['first_name'] = 'Bob'}.should_not raise_error
67
+
68
+ it 'accepts nil' do
69
+ lambda { described_class.new(nil) }.should_not raise_error
56
70
  end
57
-
58
- it 'should be able to read/write to an existing property using a method call' do
59
- @dash.first_name = 'Franklin'
60
- @dash.first_name.should == 'Franklin'
71
+
72
+ it 'accepts block to define a global default' do
73
+ obj = described_class.new { |hash, key| key.to_s.upcase }
74
+ obj.first_name.should == 'FIRST_NAME'
75
+ obj.count.should be_zero
61
76
  end
62
77
  end
63
-
64
- describe ' initializing with a Hash' do
65
- it 'should not be able to initialize non-existent properties' do
66
- lambda{DashTest.new(:bork => 'abc')}.should raise_error(NoMethodError)
67
- end
68
-
69
- it 'should set properties that it is able to' do
70
- DashTest.new(:first_name => 'Michael').first_name.should == 'Michael'
78
+
79
+ describe 'properties' do
80
+ it 'lists defined properties' do
81
+ described_class.properties.should == Set.new([:first_name, :email, :count])
71
82
  end
72
- end
73
-
74
- describe 'initializing with a nil' do
75
- it 'accepts nil' do
76
- lambda { DashTest.new(nil) }.should_not raise_error
83
+
84
+ it 'checks if a property exists' do
85
+ described_class.property?('first_name').should be_true
86
+ described_class.property?(:first_name).should be_true
77
87
  end
78
- end
79
-
80
- describe ' defaults' do
81
- before do
82
- @dash = DashTest.new
88
+
89
+ it 'doesnt include property from subclass' do
90
+ described_class.property?(:last_name).should be_false
83
91
  end
84
-
85
- it 'should return the default value for defaulted' do
86
- DashTest.property :defaulted, :default => 'abc'
87
- DashTest.new.defaulted.should == 'abc'
92
+
93
+ it 'lists declared defaults' do
94
+ described_class.defaults.should == { :count => 0 }
88
95
  end
89
96
  end
90
97
  end
91
98
 
92
- describe Subclassed do
93
- it "should inherit all properties from DashTest" do
94
- Subclassed.properties.size.should == 6
99
+ describe Hashie::Dash, 'inheritance' do
100
+ before do
101
+ @top = Class.new(Hashie::Dash)
102
+ @middle = Class.new(@top)
103
+ @bottom = Class.new(@middle)
95
104
  end
96
-
97
- it "should inherit all defaults from DashTest" do
98
- Subclassed.defaults.size.should == 6
105
+
106
+ it 'reports empty properties when nothing defined' do
107
+ @top.properties.should be_empty
108
+ @top.defaults.should be_empty
99
109
  end
100
-
101
- it "should init without raising" do
102
- lambda { Subclassed.new }.should_not raise_error
103
- lambda { Subclassed.new(:first_name => 'Michael') }.should_not raise_error
110
+
111
+ it 'inherits properties downwards' do
112
+ @top.property :echo
113
+ @middle.properties.should include(:echo)
114
+ @bottom.properties.should include(:echo)
115
+ end
116
+
117
+ it 'doesnt inherit properties upwards' do
118
+ @middle.property :echo
119
+ @top.properties.should_not include(:echo)
120
+ @bottom.properties.should include(:echo)
121
+ end
122
+
123
+ it 'allows overriding a default on an existing property' do
124
+ @top.property :echo
125
+ @middle.property :echo, :default => 123
126
+ @bottom.properties.to_a.should == [:echo]
127
+ @bottom.new.echo.should == 123
128
+ end
129
+
130
+ it 'allows clearing an existing default' do
131
+ @top.property :echo
132
+ @middle.property :echo, :default => 123
133
+ @bottom.property :echo
134
+ @bottom.properties.to_a.should == [:echo]
135
+ @bottom.new.echo.should be_nil
104
136
  end
137
+ end
105
138
 
106
- it "should share defaults from DashTest" do
107
- Subclassed.new.count.should == 0
139
+ describe Subclassed do
140
+
141
+ its(:count) { should be_zero }
142
+
143
+ it { should respond_to(:first_name) }
144
+ it { should respond_to(:first_name=) }
145
+ it { should respond_to(:last_name) }
146
+ it { should respond_to(:last_name=) }
147
+
148
+ it 'has one additional property' do
149
+ described_class.property?(:last_name).should be_true
150
+ end
151
+
152
+ it "didn't override superclass inheritance logic" do
153
+ described_class.instance_variable_get('@inheritance_test').should be_true
108
154
  end
155
+
109
156
  end
@@ -67,12 +67,67 @@ describe Hashie::Mash do
67
67
  @mash.author.website.should == Hashie::Mash.new(:url => "http://www.mbleigh.com/")
68
68
  end
69
69
 
70
- it "#deep_update should recursively Hashie::Mash Hashie::Mashes and hashes together" do
71
- @mash.first_name = "Michael"
72
- @mash.last_name = "Bleigh"
73
- @mash.details = Hashie::Hash[:email => "michael@asf.com"].to_mash
74
- @mash.deep_update({:details => {:email => "michael@intridea.com"}})
75
- @mash.details.email.should == "michael@intridea.com"
70
+ context "updating" do
71
+ subject {
72
+ described_class.new :first_name => "Michael", :last_name => "Bleigh",
73
+ :details => {:email => "michael@asf.com", :address => "Nowhere road"}
74
+ }
75
+
76
+ describe "#deep_update" do
77
+ it "should recursively Hashie::Mash Hashie::Mashes and hashes together" do
78
+ subject.deep_update(:details => {:email => "michael@intridea.com", :city => "Imagineton"})
79
+ subject.first_name.should == "Michael"
80
+ subject.details.email.should == "michael@intridea.com"
81
+ subject.details.address.should == "Nowhere road"
82
+ subject.details.city.should == "Imagineton"
83
+ end
84
+
85
+ it "should make #update deep by default" do
86
+ subject.update(:details => {:address => "Fake street"}).should eql(subject)
87
+ subject.details.address.should == "Fake street"
88
+ subject.details.email.should == "michael@asf.com"
89
+ end
90
+
91
+ it "should clone before a #deep_merge" do
92
+ duped = subject.deep_merge(:details => {:address => "Fake street"})
93
+ duped.should_not eql(subject)
94
+ duped.details.address.should == "Fake street"
95
+ subject.details.address.should == "Nowhere road"
96
+ duped.details.email.should == "michael@asf.com"
97
+ end
98
+
99
+ it "regular #merge should be deep" do
100
+ duped = subject.merge(:details => {:email => "michael@intridea.com"})
101
+ duped.should_not eql(subject)
102
+ duped.details.email.should == "michael@intridea.com"
103
+ duped.details.address.should == "Nowhere road"
104
+ end
105
+ end
106
+
107
+ describe "shallow update" do
108
+ it "should shallowly Hashie::Mash Hashie::Mashes and hashes together" do
109
+ subject.shallow_update(:details => {
110
+ :email => "michael@intridea.com", :city => "Imagineton"
111
+ }).should eql(subject)
112
+
113
+ subject.first_name.should == "Michael"
114
+ subject.details.email.should == "michael@intridea.com"
115
+ subject.details.address.should be_nil
116
+ subject.details.city.should == "Imagineton"
117
+ end
118
+
119
+ it "should clone before a #regular_merge" do
120
+ duped = subject.shallow_merge(:details => {:address => "Fake street"})
121
+ duped.should_not eql(subject)
122
+ end
123
+
124
+ it "regular merge should be shallow" do
125
+ duped = subject.shallow_merge(:details => {:address => "Fake street"})
126
+ duped.details.address.should == "Fake street"
127
+ subject.details.address.should == "Nowhere road"
128
+ duped.details.email.should be_nil
129
+ end
130
+ end
76
131
  end
77
132
 
78
133
  it "should convert hash assignments into Hashie::Mashes" do
@@ -100,6 +155,30 @@ describe Hashie::Mash do
100
155
  record['submash'].should be_kind_of(SubMash)
101
156
  end
102
157
 
158
+ it "should respect the class when passed a bang method for a non-existent key" do
159
+ record = Hashie::Mash.new
160
+ record.non_existent!.should be_kind_of(Hashie::Mash)
161
+
162
+ class SubMash < Hashie::Mash
163
+ end
164
+
165
+ son = SubMash.new
166
+ son.non_existent!.should be_kind_of(SubMash)
167
+ end
168
+
169
+ it "should respect the class when converting the value" do
170
+ record = Hashie::Mash.new
171
+ record.details = Hashie::Mash.new({:email => "randy@asf.com"})
172
+ record.details.should be_kind_of(Hashie::Mash)
173
+
174
+ class SubMash < Hashie::Mash
175
+ end
176
+
177
+ son = SubMash.new
178
+ son.details = Hashie::Mash.new({:email => "randyjr@asf.com"})
179
+ son.details.should be_kind_of(SubMash)
180
+ end
181
+
103
182
  describe '#respond_to?' do
104
183
  it 'should respond to a normal method' do
105
184
  Hashie::Mash.new.should be_respond_to(:key?)
@@ -9,8 +9,7 @@ describe Hashie::Trash do
9
9
 
10
10
  describe 'translating properties' do
11
11
  it 'adds the property to the list' do
12
- TrashTest.property :not_an_att, :from => :notAnAtt
13
- TrashTest.properties.should include('not_an_att')
12
+ TrashTest.properties.should include(:first_name)
14
13
  end
15
14
 
16
15
  it 'creates a method for reading the property' do
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hashie
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 3
9
- - 1
10
- version: 0.3.1
8
+ - 4
9
+ - 0
10
+ version: 0.4.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Michael Bleigh
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-19 00:00:00 -05:00
18
+ date: 2010-08-31 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency