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 CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.0.3
@@ -22,11 +22,12 @@ module Characterizable
22
22
  @_characteristics ||= Snapshot.new self
23
23
  end
24
24
 
25
- def dirty_characteristics!
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 snapshotted_obj
67
+ def target
67
68
  survivor_args.first
68
69
  end
69
70
  def []=(key, value)
70
- snapshotted_obj.dirty_characteristics!
71
+ target.expire_snapshot!
71
72
  super
72
73
  end
73
74
  def take_snapshot
74
- snapshotted_obj.characterizable_base.characteristics.each do |k, c|
75
- if c.known?(snapshotted_obj) and c.requited?(snapshotted_obj) and not c.trumped?(snapshotted_obj)
76
- self[k] = snapshotted_obj.send c.name
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 known
81
- snapshotted_obj.characterizable_base.characteristics.select do |_, c|
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 visible_unknown
94
- unknown.reject { |_, c| c.hidden? }
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}_with_dirty_characteristics=(new_#{name})
119
- dirty_characteristics!
120
- self.#{name}_without_dirty_characteristics = new_#{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}=, :dirty_characteristics
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(options.delete(:trumps))
138
- @prerequisite = options.delete :prerequisite
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(obj)
145
- case obj
135
+ def value(target)
136
+ case target
146
137
  when Hash
147
- obj[name]
138
+ target[name]
148
139
  else
149
- obj.send name
140
+ target.send name if target.respond_to?(name)
150
141
  end
151
142
  end
152
- def unknown?(obj)
153
- value(obj).nil?
143
+ def irrelevant?(target)
144
+ value(target).nil? and revealed? target and untrumped? target
154
145
  end
155
- def known?(obj)
156
- not unknown?(obj)
146
+ def relevant?(target)
147
+ !value(target).nil? and revealed? target and untrumped? target
157
148
  end
158
- def trumped?(obj)
159
- characteristics.any? do |_, c|
160
- c.known?(obj) and c.trumps.include?(name)
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 requited?(obj)
155
+ def revealed?(target)
164
156
  return true if prerequisite.nil?
165
- characteristics[prerequisite].known? obj
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
- # TODO
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.known.class
65
- assert_equal Characterizable::SurvivorHash, a.characteristics.known.select { false }.class
66
- assert_equal Characterizable::SurvivorHash, a.characteristics.known.slice(:hello).class
67
- assert_equal Characterizable::SurvivorHash, a.characteristics.known.merge({:hi => 'there'}).class
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 known" do
81
+ should "tell you what characteristics are relevant" do
76
82
  a = SimpleAutomobile.new
77
83
  a.make = 'Ford'
78
- assert_equal [:make], a.characteristics.known.keys
84
+ assert_same_contents [:make], a.characteristics.relevant.keys
79
85
  end
80
86
 
81
- should "tell you what characteristics are unknown" do
87
+ should "tell you what characteristics are irrelevant" do
82
88
  a = SimpleAutomobile.new
83
89
  a.make = 'Ford'
84
- assert_equal [:model, :variant], a.characteristics.unknown.keys
90
+ assert_same_contents [:model, :variant], a.characteristics.irrelevant.keys
85
91
  end
86
92
 
87
- should "present a concise set of known characteristics by getting rid of those that have been trumped" do
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
- assert_equal [:make, :variant], a.characteristics.known.keys
98
+ assert_same_contents [:make, :variant], a.characteristics.relevant.keys
93
99
  end
94
100
 
95
- should "not mention a characteristic as unknown if, in fact, it has been trumped" do
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
- assert_equal [], a.characteristics.unknown.keys
105
+ assert_same_contents [], a.characteristics.irrelevant.keys
100
106
  end
101
107
 
102
- should "not mention a characteristic as unknown if it is waiting on something else to be revealed" do
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.unknown.keys.include?(:model_year)
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.unknown.keys.include?(:size_class)
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
- assert_equal [:make, :model_year, :model], a.characteristics.known.keys
115
- assert !a.characteristics.unknown.keys.include?(:size_class)
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.known[:model_year]
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 "know which characteristics are 'visible'" do
136
- a = Automobile.new
137
- assert a.characteristics.unknown.keys.include?(:record_creation_date)
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 known on a snapshot" do
152
+ should "know what is relevant on a snapshot" do
151
153
  a = Automobile.new
152
154
  a.make = 'Ford'
153
- assert_equal [:make], a.characteristics.known.keys
155
+ assert_same_contents [:make], a.characteristics.relevant.keys
154
156
  end
155
157
 
156
- should "know what is unknown on a snapshot" do
158
+ should "know what is irrelevant on a snapshot" do
157
159
  a = Automobile.new
158
160
  a.make = 'Ford'
159
- assert a.characteristics.unknown.keys.include?(:model_year)
161
+ assert a.characteristics.irrelevant.keys.include?(:model_year)
160
162
  end
161
163
 
162
- should "not reveal unknown characteristics in snapshots" do
164
+ should "not reveal irrelevant characteristics in snapshots" do
163
165
  a = Automobile.new
164
166
  a.model_year = 1999
165
- assert_equal [], a.characteristics.known.keys
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 unknown characteristics in snapshots, even if it was previously revealed" do
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
- assert_equal [:make, :model_year], a.characteristics.known.keys
175
+ assert_same_contents [:make, :model_year], a.characteristics.relevant.keys
174
176
  a.make = nil
175
- assert_equal [], a.characteristics.known.keys
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
- assert_equal [:make, :model_year], snapshot.known.keys
185
+ assert_same_contents [:make, :model_year], snapshot.relevant.keys
184
186
  a.make = nil
185
- assert_equal [], a.characteristics.known.keys
186
- assert_equal [:make, :model_year], snapshot.known.keys
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
- assert_equal [:make], snapshot.known.keys
194
- assert snapshot.unknown.keys.include?(:model_year)
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
- assert_equal [:make, :model_year], snapshot.known.keys
198
- assert !snapshot.unknown.keys.include?(:model_year)
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
- - 2
9
- version: 0.0.2
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-11 00:00:00 -04:00
18
+ date: 2010-05-12 00:00:00 -04:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency