characterizable 0.0.2 → 0.0.3
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 +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
|