wahashie 1.2.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.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.gitignore +8 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/.yardopts +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +37 -0
- data/Guardfile +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +120 -0
- data/Rakefile +13 -0
- data/lib/wahashie.rb +9 -0
- data/lib/wahashie/clash.rb +86 -0
- data/lib/wahashie/dash.rb +150 -0
- data/lib/wahashie/hash.rb +25 -0
- data/lib/wahashie/hash_extensions.rb +49 -0
- data/lib/wahashie/mash.rb +197 -0
- data/lib/wahashie/trash.rb +55 -0
- data/lib/wahashie/version.rb +3 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/wahashie/clash_spec.rb +42 -0
- data/spec/wahashie/dash_spec.rb +215 -0
- data/spec/wahashie/hash_spec.rb +32 -0
- data/spec/wahashie/mash_spec.rb +281 -0
- data/spec/wahashie/trash_spec.rb +70 -0
- data/wahashie.gemspec +21 -0
- metadata +134 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'wahashie/hash_extensions'
|
2
|
+
|
3
|
+
module Wahashie
|
4
|
+
# A Wahashie Hash is simply a Hash that has convenience
|
5
|
+
# functions baked in such as stringify_keys that may
|
6
|
+
# not be available in all libraries.
|
7
|
+
class Hash < Hash
|
8
|
+
include Wahashie::HashExtensions
|
9
|
+
|
10
|
+
# Converts a mash back to a hash.
|
11
|
+
def to_hash(options = {})
|
12
|
+
out = {}
|
13
|
+
keys.each do |k|
|
14
|
+
key = options[:symbolize_keys] ? k.to_sym : k.to_s
|
15
|
+
out[key] = Wahashie::Hash === self[k] ? self[k].to_hash : self[k]
|
16
|
+
end
|
17
|
+
out
|
18
|
+
end
|
19
|
+
|
20
|
+
# The C geneartor for the json gem doesn't like mashies
|
21
|
+
def to_json(*args)
|
22
|
+
to_hash.to_json(*args)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Wahashie
|
2
|
+
module HashExtensions
|
3
|
+
def self.included(base)
|
4
|
+
# Don't tread on existing extensions of Hash by
|
5
|
+
# adding methods that are likely to exist.
|
6
|
+
%w(stringify_keys stringify_keys!).each do |wahashie_method|
|
7
|
+
base.send :alias_method, wahashie_method, "wahashie_#{wahashie_method}" unless base.instance_methods.include?(wahashie_method)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Destructively convert all of the keys of a Hash
|
12
|
+
# to their string representations.
|
13
|
+
def wahashie_stringify_keys!
|
14
|
+
self.keys.each do |k|
|
15
|
+
unless String === k
|
16
|
+
self[k.to_s] = self.delete(k)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
# Convert all of the keys of a Hash
|
23
|
+
# to their string representations.
|
24
|
+
def wahashie_stringify_keys
|
25
|
+
self.dup.stringify_keys!
|
26
|
+
end
|
27
|
+
|
28
|
+
# Convert this hash into a Mash
|
29
|
+
def to_mash
|
30
|
+
::Wahashie::Mash.new(self)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module PrettyInspect
|
35
|
+
def self.included(base)
|
36
|
+
base.send :alias_method, :hash_inspect, :inspect
|
37
|
+
base.send :alias_method, :inspect, :wahashie_inspect
|
38
|
+
end
|
39
|
+
|
40
|
+
def wahashie_inspect
|
41
|
+
ret = "#<#{self.class.to_s}"
|
42
|
+
stringify_keys.keys.sort.each do |key|
|
43
|
+
ret << " #{key}=#{self[key].inspect}"
|
44
|
+
end
|
45
|
+
ret << ">"
|
46
|
+
ret
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'wahashie/hash'
|
2
|
+
|
3
|
+
module Wahashie
|
4
|
+
# Mash allows you to create pseudo-objects that have method-like
|
5
|
+
# accessors for hash keys. This is useful for such implementations
|
6
|
+
# as an API-accessing library that wants to fake robust objects
|
7
|
+
# without the overhead of actually doing so. Think of it as OpenStruct
|
8
|
+
# with some additional goodies.
|
9
|
+
#
|
10
|
+
# A Mash will look at the methods you pass it and perform operations
|
11
|
+
# based on the following rules:
|
12
|
+
#
|
13
|
+
# * No punctuation: Returns the value of the hash for that key, or nil if none exists.
|
14
|
+
# * Assignment (<tt>=</tt>): Sets the attribute of the given method name.
|
15
|
+
# * Existence (<tt>?</tt>): Returns true or false depending on whether that key has been set.
|
16
|
+
# * Bang (<tt>!</tt>): Forces the existence of this key, used for deep Mashes. Think of it as "touch" for mashes.
|
17
|
+
#
|
18
|
+
# == Basic Example
|
19
|
+
#
|
20
|
+
# mash = Mash.new
|
21
|
+
# mash.name? # => false
|
22
|
+
# mash.name = "Bob"
|
23
|
+
# mash.name # => "Bob"
|
24
|
+
# mash.name? # => true
|
25
|
+
#
|
26
|
+
# == Hash Conversion Example
|
27
|
+
#
|
28
|
+
# hash = {:a => {:b => 23, :d => {:e => "abc"}}, :f => [{:g => 44, :h => 29}, 12]}
|
29
|
+
# mash = Mash.new(hash)
|
30
|
+
# mash.a.b # => 23
|
31
|
+
# mash.a.d.e # => "abc"
|
32
|
+
# mash.f.first.g # => 44
|
33
|
+
# mash.f.last # => 12
|
34
|
+
#
|
35
|
+
# == Bang Example
|
36
|
+
#
|
37
|
+
# mash = Mash.new
|
38
|
+
# mash.author # => nil
|
39
|
+
# mash.author! # => <Mash>
|
40
|
+
#
|
41
|
+
# mash = Mash.new
|
42
|
+
# mash.author!.name = "Michael Bleigh"
|
43
|
+
# mash.author # => <Mash name="Michael Bleigh">
|
44
|
+
#
|
45
|
+
class Mash < Wahashie::Hash
|
46
|
+
include Wahashie::PrettyInspect
|
47
|
+
alias_method :to_s, :inspect
|
48
|
+
|
49
|
+
# If you pass in an existing hash, it will
|
50
|
+
# convert it to a Mash including recursively
|
51
|
+
# descending into arrays and hashes, converting
|
52
|
+
# them as well.
|
53
|
+
def initialize(source_hash = nil, default = nil, &blk)
|
54
|
+
deep_update(source_hash) if source_hash
|
55
|
+
default ? super(default) : super(&blk)
|
56
|
+
end
|
57
|
+
|
58
|
+
class << self
|
59
|
+
alias [] new
|
60
|
+
|
61
|
+
def subkey_class
|
62
|
+
self
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def id #:nodoc:
|
67
|
+
key?("id") ? self["id"] : super
|
68
|
+
end
|
69
|
+
|
70
|
+
def type #:nodoc:
|
71
|
+
key?("type") ? self["type"] : super
|
72
|
+
end
|
73
|
+
|
74
|
+
alias_method :regular_reader, :[]
|
75
|
+
alias_method :regular_writer, :[]=
|
76
|
+
|
77
|
+
# Retrieves an attribute set in the Mash. Will convert
|
78
|
+
# any key passed in to a string before retrieving.
|
79
|
+
def [](key)
|
80
|
+
value = regular_reader(convert_key(key))
|
81
|
+
yield value if block_given?
|
82
|
+
value
|
83
|
+
end
|
84
|
+
|
85
|
+
# Sets an attribute in the Mash. Key will be converted to
|
86
|
+
# a string before it is set, and Hashes will be converted
|
87
|
+
# into Mashes for nesting purposes.
|
88
|
+
def []=(key,value) #:nodoc:
|
89
|
+
regular_writer(convert_key(key), convert_value(value))
|
90
|
+
end
|
91
|
+
|
92
|
+
# This is the bang method reader, it will return a new Mash
|
93
|
+
# if there isn't a value already assigned to the key requested.
|
94
|
+
def initializing_reader(key)
|
95
|
+
ck = convert_key(key)
|
96
|
+
regular_writer(ck, self.class.new) unless key?(ck)
|
97
|
+
regular_reader(ck)
|
98
|
+
end
|
99
|
+
|
100
|
+
def delete(key)
|
101
|
+
super(convert_key(key))
|
102
|
+
end
|
103
|
+
|
104
|
+
alias_method :regular_dup, :dup
|
105
|
+
# Duplicates the current mash as a new mash.
|
106
|
+
def dup
|
107
|
+
self.class.new(self, self.default)
|
108
|
+
end
|
109
|
+
|
110
|
+
def key?(key)
|
111
|
+
super(convert_key(key))
|
112
|
+
end
|
113
|
+
alias_method :has_key?, :key?
|
114
|
+
alias_method :include?, :key?
|
115
|
+
alias_method :member?, :key?
|
116
|
+
|
117
|
+
# Performs a deep_update on a duplicate of the
|
118
|
+
# current mash.
|
119
|
+
def deep_merge(other_hash)
|
120
|
+
dup.deep_update(other_hash)
|
121
|
+
end
|
122
|
+
alias_method :merge, :deep_merge
|
123
|
+
|
124
|
+
# Recursively merges this mash with the passed
|
125
|
+
# in hash, merging each hash in the hierarchy.
|
126
|
+
def deep_update(other_hash)
|
127
|
+
other_hash.each_pair do |k,v|
|
128
|
+
key = convert_key(k)
|
129
|
+
if regular_reader(key).is_a?(Mash) and v.is_a?(::Hash)
|
130
|
+
regular_reader(key).deep_update(v)
|
131
|
+
else
|
132
|
+
regular_writer(key, convert_value(v, true))
|
133
|
+
end
|
134
|
+
end
|
135
|
+
self
|
136
|
+
end
|
137
|
+
alias_method :deep_merge!, :deep_update
|
138
|
+
alias_method :update, :deep_update
|
139
|
+
alias_method :merge!, :update
|
140
|
+
|
141
|
+
# Performs a shallow_update on a duplicate of the current mash
|
142
|
+
def shallow_merge(other_hash)
|
143
|
+
dup.shallow_update(other_hash)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Merges (non-recursively) the hash from the argument,
|
147
|
+
# changing the receiving hash
|
148
|
+
def shallow_update(other_hash)
|
149
|
+
other_hash.each_pair do |k,v|
|
150
|
+
regular_writer(convert_key(k), convert_value(v, true))
|
151
|
+
end
|
152
|
+
self
|
153
|
+
end
|
154
|
+
|
155
|
+
# Will return true if the Mash has had a key
|
156
|
+
# set in addition to normal respond_to? functionality.
|
157
|
+
def respond_to?(method_name, include_private=false)
|
158
|
+
return true if key?(method_name)
|
159
|
+
super
|
160
|
+
end
|
161
|
+
|
162
|
+
def method_missing(method_name, *args, &blk)
|
163
|
+
return self.[](method_name, &blk) if key?(method_name)
|
164
|
+
match = method_name.to_s.match(/(.*?)([?=!]?)$/)
|
165
|
+
case match[2]
|
166
|
+
when "="
|
167
|
+
self[match[1]] = args.first
|
168
|
+
when "?"
|
169
|
+
!!self[match[1]]
|
170
|
+
when "!"
|
171
|
+
initializing_reader(match[1])
|
172
|
+
else
|
173
|
+
default(method_name, *args, &blk)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
protected
|
178
|
+
|
179
|
+
def convert_key(key) #:nodoc:
|
180
|
+
key.to_s
|
181
|
+
end
|
182
|
+
|
183
|
+
def convert_value(val, duping=false) #:nodoc:
|
184
|
+
case val
|
185
|
+
when self.class
|
186
|
+
val.dup
|
187
|
+
when ::Hash
|
188
|
+
val = val.dup if duping
|
189
|
+
self.class.subkey_class.new.merge(val)
|
190
|
+
when Array
|
191
|
+
val.collect{ |e| convert_value(e) }
|
192
|
+
else
|
193
|
+
val
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'wahashie/dash'
|
2
|
+
|
3
|
+
module Wahashie
|
4
|
+
# A Trash is a 'translated' Dash where the keys can be remapped from a source
|
5
|
+
# hash.
|
6
|
+
#
|
7
|
+
# Trashes are useful when you need to read data from another application,
|
8
|
+
# such as a Java api, where the keys are named differently from how we would
|
9
|
+
# in Ruby.
|
10
|
+
class Trash < Wahashie::Dash
|
11
|
+
|
12
|
+
# Defines a property on the Trash. Options are as follows:
|
13
|
+
#
|
14
|
+
# * <tt>:default</tt> - Specify a default value for this property, to be
|
15
|
+
# returned before a value is set on the property in a new Dash.
|
16
|
+
# * <tt>:from</tt> - Specify the original key name that will be write only.
|
17
|
+
def self.property(property_name, options = {})
|
18
|
+
super
|
19
|
+
|
20
|
+
if options[:from]
|
21
|
+
translations << options[:from].to_sym
|
22
|
+
class_eval <<-RUBY
|
23
|
+
def #{options[:from]}=(val)
|
24
|
+
self[:#{property_name}] = val
|
25
|
+
end
|
26
|
+
RUBY
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Set a value on the Dash in a Hash-like way. Only works
|
31
|
+
# on pre-existing properties.
|
32
|
+
def []=(property, value)
|
33
|
+
if self.class.translations.include? property.to_sym
|
34
|
+
send("#{property}=", value)
|
35
|
+
elsif property_exists? property
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def self.translations
|
43
|
+
@translations ||= []
|
44
|
+
end
|
45
|
+
|
46
|
+
# Raises an NoMethodError if the property doesn't exist
|
47
|
+
#
|
48
|
+
def property_exists?(property)
|
49
|
+
unless self.class.property?(property.to_sym)
|
50
|
+
raise NoMethodError, "The property '#{property}' is not defined for this Trash."
|
51
|
+
end
|
52
|
+
true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Wahashie::Clash do
|
4
|
+
before do
|
5
|
+
@c = Wahashie::Clash.new
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'should be able to set an attribute via method_missing' do
|
9
|
+
@c.foo('bar')
|
10
|
+
@c[:foo].should == 'bar'
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should be able to set multiple attributes' do
|
14
|
+
@c.foo('bar').baz('wok')
|
15
|
+
@c.should == {:foo => 'bar', :baz => 'wok'}
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should convert multiple arguments into an array' do
|
19
|
+
@c.foo(1, 2, 3)
|
20
|
+
@c[:foo].should == [1,2,3]
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should be able to use bang notation to create a new Clash on a key' do
|
24
|
+
@c.foo!
|
25
|
+
@c[:foo].should be_kind_of(Wahashie::Clash)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should be able to chain onto the new Clash when using bang notation' do
|
29
|
+
@c.foo!.bar('abc').baz(123)
|
30
|
+
@c.should == {:foo => {:bar => 'abc', :baz => 123}}
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should be able to jump back up to the parent in the chain with #_end!' do
|
34
|
+
@c.foo!.bar('abc')._end!.baz(123)
|
35
|
+
@c.should == {:foo => {:bar => 'abc'}, :baz => 123}
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should merge rather than replace existing keys' do
|
39
|
+
@c.where(:abc => 'def').where(:hgi => 123)
|
40
|
+
@c.should == {:where => {:abc => 'def', :hgi => 123}}
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
Wahashie::Hash.class_eval do
|
4
|
+
def self.inherited(klass)
|
5
|
+
klass.instance_variable_set('@inheritance_test', true)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class DashTest < Wahashie::Dash
|
10
|
+
property :first_name, :required => true
|
11
|
+
property :email
|
12
|
+
property :count, :default => 0
|
13
|
+
end
|
14
|
+
|
15
|
+
class DashNoRequiredTest < Wahashie::Dash
|
16
|
+
property :first_name
|
17
|
+
property :email
|
18
|
+
property :count, :default => 0
|
19
|
+
end
|
20
|
+
|
21
|
+
class Subclassed < DashTest
|
22
|
+
property :last_name, :required => true
|
23
|
+
end
|
24
|
+
|
25
|
+
describe DashTest do
|
26
|
+
|
27
|
+
subject { DashTest.new(:first_name => 'Bob', :email => 'bob@example.com') }
|
28
|
+
|
29
|
+
it('subclasses Wahashie::Hash') { should respond_to(:to_mash) }
|
30
|
+
|
31
|
+
its(:to_s) { should == '#<DashTest count=0 email="bob@example.com" first_name="Bob">' }
|
32
|
+
|
33
|
+
it 'lists all set properties in inspect' do
|
34
|
+
subject.first_name = 'Bob'
|
35
|
+
subject.email = 'bob@example.com'
|
36
|
+
subject.inspect.should == '#<DashTest count=0 email="bob@example.com" first_name="Bob">'
|
37
|
+
end
|
38
|
+
|
39
|
+
its(:count) { should be_zero }
|
40
|
+
|
41
|
+
it { should respond_to(:first_name) }
|
42
|
+
it { should respond_to(:first_name=) }
|
43
|
+
it { should_not respond_to(:nonexistent) }
|
44
|
+
|
45
|
+
it 'errors out for a non-existent property' do
|
46
|
+
lambda { subject['nonexistent'] }.should raise_error(NoMethodError)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'errors out when attempting to set a required property to nil' do
|
50
|
+
lambda { subject.first_name = nil }.should raise_error(ArgumentError)
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'writing to properties' do
|
54
|
+
|
55
|
+
it 'fails writing a required property to nil' do
|
56
|
+
lambda { subject.first_name = nil }.should raise_error(ArgumentError)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'fails writing a required property to nil using []=' do
|
60
|
+
lambda { subject['first_name'] = nil }.should raise_error(ArgumentError)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'fails writing to a non-existent property using []=' do
|
64
|
+
lambda { subject['nonexistent'] = 123 }.should raise_error(NoMethodError)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'works for an existing property using []=' do
|
68
|
+
subject['first_name'] = 'Bob'
|
69
|
+
subject['first_name'].should == 'Bob'
|
70
|
+
subject[:first_name].should == 'Bob'
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'works for an existing property using a method call' do
|
74
|
+
subject.first_name = 'Franklin'
|
75
|
+
subject.first_name.should == 'Franklin'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'reading from properties' do
|
80
|
+
it 'fails reading from a non-existent property using []' do
|
81
|
+
lambda { subject['nonexistent'] }.should raise_error(NoMethodError)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should be able to retrieve properties through blocks" do
|
85
|
+
subject["first_name"] = "Aiden"
|
86
|
+
value = nil
|
87
|
+
subject.[]("first_name") { |v| value = v }
|
88
|
+
value.should == "Aiden"
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should be able to retrieve properties through blocks with method calls" do
|
92
|
+
subject["first_name"] = "Frodo"
|
93
|
+
value = nil
|
94
|
+
subject.first_name { |v| value = v }
|
95
|
+
value.should == "Frodo"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '.new' do
|
100
|
+
it 'fails with non-existent properties' do
|
101
|
+
lambda { described_class.new(:bork => '') }.should raise_error(NoMethodError)
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should set properties that it is able to' do
|
105
|
+
obj = described_class.new :first_name => 'Michael'
|
106
|
+
obj.first_name.should == 'Michael'
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'accepts nil' do
|
110
|
+
lambda { DashNoRequiredTest.new(nil) }.should_not raise_error
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'accepts block to define a global default' do
|
114
|
+
obj = described_class.new { |hash, key| key.to_s.upcase }
|
115
|
+
obj.first_name.should == 'FIRST_NAME'
|
116
|
+
obj.count.should be_zero
|
117
|
+
end
|
118
|
+
|
119
|
+
it "fails when required values are missing" do
|
120
|
+
expect { DashTest.new }.to raise_error(ArgumentError)
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
describe 'properties' do
|
126
|
+
it 'lists defined properties' do
|
127
|
+
described_class.properties.should == Set.new([:first_name, :email, :count])
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'checks if a property exists' do
|
131
|
+
described_class.property?('first_name').should be_true
|
132
|
+
described_class.property?(:first_name).should be_true
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'checks if a property is required' do
|
136
|
+
described_class.required?('first_name').should be_true
|
137
|
+
described_class.required?(:first_name).should be_true
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'doesnt include property from subclass' do
|
141
|
+
described_class.property?(:last_name).should be_false
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'lists declared defaults' do
|
145
|
+
described_class.defaults.should == { :count => 0 }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe Wahashie::Dash, 'inheritance' do
|
151
|
+
before do
|
152
|
+
@top = Class.new(Wahashie::Dash)
|
153
|
+
@middle = Class.new(@top)
|
154
|
+
@bottom = Class.new(@middle)
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'reports empty properties when nothing defined' do
|
158
|
+
@top.properties.should be_empty
|
159
|
+
@top.defaults.should be_empty
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'inherits properties downwards' do
|
163
|
+
@top.property :echo
|
164
|
+
@middle.properties.should include(:echo)
|
165
|
+
@bottom.properties.should include(:echo)
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'doesnt inherit properties upwards' do
|
169
|
+
@middle.property :echo
|
170
|
+
@top.properties.should_not include(:echo)
|
171
|
+
@bottom.properties.should include(:echo)
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'allows overriding a default on an existing property' do
|
175
|
+
@top.property :echo
|
176
|
+
@middle.property :echo, :default => 123
|
177
|
+
@bottom.properties.to_a.should == [:echo]
|
178
|
+
@bottom.new.echo.should == 123
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'allows clearing an existing default' do
|
182
|
+
@top.property :echo
|
183
|
+
@middle.property :echo, :default => 123
|
184
|
+
@bottom.property :echo
|
185
|
+
@bottom.properties.to_a.should == [:echo]
|
186
|
+
@bottom.new.echo.should be_nil
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'should allow nil defaults' do
|
190
|
+
@bottom.property :echo, :default => nil
|
191
|
+
@bottom.new.should have_key('echo')
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
|
196
|
+
describe Subclassed do
|
197
|
+
|
198
|
+
subject { Subclassed.new(:first_name => 'Bob', :last_name => 'McNob', :email => 'bob@example.com') }
|
199
|
+
|
200
|
+
its(:count) { should be_zero }
|
201
|
+
|
202
|
+
it { should respond_to(:first_name) }
|
203
|
+
it { should respond_to(:first_name=) }
|
204
|
+
it { should respond_to(:last_name) }
|
205
|
+
it { should respond_to(:last_name=) }
|
206
|
+
|
207
|
+
it 'has one additional property' do
|
208
|
+
described_class.property?(:last_name).should be_true
|
209
|
+
end
|
210
|
+
|
211
|
+
it "didn't override superclass inheritance logic" do
|
212
|
+
described_class.instance_variable_get('@inheritance_test').should be_true
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|