hashie 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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