leap 0.5.6 → 0.5.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -32,10 +32,9 @@ module Leap
32
32
  #
33
33
  # General you won't call this directly, but rather use the dynamically-created method with this decision's goal as its name on the subject instance.
34
34
  # @see Leap::GoalMethodsDocumentation
35
- def make(characteristics, *considerations)
35
+ def make(characteristics, options, *considerations)
36
36
  Leap.log.decision "Leaping to conclusion", goal
37
37
  Leap.log.decision "Initial characteristics: #{characteristics.inspect}", goal
38
- options = considerations.extract_options!
39
38
  committees.reject { |c| characteristics.keys.include? c.name }.reverse.inject(Deliberation.new(characteristics)) do |deliberation, committee|
40
39
  Leap.instrument.committee committee.name do
41
40
  if report = committee.report(deliberation.characteristics, considerations, options)
@@ -19,14 +19,39 @@ module Leap
19
19
  def [](characteristic)
20
20
  characteristics[characteristic]
21
21
  end
22
+
23
+ # Convenience method to access a certain committee's report within this deliberation.
24
+ # @param [Symbol] committee
25
+ def report(committee)
26
+ reports.find { |r| r.committee.name == committee }
27
+ end
22
28
 
23
29
  # Report which named protocols the deliberation incidentally complied with.
30
+ # @param [Symbol, optional] committee If provided, Leap will compute this decision's compliance with respect only to this particular conclusion within it. If not provided, compliance will be computed for the entire decision.
24
31
  # @return [Array]
25
- def compliance
32
+ def compliance(committee = nil)
33
+ (committee ? compliance_from(committee) : general_compliance) || []
34
+ end
35
+
36
+ private
37
+
38
+ def general_compliance
26
39
  reports.map(&:quorum).map(&:compliance).inject do |memo, c|
27
- next c unless memo
28
40
  memo & c
29
41
  end
30
42
  end
43
+
44
+ def compliance_from(committee)
45
+ if report = report(committee)
46
+ compliance = report.quorum.requirements.inject(nil) do |memo, requirement|
47
+ if subcompliance = compliance_from(requirement)
48
+ memo ? memo & subcompliance : subcompliance
49
+ else
50
+ memo
51
+ end
52
+ end
53
+ report.quorum.compliance & (compliance || report.quorum.compliance)
54
+ end
55
+ end
31
56
  end
32
57
  end
@@ -10,7 +10,7 @@ module Leap
10
10
  #
11
11
  # @param [optional, Array] considerations An ordered array of additional details, immutable during the course of deliberation, that should be made available to each committee for provision, upon request, to quorums.
12
12
  # @param [optional, Hash] options Additional options
13
- # @option comply Force the ensuing deliberation to comply with one or more "protocols" by only respecting quorums that comply with this (these) protocol(s). Protocols can be anything--a Fixnum, a String, whatever, but by tradition a Symbol. If compliance is required with multiple protocols, they should be passed in an Array.
13
+ # @option comply Force the ensuing deliberation to comply with one or more "protocols" by only respecting quorums that comply with this (these) protocol(s). Protocols can be anything--a Fixnum, a String, whatever, but by tradition a Symbol. If compliance is required with multiple protocols, they should be passed in an Array. If complex compliance is desired, where certain conclusions must comply with specific protocols, use a Hash in the form of <tt>{ :first_protocol => [:a_committee, :another_committee], :second_protocol => :another_committee }</tt>.
14
14
  # @return The value of the newly-decided goal--or, if there is no committee with the same name as the goal, a hash of committee reports
15
15
  # @raise [Leap::NoSolutionError] Leap could not compute the decision's goal on this subject instance given its characteristics and compliance constraint.
16
16
  def method_missing(*args, &blk)
@@ -29,7 +29,7 @@ module Leap
29
29
  def deliberation_report
30
30
  @deliberation.characteristics.keys.sort_by(&:to_s).map do |characteristic|
31
31
  statement = "#{characteristic}: "
32
- if report = @deliberation.reports.find { |r| r.committee.name == characteristic }
32
+ if report = @deliberation.report(characteristic)
33
33
  statement << report.quorum.name.humanize.downcase
34
34
  else
35
35
  statement << 'provided as input'
@@ -51,15 +51,23 @@ module Leap
51
51
  Blockenspiel.invoke(blk, decisions[goal])
52
52
  define_method goal do |*considerations|
53
53
  Leap.instrument.decision goal do
54
+ options = considerations.extract_options!
54
55
  @deliberations ||= {}
55
56
  decision = self.class.decisions[goal]
56
57
  characteristics = send(decision.signature_method)
57
- @deliberations[goal] = decision.make(characteristics, *considerations)
58
+ @deliberations[goal] = decision.make(characteristics, options, *considerations)
58
59
  if decision.mastered? and @deliberations[goal][goal].nil?
59
60
  raise ::Leap::NoSolutionError, :goal => goal, :deliberation => @deliberations[goal]
60
61
  elsif decision.mastered?
61
62
  Leap.log.decision "Success", goal
62
63
  @deliberations[goal][goal]
64
+ elsif options[:comply].is_a? Hash
65
+ options[:comply].each do |protocol, committees|
66
+ [committees].flatten.each do |committee|
67
+ @deliberations[goal].compliance(committee).include?(protocol) or raise ::Leap::NoSolutionError, :goal => committee, :deliberation => @deliberations[goal]
68
+ end
69
+ end
70
+ Leap.log.decision "Success", goal
63
71
  else
64
72
  Leap.log.decision "Success", goal
65
73
  @deliberations[goal]
@@ -1,3 +1,3 @@
1
1
  module Leap
2
- VERSION = "0.5.6"
2
+ VERSION = "0.5.7"
3
3
  end
@@ -120,14 +120,29 @@ class Seamus
120
120
  end
121
121
  end
122
122
 
123
- class Idea
123
+ class Idea < Struct.new(:gotchas, :caveats)
124
+ def to_hash() Hash[members.zip(values)].reject {|_,v| v.nil?} end
125
+
124
126
  include Leap
125
- decide :value do
127
+ decide :value, :with => :to_hash do
126
128
  committee :cost do
127
- quorum('default') {0}
129
+ quorum 'based on estimate of hangups', :needs => :hangups, :complies => :common_sense do |characteristics|
130
+ characteristics[:hangups]
131
+ end
128
132
  end
129
133
  committee :benefit do
134
+ quorum 'based on caveats', :needs => :caveats, :complies => :wisdom do |characteristics|
135
+ 10 - characteristics[:caveats]
136
+ end
137
+ end
138
+ committee :caveats do
130
139
  quorum('default') {1}
131
140
  end
141
+ committee :hangups do
142
+ quorum 'based on estimate of gotchas', :needs => :gotchas, :complies => :common_sense do |characteristics|
143
+ characteristics[:gotchas]
144
+ end
145
+ quorum('default') {0}
146
+ end
132
147
  end
133
148
  end
@@ -161,15 +161,50 @@ class TestLeap < Test::Unit::TestCase
161
161
  context 'A decision without a master committee' do
162
162
  setup do
163
163
  @idea = Idea.new
164
+ @bad_idea = Idea.new(100, 10) # gotchas, caveats
164
165
  end
165
166
 
166
167
  should 'still compute' do
167
168
  @idea.value
168
- assert_equal({:cost => 0, :benefit => 1}, @idea.deliberations[:value].characteristics)
169
+ assert_equal({:cost => 0, :benefit => 9, :caveats => 1, :hangups => 0}, @idea.deliberations[:value].characteristics)
169
170
  end
170
171
 
171
172
  should 'provide easy access to committee reports' do
172
173
  assert_equal 0, @idea.value[:cost]
173
174
  end
175
+
176
+ should 'provide compliance specific to a certain conclusion' do
177
+ # If hangups does not comply with common sense, neither should cost
178
+ assert_equal [], @idea.deliberations[:value].compliance(:hangups)
179
+ assert_equal [], @idea.deliberations[:value].compliance(:benefit)
180
+ assert_equal [], @idea.deliberations[:value].compliance(:cost)
181
+
182
+ # If hangups complies with common sense, cost should also
183
+ assert_equal [:common_sense], @bad_idea.deliberations[:value].compliance(:hangups)
184
+ assert_equal [:common_sense], @bad_idea.deliberations[:value].compliance(:cost)
185
+
186
+ # User input complies with all standards
187
+ assert_equal [:wisdom], @bad_idea.deliberations[:value].compliance(:benefit)
188
+ end
189
+
190
+ should 'only return compliant values when compliance is requested and endpoint is unknown' do
191
+ # Nothing complies
192
+ assert_equal({}, @idea.value(:comply => :common_sense).characteristics)
193
+
194
+ # Everything but benefit complies
195
+ assert_equal({:gotchas => 100, :caveats => 10, :hangups => 100, :cost => 100}, @bad_idea.value(:comply => :common_sense).characteristics)
196
+ end
197
+
198
+ should 'return an error message when known endpoint cannot be achieved' do
199
+ exception = assert_raise ::Leap::NoSolutionError do
200
+ @idea.value(:comply => { :common_sense => :benefit })
201
+ end
202
+ assert_match(/No solution was found for "benefit"/, exception.message)
203
+
204
+ exception = assert_raise ::Leap::NoSolutionError do
205
+ @bad_idea.value(:comply => { :common_sense => :benefit })
206
+ end
207
+ assert_match(/No solution was found for "benefit"/, exception.message)
208
+ end
174
209
  end
175
210
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.6
4
+ version: 0.5.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -15,7 +15,7 @@ date: 2011-12-15 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: charisma
18
- requirement: &10759248 !ruby/object:Gem::Requirement
18
+ requirement: &6662508 !ruby/object:Gem::Requirement
19
19
  none: false
20
20
  requirements:
21
21
  - - ~>
@@ -23,10 +23,10 @@ dependencies:
23
23
  version: 0.2.0
24
24
  type: :development
25
25
  prerelease: false
26
- version_requirements: *10759248
26
+ version_requirements: *6662508
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: shoulda
29
- requirement: &10758996 !ruby/object:Gem::Requirement
29
+ requirement: &6662088 !ruby/object:Gem::Requirement
30
30
  none: false
31
31
  requirements:
32
32
  - - ! '>='
@@ -34,10 +34,10 @@ dependencies:
34
34
  version: '0'
35
35
  type: :development
36
36
  prerelease: false
37
- version_requirements: *10758996
37
+ version_requirements: *6662088
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: bueller
40
- requirement: &10758720 !ruby/object:Gem::Requirement
40
+ requirement: &6661620 !ruby/object:Gem::Requirement
41
41
  none: false
42
42
  requirements:
43
43
  - - ! '>='
@@ -45,10 +45,10 @@ dependencies:
45
45
  version: '0'
46
46
  type: :development
47
47
  prerelease: false
48
- version_requirements: *10758720
48
+ version_requirements: *6661620
49
49
  - !ruby/object:Gem::Dependency
50
50
  name: blockenspiel
51
- requirement: &10758420 !ruby/object:Gem::Requirement
51
+ requirement: &6661068 !ruby/object:Gem::Requirement
52
52
  none: false
53
53
  requirements:
54
54
  - - ! '>='
@@ -56,10 +56,10 @@ dependencies:
56
56
  version: 0.3.2
57
57
  type: :runtime
58
58
  prerelease: false
59
- version_requirements: *10758420
59
+ version_requirements: *6661068
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: activesupport
62
- requirement: &10758120 !ruby/object:Gem::Requirement
62
+ requirement: &6660456 !ruby/object:Gem::Requirement
63
63
  none: false
64
64
  requirements:
65
65
  - - ! '>='
@@ -67,10 +67,10 @@ dependencies:
67
67
  version: 2.3.4
68
68
  type: :runtime
69
69
  prerelease: false
70
- version_requirements: *10758120
70
+ version_requirements: *6660456
71
71
  - !ruby/object:Gem::Dependency
72
72
  name: i18n
73
- requirement: &10757892 !ruby/object:Gem::Requirement
73
+ requirement: &6660096 !ruby/object:Gem::Requirement
74
74
  none: false
75
75
  requirements:
76
76
  - - ! '>='
@@ -78,10 +78,10 @@ dependencies:
78
78
  version: '0'
79
79
  type: :runtime
80
80
  prerelease: false
81
- version_requirements: *10757892
81
+ version_requirements: *6660096
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: builder
84
- requirement: &10757616 !ruby/object:Gem::Requirement
84
+ requirement: &6659700 !ruby/object:Gem::Requirement
85
85
  none: false
86
86
  requirements:
87
87
  - - ! '>='
@@ -89,7 +89,7 @@ dependencies:
89
89
  version: '0'
90
90
  type: :runtime
91
91
  prerelease: false
92
- version_requirements: *10757616
92
+ version_requirements: *6659700
93
93
  description: Leap to conclusions
94
94
  email: andy@rossmeissl.net
95
95
  executables: []
@@ -132,7 +132,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
132
132
  version: '0'
133
133
  segments:
134
134
  - 0
135
- hash: 744383405
135
+ hash: 545920577
136
136
  required_rubygems_version: !ruby/object:Gem::Requirement
137
137
  none: false
138
138
  requirements:
@@ -141,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
141
  version: '0'
142
142
  segments:
143
143
  - 0
144
- hash: 744383405
144
+ hash: 545920577
145
145
  requirements: []
146
146
  rubyforge_project:
147
147
  rubygems_version: 1.8.11