characterizable 0.0.1 → 0.0.2
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 +90 -73
- data/test/test_characterizable.rb +74 -34
- metadata +2 -2
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.2
|
data/lib/characterizable.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'set'
|
1
2
|
require 'blockenspiel'
|
2
3
|
require 'active_support'
|
3
4
|
require 'active_support/version'
|
@@ -18,31 +19,80 @@ module Characterizable
|
|
18
19
|
end
|
19
20
|
|
20
21
|
def characteristics
|
21
|
-
@_characteristics ||=
|
22
|
+
@_characteristics ||= Snapshot.new self
|
22
23
|
end
|
23
24
|
|
24
|
-
def
|
25
|
+
def dirty_characteristics!
|
25
26
|
@_characteristics = nil
|
26
27
|
end
|
27
|
-
|
28
|
-
def known_characteristics
|
29
|
-
characteristics.select do |c|
|
30
|
-
c.known? and not c.trumped?
|
31
|
-
end
|
32
|
-
end
|
33
28
|
|
34
|
-
|
35
|
-
|
36
|
-
|
29
|
+
# hashes that survive as such when you select/reject/slice them
|
30
|
+
class SurvivorHash < Hash
|
31
|
+
attr_reader :survivor_args
|
32
|
+
def initialize(*survivor_args)
|
33
|
+
@survivor_args = survivor_args
|
34
|
+
end
|
35
|
+
def reject(&block)
|
36
|
+
inject(self.class.new(*survivor_args)) do |memo, ary|
|
37
|
+
unless block.call(*ary)
|
38
|
+
memo[ary[0]] = ary[1]
|
39
|
+
end
|
40
|
+
memo
|
41
|
+
end
|
42
|
+
end
|
43
|
+
def select(&block)
|
44
|
+
inject(self.class.new(*survivor_args)) do |memo, ary|
|
45
|
+
if block.call(*ary)
|
46
|
+
memo[ary[0]] = ary[1]
|
47
|
+
end
|
48
|
+
memo
|
49
|
+
end
|
50
|
+
end
|
51
|
+
def slice(*keys)
|
52
|
+
inject(self.class.new(*survivor_args)) do |memo, ary|
|
53
|
+
if keys.include?(ary[0])
|
54
|
+
memo[ary[0]] = ary[1]
|
55
|
+
end
|
56
|
+
memo
|
57
|
+
end
|
37
58
|
end
|
38
59
|
end
|
39
60
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
61
|
+
class Snapshot < SurvivorHash
|
62
|
+
def initialize(*survivor_args)
|
63
|
+
super
|
64
|
+
take_snapshot
|
65
|
+
end
|
66
|
+
def snapshotted_obj
|
67
|
+
survivor_args.first
|
68
|
+
end
|
69
|
+
def []=(key, value)
|
70
|
+
snapshotted_obj.dirty_characteristics!
|
71
|
+
super
|
72
|
+
end
|
73
|
+
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
|
77
|
+
end
|
78
|
+
end
|
79
|
+
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? }
|
92
|
+
end
|
93
|
+
def visible_unknown
|
94
|
+
unknown.reject { |_, c| c.hidden? }
|
95
|
+
end
|
46
96
|
end
|
47
97
|
|
48
98
|
module ClassMethods
|
@@ -53,51 +103,28 @@ module Characterizable
|
|
53
103
|
delegate :characteristics, :to => :characterizable_base
|
54
104
|
end
|
55
105
|
|
56
|
-
# don't want to use a Hash, because that would be annoying to select from
|
57
|
-
class ArrayOfCharacteristics < Array
|
58
|
-
def [](key_or_index)
|
59
|
-
case key_or_index
|
60
|
-
when String, Symbol
|
61
|
-
detect { |c| c.name == key_or_index }
|
62
|
-
else
|
63
|
-
super
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
class ArrayOfBoundCharacteristics < ArrayOfCharacteristics
|
69
|
-
attr_reader :bound_obj
|
70
|
-
def initialize(*args)
|
71
|
-
@bound_obj = args.pop
|
72
|
-
super
|
73
|
-
load_bound_characteristics
|
74
|
-
end
|
75
|
-
def load_bound_characteristics
|
76
|
-
bound_obj.characterizable_base.characteristics.each do |c|
|
77
|
-
b_c = c.dup
|
78
|
-
b_c.bound_obj = bound_obj
|
79
|
-
push b_c
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
106
|
class Base
|
85
107
|
attr_reader :klass
|
86
108
|
def initialize(klass)
|
87
109
|
@klass = klass
|
88
110
|
end
|
89
111
|
def characteristics
|
90
|
-
@_characteristics ||=
|
112
|
+
@_characteristics ||= SurvivorHash.new
|
91
113
|
end
|
92
114
|
include Blockenspiel::DSL
|
93
115
|
def has(name, options = {}, &block)
|
94
|
-
characteristics
|
116
|
+
characteristics[name] = Characteristic.new(self, name, options, &block)
|
117
|
+
klass.module_eval(%{
|
118
|
+
def #{name}_with_dirty_characteristics=(new_#{name})
|
119
|
+
dirty_characteristics!
|
120
|
+
self.#{name}_without_dirty_characteristics = new_#{name}
|
121
|
+
end
|
122
|
+
alias_method_chain :#{name}=, :dirty_characteristics
|
123
|
+
}, __FILE__, __LINE__) if klass.instance_methods.include?("#{name}=")
|
95
124
|
end
|
96
125
|
end
|
97
126
|
|
98
127
|
class Characteristic
|
99
|
-
class TreatedBoundAsUnbound < RuntimeError; end
|
100
|
-
class TreatedUnboundAsBound < RuntimeError; end
|
101
128
|
attr_reader :base
|
102
129
|
attr_reader :name
|
103
130
|
attr_reader :trumps
|
@@ -106,7 +133,7 @@ module Characterizable
|
|
106
133
|
attr_reader :options
|
107
134
|
def initialize(base, name, options = {}, &block)
|
108
135
|
@base = base
|
109
|
-
@name = name
|
136
|
+
@name = name
|
110
137
|
@trumps = Array.wrap(options.delete(:trumps))
|
111
138
|
@prerequisite = options.delete :prerequisite
|
112
139
|
@hidden = options.delete :hidden
|
@@ -114,29 +141,28 @@ module Characterizable
|
|
114
141
|
Blockenspiel.invoke block, self if block_given?
|
115
142
|
end
|
116
143
|
delegate :characteristics, :to => :base
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
effective_obj(obj).send name
|
144
|
+
def value(obj)
|
145
|
+
case obj
|
146
|
+
when Hash
|
147
|
+
obj[name]
|
148
|
+
else
|
149
|
+
obj.send name
|
150
|
+
end
|
125
151
|
end
|
126
|
-
def unknown?(obj
|
152
|
+
def unknown?(obj)
|
127
153
|
value(obj).nil?
|
128
154
|
end
|
129
|
-
def known?(obj
|
155
|
+
def known?(obj)
|
130
156
|
not unknown?(obj)
|
131
157
|
end
|
132
|
-
def trumped?(obj
|
133
|
-
|
158
|
+
def trumped?(obj)
|
159
|
+
characteristics.any? do |_, c|
|
134
160
|
c.known?(obj) and c.trumps.include?(name)
|
135
161
|
end
|
136
162
|
end
|
137
|
-
def requited?(obj
|
163
|
+
def requited?(obj)
|
138
164
|
return true if prerequisite.nil?
|
139
|
-
|
165
|
+
characteristics[prerequisite].known? obj
|
140
166
|
end
|
141
167
|
def hidden?
|
142
168
|
hidden
|
@@ -144,15 +170,6 @@ module Characterizable
|
|
144
170
|
include Blockenspiel::DSL
|
145
171
|
def reveals(other_name, other_options = {}, &block)
|
146
172
|
base.has other_name, other_options.merge(:prerequisite => name), &block
|
147
|
-
base.klass.module_eval %{
|
148
|
-
def #{name}_with_dependent_#{other_name}=(new_#{name})
|
149
|
-
if new_#{name}.nil?
|
150
|
-
self.#{other_name} = nil
|
151
|
-
end
|
152
|
-
self.#{name}_without_dependent_#{other_name} = new_#{name}
|
153
|
-
end
|
154
|
-
alias_method_chain :#{name}=, :dependent_#{other_name}
|
155
|
-
}, __FILE__, __LINE__
|
156
173
|
end
|
157
174
|
end
|
158
175
|
end
|
@@ -53,16 +53,35 @@ class TestCharacterizable < Test::Unit::TestCase
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
+
should "survive as a certain kind of hash" do
|
57
|
+
a = SimpleAutomobile.new
|
58
|
+
|
59
|
+
assert_equal Characterizable::SurvivorHash, SimpleAutomobile.characteristics.class
|
60
|
+
assert_equal Characterizable::SurvivorHash, SimpleAutomobile.characteristics.select { false }.class
|
61
|
+
assert_equal Characterizable::SurvivorHash, SimpleAutomobile.characteristics.slice(:hello).class
|
62
|
+
assert_equal Characterizable::SurvivorHash, SimpleAutomobile.characteristics.merge({:hi => 'there'}).class
|
63
|
+
|
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
|
68
|
+
|
69
|
+
assert_equal Characterizable::Snapshot, a.characteristics.class
|
70
|
+
assert_equal Characterizable::Snapshot, a.characteristics.select { false }.class
|
71
|
+
assert_equal Characterizable::Snapshot, a.characteristics.slice(:hello).class
|
72
|
+
assert_equal Characterizable::Snapshot, a.characteristics.merge({:hi => 'there'}).class
|
73
|
+
end
|
74
|
+
|
56
75
|
should "tell you what characteristics are known" do
|
57
76
|
a = SimpleAutomobile.new
|
58
77
|
a.make = 'Ford'
|
59
|
-
assert_equal [:make], a.
|
78
|
+
assert_equal [:make], a.characteristics.known.keys
|
60
79
|
end
|
61
80
|
|
62
81
|
should "tell you what characteristics are unknown" do
|
63
82
|
a = SimpleAutomobile.new
|
64
83
|
a.make = 'Ford'
|
65
|
-
assert_equal [:model, :variant], a.
|
84
|
+
assert_equal [:model, :variant], a.characteristics.unknown.keys
|
66
85
|
end
|
67
86
|
|
68
87
|
should "present a concise set of known characteristics by getting rid of those that have been trumped" do
|
@@ -70,38 +89,39 @@ class TestCharacterizable < Test::Unit::TestCase
|
|
70
89
|
a.make = 'Ford'
|
71
90
|
a.model = 'Taurus'
|
72
91
|
a.variant = 'Taurus V6 DOHC'
|
73
|
-
assert_equal [:make, :variant], a.
|
92
|
+
assert_equal [:make, :variant], a.characteristics.known.keys
|
74
93
|
end
|
75
94
|
|
76
95
|
should "not mention a characteristic as unknown if, in fact, it has been trumped" do
|
77
96
|
a = SimpleAutomobile.new
|
78
97
|
a.make = 'Ford'
|
79
98
|
a.variant = 'Taurus V6 DOHC'
|
80
|
-
assert_equal [], a.
|
99
|
+
assert_equal [], a.characteristics.unknown.keys
|
81
100
|
end
|
82
101
|
|
83
102
|
should "not mention a characteristic as unknown if it is waiting on something else to be revealed" do
|
84
103
|
a = Automobile.new
|
85
|
-
assert !a.
|
104
|
+
assert !a.characteristics.unknown.keys.include?(:model_year)
|
86
105
|
end
|
87
106
|
|
88
107
|
should "make sure that trumping works even within revealed characteristics" do
|
89
108
|
a = Automobile.new
|
90
|
-
assert a.
|
109
|
+
assert a.characteristics.unknown.keys.include?(:size_class)
|
91
110
|
a.make = 'Ford'
|
92
111
|
a.model_year = 1999
|
93
112
|
a.model = 'Taurus'
|
94
113
|
a.size_class = 'mid-size'
|
95
|
-
assert_equal [:
|
96
|
-
assert !a.
|
114
|
+
assert_equal [:make, :model_year, :model], a.characteristics.known.keys
|
115
|
+
assert !a.characteristics.unknown.keys.include?(:size_class)
|
97
116
|
end
|
98
117
|
|
99
|
-
should "enforce prerequisites by using an object's setter" do
|
118
|
+
should "not enforce prerequisites by using an object's setter" do
|
100
119
|
a = Automobile.new
|
101
120
|
a.make = 'Ford'
|
102
121
|
a.model_year = 1999
|
103
122
|
a.make = nil
|
104
|
-
assert_equal
|
123
|
+
assert_equal 1999, a.model_year
|
124
|
+
assert_equal nil, a.characteristics.known[:model_year]
|
105
125
|
end
|
106
126
|
|
107
127
|
should "keep user-defined options on a characteristic" do
|
@@ -114,50 +134,70 @@ class TestCharacterizable < Test::Unit::TestCase
|
|
114
134
|
|
115
135
|
should "know which characteristics are 'visible'" do
|
116
136
|
a = Automobile.new
|
117
|
-
assert a.
|
118
|
-
assert !a.
|
137
|
+
assert a.characteristics.unknown.keys.include?(:record_creation_date)
|
138
|
+
assert !a.characteristics.visible_unknown.keys.include?(:record_creation_date)
|
119
139
|
a.record_creation_date = 'yesterday'
|
120
|
-
assert a.
|
121
|
-
assert !a.
|
140
|
+
assert a.characteristics.known.keys.include?(:record_creation_date)
|
141
|
+
assert !a.characteristics.visible_known.keys.include?(:record_creation_date)
|
122
142
|
end
|
123
|
-
|
143
|
+
|
124
144
|
should "be able to access values" do
|
125
145
|
a = Automobile.new
|
126
146
|
a.make = 'Ford'
|
127
|
-
|
128
|
-
b.make = 'Pontiac'
|
129
|
-
assert_equal 'Ford', Automobile.characteristics[:make].value(a)
|
130
|
-
assert_equal 'Pontiac', Automobile.characteristics[:make].value(b)
|
147
|
+
assert_equal 'Ford', a.characteristics[:make]
|
131
148
|
end
|
132
149
|
|
133
|
-
should "
|
150
|
+
should "know what is known on a snapshot" do
|
134
151
|
a = Automobile.new
|
135
152
|
a.make = 'Ford'
|
136
|
-
assert_equal
|
153
|
+
assert_equal [:make], a.characteristics.known.keys
|
137
154
|
end
|
138
155
|
|
139
|
-
should "
|
156
|
+
should "know what is unknown on a snapshot" do
|
140
157
|
a = Automobile.new
|
141
158
|
a.make = 'Ford'
|
142
|
-
|
143
|
-
Automobile.characteristics[:make].value
|
144
|
-
end
|
159
|
+
assert a.characteristics.unknown.keys.include?(:model_year)
|
145
160
|
end
|
146
161
|
|
147
|
-
should "not
|
162
|
+
should "not reveal unknown characteristics in snapshots" do
|
163
|
+
a = Automobile.new
|
164
|
+
a.model_year = 1999
|
165
|
+
assert_equal [], a.characteristics.known.keys
|
166
|
+
assert_equal nil, a.characteristics[:model_year]
|
167
|
+
end
|
168
|
+
|
169
|
+
should "not reveal unknown characteristics in snapshots, even if it was previously revealed" do
|
148
170
|
a = Automobile.new
|
149
171
|
a.make = 'Ford'
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
end
|
172
|
+
a.model_year = 1999
|
173
|
+
assert_equal [:make, :model_year], a.characteristics.known.keys
|
174
|
+
a.make = nil
|
175
|
+
assert_equal [], a.characteristics.known.keys
|
155
176
|
end
|
156
177
|
|
157
|
-
should "
|
178
|
+
should "keep snapshots separately" do
|
179
|
+
a = Automobile.new
|
180
|
+
a.make = 'Ford'
|
181
|
+
a.model_year = 1999
|
182
|
+
snapshot = a.characteristics
|
183
|
+
assert_equal [:make, :model_year], snapshot.known.keys
|
184
|
+
a.make = nil
|
185
|
+
assert_equal [], a.characteristics.known.keys
|
186
|
+
assert_equal [:make, :model_year], snapshot.known.keys
|
187
|
+
end
|
188
|
+
|
189
|
+
should "work when passed around as a snapshot" do
|
158
190
|
a = Automobile.new
|
159
191
|
a.make = 'Ford'
|
160
|
-
|
161
|
-
assert_equal [:make],
|
192
|
+
snapshot = a.characteristics
|
193
|
+
assert_equal [:make], snapshot.known.keys
|
194
|
+
assert snapshot.unknown.keys.include?(:model_year)
|
195
|
+
snapshot[:model_year] = 1999
|
196
|
+
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_equal nil, a.model_year
|
200
|
+
assert_equal nil, a.characteristics[:model_year]
|
201
|
+
assert_equal 1999, snapshot[:model_year]
|
162
202
|
end
|
163
203
|
end
|