characterizable 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/lib/characterizable.rb +34 -45
- data/test/helper.rb +4 -0
- data/test/test_characterizable.rb +101 -42
- metadata +3 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.3
|
data/lib/characterizable.rb
CHANGED
@@ -22,11 +22,12 @@ module Characterizable
|
|
22
22
|
@_characteristics ||= Snapshot.new self
|
23
23
|
end
|
24
24
|
|
25
|
-
def
|
25
|
+
def expire_snapshot!
|
26
26
|
@_characteristics = nil
|
27
27
|
end
|
28
28
|
|
29
29
|
# hashes that survive as such when you select/reject/slice them
|
30
|
+
# they also keep arguments passed to them
|
30
31
|
class SurvivorHash < Hash
|
31
32
|
attr_reader :survivor_args
|
32
33
|
def initialize(*survivor_args)
|
@@ -63,35 +64,25 @@ module Characterizable
|
|
63
64
|
super
|
64
65
|
take_snapshot
|
65
66
|
end
|
66
|
-
def
|
67
|
+
def target
|
67
68
|
survivor_args.first
|
68
69
|
end
|
69
70
|
def []=(key, value)
|
70
|
-
|
71
|
+
target.expire_snapshot!
|
71
72
|
super
|
72
73
|
end
|
73
74
|
def take_snapshot
|
74
|
-
|
75
|
-
if c.
|
76
|
-
self[
|
75
|
+
target.characterizable_base.characteristics.each do |_, c|
|
76
|
+
if c.relevant?(target)
|
77
|
+
self[c.name] = c.value(target)
|
77
78
|
end
|
78
79
|
end
|
79
80
|
end
|
80
|
-
def
|
81
|
-
|
82
|
-
c.known?(self) and c.requited?(self) and not c.trumped?(self)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
def unknown
|
86
|
-
snapshotted_obj.characterizable_base.characteristics.select do |_, c|
|
87
|
-
c.unknown?(self) and c.requited?(self) and not c.trumped?(self)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
def visible_known
|
91
|
-
known.reject { |_, c| c.hidden? }
|
81
|
+
def relevant
|
82
|
+
target.characterizable_base.characteristics.select { |_, c| c.relevant?(self) }
|
92
83
|
end
|
93
|
-
def
|
94
|
-
|
84
|
+
def irrelevant
|
85
|
+
target.characterizable_base.characteristics.select { |_, c| c.irrelevant?(self) }
|
95
86
|
end
|
96
87
|
end
|
97
88
|
|
@@ -115,57 +106,55 @@ module Characterizable
|
|
115
106
|
def has(name, options = {}, &block)
|
116
107
|
characteristics[name] = Characteristic.new(self, name, options, &block)
|
117
108
|
klass.module_eval(%{
|
118
|
-
def #{name}
|
119
|
-
|
120
|
-
self.#{name}
|
109
|
+
def #{name}_with_expire_snapshot=(new_#{name})
|
110
|
+
expire_snapshot!
|
111
|
+
self.#{name}_without_expire_snapshot = new_#{name}
|
121
112
|
end
|
122
|
-
alias_method_chain :#{name}=, :
|
113
|
+
alias_method_chain :#{name}=, :expire_snapshot
|
123
114
|
}, __FILE__, __LINE__) if klass.instance_methods.include?("#{name}=")
|
124
115
|
end
|
125
116
|
end
|
126
|
-
|
127
117
|
class Characteristic
|
128
118
|
attr_reader :base
|
129
119
|
attr_reader :name
|
130
120
|
attr_reader :trumps
|
131
121
|
attr_reader :prerequisite
|
132
|
-
attr_reader :hidden
|
133
122
|
attr_reader :options
|
134
123
|
def initialize(base, name, options = {}, &block)
|
135
124
|
@base = base
|
136
125
|
@name = name
|
137
|
-
@trumps = Array.wrap
|
138
|
-
@prerequisite = options.delete
|
139
|
-
@hidden = options.delete :hidden
|
126
|
+
@trumps = Array.wrap options.delete(:trumps)
|
127
|
+
@prerequisite = options.delete(:prerequisite)
|
140
128
|
@options = options
|
141
129
|
Blockenspiel.invoke block, self if block_given?
|
142
130
|
end
|
131
|
+
def trumped_by
|
132
|
+
@_trumped_by ||= characteristics.select { |_, c| c.trumps.include? name }
|
133
|
+
end
|
143
134
|
delegate :characteristics, :to => :base
|
144
|
-
def value(
|
145
|
-
case
|
135
|
+
def value(target)
|
136
|
+
case target
|
146
137
|
when Hash
|
147
|
-
|
138
|
+
target[name]
|
148
139
|
else
|
149
|
-
|
140
|
+
target.send name if target.respond_to?(name)
|
150
141
|
end
|
151
142
|
end
|
152
|
-
def
|
153
|
-
value(
|
143
|
+
def irrelevant?(target)
|
144
|
+
value(target).nil? and revealed? target and untrumped? target
|
154
145
|
end
|
155
|
-
def
|
156
|
-
|
146
|
+
def relevant?(target)
|
147
|
+
!value(target).nil? and revealed? target and untrumped? target
|
157
148
|
end
|
158
|
-
def
|
159
|
-
|
160
|
-
|
149
|
+
def untrumped?(target)
|
150
|
+
return true if trumped_by.empty?
|
151
|
+
trumped_by.none? do |_, c|
|
152
|
+
c.relevant? target
|
161
153
|
end
|
162
154
|
end
|
163
|
-
def
|
155
|
+
def revealed?(target)
|
164
156
|
return true if prerequisite.nil?
|
165
|
-
characteristics[prerequisite].
|
166
|
-
end
|
167
|
-
def hidden?
|
168
|
-
hidden
|
157
|
+
characteristics[prerequisite].relevant? target
|
169
158
|
end
|
170
159
|
include Blockenspiel::DSL
|
171
160
|
def reveals(other_name, other_options = {}, &block)
|
data/test/helper.rb
CHANGED
@@ -2,10 +2,14 @@ require 'rubygems'
|
|
2
2
|
require 'test/unit'
|
3
3
|
require 'shoulda'
|
4
4
|
require 'ruby-debug'
|
5
|
+
require 'set'
|
5
6
|
|
6
7
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
8
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
8
9
|
require 'characterizable'
|
9
10
|
|
10
11
|
class Test::Unit::TestCase
|
12
|
+
def assert_same_contents(a, b)
|
13
|
+
assert_equal Set.new(a), Set.new(b)
|
14
|
+
end
|
11
15
|
end
|
@@ -1,6 +1,11 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
|
-
|
3
|
+
class Characterizable::Characteristic
|
4
|
+
def hidden?
|
5
|
+
!!options[:hidden]
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
4
9
|
class Automobile
|
5
10
|
attr_accessor :make, :model_year, :model, :variant, :size_class, :hybridity, :daily_distance_estimate
|
6
11
|
attr_accessor :record_creation_date
|
@@ -20,6 +25,7 @@ class Automobile
|
|
20
25
|
# has :urbanity, :measures => :percentage
|
21
26
|
has :hybridity
|
22
27
|
has :daily_distance_estimate, :trumps => [:weekly_distance_estimate, :annual_distance_estimate, :daily_duration], :measures => :length #, :weekly_fuel_cost, :annual_fuel_cost]
|
28
|
+
has :daily_distance_oracle_estimate, :trumps => :daily_distance_estimate
|
23
29
|
# has :daily_duration, :trumps => [:annual_distance_estimate, :weekly_distance_estimate, :daily_distance_estimate], :measures => :time #, :weekly_fuel_cost, :annual_fuel_cost]
|
24
30
|
# has :weekly_distance_estimate, :trumps => [:annual_distance_estimate, :daily_distance_estimate, :daily_duration], :measures => :length #, :weekly_fuel_cost, :annual_fuel_cost]
|
25
31
|
# has :annual_distance_estimate, :trumps => [:weekly_distance_estimate, :daily_distance_estimate, :daily_duration], :measures => :length #, :weekly_fuel_cost, :annual_fuel_cost]
|
@@ -61,10 +67,10 @@ class TestCharacterizable < Test::Unit::TestCase
|
|
61
67
|
assert_equal Characterizable::SurvivorHash, SimpleAutomobile.characteristics.slice(:hello).class
|
62
68
|
assert_equal Characterizable::SurvivorHash, SimpleAutomobile.characteristics.merge({:hi => 'there'}).class
|
63
69
|
|
64
|
-
assert_equal Characterizable::SurvivorHash, a.characteristics.
|
65
|
-
assert_equal Characterizable::SurvivorHash, a.characteristics.
|
66
|
-
assert_equal Characterizable::SurvivorHash, a.characteristics.
|
67
|
-
assert_equal Characterizable::SurvivorHash, a.characteristics.
|
70
|
+
assert_equal Characterizable::SurvivorHash, a.characteristics.relevant.class
|
71
|
+
assert_equal Characterizable::SurvivorHash, a.characteristics.relevant.select { false }.class
|
72
|
+
assert_equal Characterizable::SurvivorHash, a.characteristics.relevant.slice(:hello).class
|
73
|
+
assert_equal Characterizable::SurvivorHash, a.characteristics.relevant.merge({:hi => 'there'}).class
|
68
74
|
|
69
75
|
assert_equal Characterizable::Snapshot, a.characteristics.class
|
70
76
|
assert_equal Characterizable::Snapshot, a.characteristics.select { false }.class
|
@@ -72,47 +78,47 @@ class TestCharacterizable < Test::Unit::TestCase
|
|
72
78
|
assert_equal Characterizable::Snapshot, a.characteristics.merge({:hi => 'there'}).class
|
73
79
|
end
|
74
80
|
|
75
|
-
should "tell you what characteristics are
|
81
|
+
should "tell you what characteristics are relevant" do
|
76
82
|
a = SimpleAutomobile.new
|
77
83
|
a.make = 'Ford'
|
78
|
-
|
84
|
+
assert_same_contents [:make], a.characteristics.relevant.keys
|
79
85
|
end
|
80
86
|
|
81
|
-
should "tell you what characteristics are
|
87
|
+
should "tell you what characteristics are irrelevant" do
|
82
88
|
a = SimpleAutomobile.new
|
83
89
|
a.make = 'Ford'
|
84
|
-
|
90
|
+
assert_same_contents [:model, :variant], a.characteristics.irrelevant.keys
|
85
91
|
end
|
86
92
|
|
87
|
-
should "present a concise set of
|
93
|
+
should "present a concise set of relevant characteristics by getting rid of those that have been trumped" do
|
88
94
|
a = SimpleAutomobile.new
|
89
95
|
a.make = 'Ford'
|
90
96
|
a.model = 'Taurus'
|
91
97
|
a.variant = 'Taurus V6 DOHC'
|
92
|
-
|
98
|
+
assert_same_contents [:make, :variant], a.characteristics.relevant.keys
|
93
99
|
end
|
94
100
|
|
95
|
-
should "not mention a characteristic as
|
101
|
+
should "not mention a characteristic as irrelevant if, in fact, it has been trumped" do
|
96
102
|
a = SimpleAutomobile.new
|
97
103
|
a.make = 'Ford'
|
98
104
|
a.variant = 'Taurus V6 DOHC'
|
99
|
-
|
105
|
+
assert_same_contents [], a.characteristics.irrelevant.keys
|
100
106
|
end
|
101
107
|
|
102
|
-
should "not mention a characteristic as
|
108
|
+
should "not mention a characteristic as irrelevant if it is waiting on something else to be revealed" do
|
103
109
|
a = Automobile.new
|
104
|
-
assert !a.characteristics.
|
110
|
+
assert !a.characteristics.irrelevant.keys.include?(:model_year)
|
105
111
|
end
|
106
112
|
|
107
113
|
should "make sure that trumping works even within revealed characteristics" do
|
108
114
|
a = Automobile.new
|
109
|
-
assert a.characteristics.
|
115
|
+
assert a.characteristics.irrelevant.keys.include?(:size_class)
|
110
116
|
a.make = 'Ford'
|
111
117
|
a.model_year = 1999
|
112
118
|
a.model = 'Taurus'
|
113
119
|
a.size_class = 'mid-size'
|
114
|
-
|
115
|
-
assert !a.characteristics.
|
120
|
+
assert_same_contents [:make, :model_year, :model], a.characteristics.relevant.keys
|
121
|
+
assert !a.characteristics.irrelevant.keys.include?(:size_class)
|
116
122
|
end
|
117
123
|
|
118
124
|
should "not enforce prerequisites by using an object's setter" do
|
@@ -121,7 +127,7 @@ class TestCharacterizable < Test::Unit::TestCase
|
|
121
127
|
a.model_year = 1999
|
122
128
|
a.make = nil
|
123
129
|
assert_equal 1999, a.model_year
|
124
|
-
assert_equal nil, a.characteristics.
|
130
|
+
assert_equal nil, a.characteristics.relevant[:model_year]
|
125
131
|
end
|
126
132
|
|
127
133
|
should "keep user-defined options on a characteristic" do
|
@@ -132,13 +138,9 @@ class TestCharacterizable < Test::Unit::TestCase
|
|
132
138
|
assert !Automobile.characteristics[:daily_distance_estimate].options.has_key?(:trumps)
|
133
139
|
end
|
134
140
|
|
135
|
-
should "
|
136
|
-
|
137
|
-
assert
|
138
|
-
assert !a.characteristics.visible_unknown.keys.include?(:record_creation_date)
|
139
|
-
a.record_creation_date = 'yesterday'
|
140
|
-
assert a.characteristics.known.keys.include?(:record_creation_date)
|
141
|
-
assert !a.characteristics.visible_known.keys.include?(:record_creation_date)
|
141
|
+
should "allow the user to add custom functionality to characteristics" do
|
142
|
+
assert Automobile.characteristics[:record_creation_date].hidden?
|
143
|
+
assert !Automobile.characteristics[:daily_distance_estimate].hidden?
|
142
144
|
end
|
143
145
|
|
144
146
|
should "be able to access values" do
|
@@ -147,32 +149,32 @@ class TestCharacterizable < Test::Unit::TestCase
|
|
147
149
|
assert_equal 'Ford', a.characteristics[:make]
|
148
150
|
end
|
149
151
|
|
150
|
-
should "know what is
|
152
|
+
should "know what is relevant on a snapshot" do
|
151
153
|
a = Automobile.new
|
152
154
|
a.make = 'Ford'
|
153
|
-
|
155
|
+
assert_same_contents [:make], a.characteristics.relevant.keys
|
154
156
|
end
|
155
157
|
|
156
|
-
should "know what is
|
158
|
+
should "know what is irrelevant on a snapshot" do
|
157
159
|
a = Automobile.new
|
158
160
|
a.make = 'Ford'
|
159
|
-
assert a.characteristics.
|
161
|
+
assert a.characteristics.irrelevant.keys.include?(:model_year)
|
160
162
|
end
|
161
163
|
|
162
|
-
should "not reveal
|
164
|
+
should "not reveal irrelevant characteristics in snapshots" do
|
163
165
|
a = Automobile.new
|
164
166
|
a.model_year = 1999
|
165
|
-
|
167
|
+
assert_same_contents [], a.characteristics.relevant.keys
|
166
168
|
assert_equal nil, a.characteristics[:model_year]
|
167
169
|
end
|
168
170
|
|
169
|
-
should "not reveal
|
171
|
+
should "not reveal irrelevant characteristics in snapshots, even if it was previously revealed" do
|
170
172
|
a = Automobile.new
|
171
173
|
a.make = 'Ford'
|
172
174
|
a.model_year = 1999
|
173
|
-
|
175
|
+
assert_same_contents [:make, :model_year], a.characteristics.relevant.keys
|
174
176
|
a.make = nil
|
175
|
-
|
177
|
+
assert_same_contents [], a.characteristics.relevant.keys
|
176
178
|
end
|
177
179
|
|
178
180
|
should "keep snapshots separately" do
|
@@ -180,24 +182,81 @@ class TestCharacterizable < Test::Unit::TestCase
|
|
180
182
|
a.make = 'Ford'
|
181
183
|
a.model_year = 1999
|
182
184
|
snapshot = a.characteristics
|
183
|
-
|
185
|
+
assert_same_contents [:make, :model_year], snapshot.relevant.keys
|
184
186
|
a.make = nil
|
185
|
-
|
186
|
-
|
187
|
+
assert_same_contents [], a.characteristics.relevant.keys
|
188
|
+
assert_same_contents [:make, :model_year], snapshot.relevant.keys
|
187
189
|
end
|
188
190
|
|
189
191
|
should "work when passed around as a snapshot" do
|
190
192
|
a = Automobile.new
|
191
193
|
a.make = 'Ford'
|
192
194
|
snapshot = a.characteristics
|
193
|
-
|
194
|
-
assert snapshot.
|
195
|
+
assert_same_contents [:make], snapshot.relevant.keys
|
196
|
+
assert snapshot.irrelevant.keys.include?(:model_year)
|
195
197
|
snapshot[:model_year] = 1999
|
196
198
|
assert_equal 1999, snapshot[:model_year]
|
197
|
-
|
198
|
-
assert !snapshot.
|
199
|
+
assert_same_contents [:make, :model_year], snapshot.relevant.keys
|
200
|
+
assert !snapshot.irrelevant.keys.include?(:model_year)
|
199
201
|
assert_equal nil, a.model_year
|
200
202
|
assert_equal nil, a.characteristics[:model_year]
|
201
203
|
assert_equal 1999, snapshot[:model_year]
|
202
204
|
end
|
205
|
+
|
206
|
+
should "appreciate that sometimes characteristics just magically appear" do
|
207
|
+
a = Automobile.new
|
208
|
+
a.daily_distance_estimate = 15
|
209
|
+
snapshot = a.characteristics
|
210
|
+
snapshot[:daily_distance_oracle_estimate] = 20
|
211
|
+
assert_same_contents [:daily_distance_oracle_estimate], snapshot.relevant.keys
|
212
|
+
assert_same_contents [:daily_distance_estimate], a.characteristics.relevant.keys
|
213
|
+
end
|
214
|
+
|
215
|
+
# has :make do |make|
|
216
|
+
# make.reveals :model_year do |model_year|
|
217
|
+
# model_year.reveals :model, :trumps => :size_class do |model|
|
218
|
+
# model.reveals :variant, :trumps => :hybridity
|
219
|
+
should "handle revelations on multiple levels" do
|
220
|
+
a = Automobile.new
|
221
|
+
a.make = 'Ford'
|
222
|
+
a.model_year = 1999
|
223
|
+
a.model = 'Taurus'
|
224
|
+
a.variant = 'Taurus 1999'
|
225
|
+
assert_same_contents [:make, :model_year, :model, :variant], a.characteristics.relevant.keys
|
226
|
+
a.make = nil
|
227
|
+
assert_same_contents [], a.characteristics.relevant.keys
|
228
|
+
a.make = 'Ford'
|
229
|
+
assert_same_contents [:make, :model_year, :model, :variant], a.characteristics.relevant.keys
|
230
|
+
a.model_year = nil
|
231
|
+
assert_same_contents [:make], a.characteristics.relevant.keys
|
232
|
+
a.model_year = 1999
|
233
|
+
assert_same_contents [:make, :model_year, :model, :variant], a.characteristics.relevant.keys
|
234
|
+
a.model = nil
|
235
|
+
assert_same_contents [:make, :model_year], a.characteristics.relevant.keys
|
236
|
+
a.model = 'Taurus'
|
237
|
+
assert_same_contents [:make, :model_year, :model, :variant], a.characteristics.relevant.keys
|
238
|
+
a.variant = nil
|
239
|
+
assert_same_contents [:make, :model_year, :model], a.characteristics.relevant.keys
|
240
|
+
end
|
241
|
+
|
242
|
+
should "handle trumping on multiple levels" do
|
243
|
+
a = Automobile.new
|
244
|
+
a.size_class = 'small' # can be trumped by model
|
245
|
+
a.hybridity = 'no' # can be trumped by variant
|
246
|
+
a.make = 'Ford'
|
247
|
+
a.model_year = 1999
|
248
|
+
a.model = 'Taurus'
|
249
|
+
a.variant = 'Taurus 1999'
|
250
|
+
assert_same_contents [:make, :model_year, :model, :variant], a.characteristics.relevant.keys
|
251
|
+
a.variant = nil
|
252
|
+
assert_same_contents [:make, :model_year, :model, :hybridity], a.characteristics.relevant.keys
|
253
|
+
a.variant = 'Taurus 1999'
|
254
|
+
assert_same_contents [:make, :model_year, :model, :variant], a.characteristics.relevant.keys
|
255
|
+
a.model = nil # which reveals size class, but also hybridity!
|
256
|
+
assert_same_contents [:make, :model_year, :size_class, :hybridity], a.characteristics.relevant.keys
|
257
|
+
a.model = 'Taurus'
|
258
|
+
assert_same_contents [:make, :model_year, :model, :variant], a.characteristics.relevant.keys
|
259
|
+
a.make = nil
|
260
|
+
assert_same_contents [:size_class, :hybridity], a.characteristics.relevant.keys
|
261
|
+
end
|
203
262
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 3
|
9
|
+
version: 0.0.3
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Andy Rossmeissl
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-05-
|
18
|
+
date: 2010-05-12 00:00:00 -04:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|