leap 0.4.6 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.markdown +250 -0
- data/Rakefile +4 -0
- data/leap.gemspec +28 -27
- data/lib/leap.rb +7 -3
- data/lib/leap/committee.rb +46 -20
- data/lib/leap/core_ext.rb +1 -1
- data/lib/leap/decision.rb +27 -4
- data/lib/leap/deliberation.rb +10 -0
- data/lib/leap/deliberations_accessor.rb +16 -0
- data/lib/leap/goal_methods_documentation.rb +20 -0
- data/lib/leap/implicit_attributes.rb +5 -1
- data/lib/leap/no_solution_error.rb +40 -0
- data/lib/leap/quorum.rb +30 -23
- data/lib/leap/report.rb +17 -31
- data/lib/leap/subject.rb +50 -3
- data/lib/leap/version.rb +3 -3
- data/test/helper.rb +20 -3
- data/test/test_leap.rb +50 -10
- metadata +41 -31
- data/README.rdoc +0 -7
- data/lib/leap/xml_serializer.rb +0 -27
- data/test/leap/test_committee.rb +0 -42
- data/test/leap/test_quorum.rb +0 -31
- data/test/leap/test_report.rb +0 -134
data/lib/leap/decision.rb
CHANGED
@@ -1,18 +1,36 @@
|
|
1
1
|
module Leap
|
2
|
+
# Encapsulates a set of strategies to determine a potentially non-obvious attribute of a Leap subject.
|
2
3
|
class Decision
|
3
|
-
|
4
|
+
# The goal of the decision, as defined by the first parameter of <tt>Leap::Subject#decide</tt>.
|
5
|
+
attr_reader :goal
|
4
6
|
|
7
|
+
# The method name used to retrieve a curated hash of the subject instance's attributes for use during deliberation. Initially defined by the <tt>:with</tt> option on <tt>Leap::Subject#decide</tt>.
|
8
|
+
attr_reader :signature_method
|
9
|
+
|
10
|
+
# Returns the array of committees defined within the decision block.
|
11
|
+
attr_reader :committees
|
12
|
+
|
13
|
+
# Creates a <tt>Leap::Decision</tt> object with a given goal.
|
14
|
+
#
|
15
|
+
# Generally you won't initialize a <tt>Leap::Decision</tt> directly; <tt>Leap::Subject#decide</tt> does it for you.
|
16
|
+
#
|
17
|
+
# @param [Symbol] goal The ultimate goal of the decision--what are we trying to decide about the subject?
|
18
|
+
# @param [Hash] options
|
19
|
+
# @option [optional, Symbol] with The name of an instance method on the subject that will, when called, provide a Hash of curated attributes about itself. <a href="http://github.com/brighterplanet/charisma">Charisma</a> is great for this. If this option is not provided, Leap will use a low-budget alternative (see <tt>Leap::ImplicitAttributes</tt>)
|
5
20
|
def initialize(goal, options)
|
6
21
|
@goal = goal
|
7
22
|
@signature_method = options[:with] || :_leap_implicit_attributes
|
8
23
|
@committees = []
|
9
24
|
end
|
10
25
|
|
11
|
-
|
12
|
-
|
26
|
+
# Make the decision.
|
27
|
+
#
|
28
|
+
# 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.
|
29
|
+
# @see Leap::GoalMethodsDocumentation
|
30
|
+
def make(characteristics, *considerations)
|
13
31
|
options = considerations.extract_options!
|
14
32
|
committees.reject { |c| characteristics.keys.include? c.name }.reverse.inject(Deliberation.new(characteristics)) do |deliberation, committee|
|
15
|
-
if report = committee.report(
|
33
|
+
if report = committee.report(deliberation.characteristics, considerations, options)
|
16
34
|
deliberation.reports.unshift report
|
17
35
|
deliberation.characteristics[committee.name] = report.conclusion
|
18
36
|
end
|
@@ -21,6 +39,11 @@ module Leap
|
|
21
39
|
end
|
22
40
|
|
23
41
|
include ::Blockenspiel::DSL
|
42
|
+
|
43
|
+
# Define a committee within the decision.
|
44
|
+
#
|
45
|
+
# Used within a <tt>Leap::Subject#decide</tt> block to define a new committee. See <tt>Leap::Committee</tt> for details.
|
46
|
+
# @param [Symbol] name The name of the attribute that the committee will return.
|
24
47
|
def committee(name, &blk)
|
25
48
|
committee = ::Leap::Committee.new(name)
|
26
49
|
@committees << committee
|
data/lib/leap/deliberation.rb
CHANGED
@@ -1,17 +1,27 @@
|
|
1
1
|
module Leap
|
2
|
+
# Encapsulates a single computation of a Leap subject's decision, as performed on an instance of that subject class.
|
2
3
|
class Deliberation
|
4
|
+
# Accumulates knowledge about the Leap subject instance, beginning with the instance's explicit attributes, and later augmented by committee proceedings.
|
3
5
|
attr_accessor :characteristics
|
6
|
+
|
7
|
+
# Accumulates proceedings of the deliberation, including conclusions and methodology.
|
4
8
|
attr_accessor :reports
|
5
9
|
|
10
|
+
# Create a new deliberation, to be made in light of the provided characteristics of the Leap subject instance.
|
11
|
+
# @param [Hash] characteristics The initial set of characteristics made available to committees during deliberation.
|
6
12
|
def initialize(characteristics)
|
7
13
|
self.characteristics = characteristics
|
8
14
|
self.reports = []
|
9
15
|
end
|
10
16
|
|
17
|
+
# Convenience method to access values within the deliberation's characteristics hash.
|
18
|
+
# @param [Symbol] characteristic
|
11
19
|
def [](characteristic)
|
12
20
|
characteristics[characteristic]
|
13
21
|
end
|
14
22
|
|
23
|
+
# Report which named protocols the deliberation incidentally complied with.
|
24
|
+
# @return [Array]
|
15
25
|
def compliance
|
16
26
|
reports.map(&:quorum).map(&:compliance).inject do |memo, c|
|
17
27
|
next c unless memo
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Leap
|
2
|
+
# Provides an intelligent accessor method for a subject instance's deliberations.
|
3
|
+
# @see Leap::Subject
|
4
|
+
module DeliberationsAccessor
|
5
|
+
# Returns a special hash of deliberations that will make necessary decisions if they have not yet been made.
|
6
|
+
def deliberations
|
7
|
+
@deliberations ||= Hash.new do |h, k|
|
8
|
+
return nil unless respond_to? k
|
9
|
+
send k
|
10
|
+
h[k]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Leap
|
2
|
+
# Used strictly for documenting the dynamic methods created by <tt>Leap::Subject#decide</tt>
|
3
|
+
#
|
4
|
+
# @note This module is provided due to limitations in the YARD documentation system.
|
5
|
+
# @see Leap::Subject#decide
|
6
|
+
module GoalMethodsDocumentation
|
7
|
+
|
8
|
+
# @overload your_goal_name(*considerations = [], options = {})
|
9
|
+
# Computes a previously-defined Leap decision named <tt>your_goal_name</tt>.
|
10
|
+
#
|
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
|
+
# @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.
|
14
|
+
# @return The value of the newly-decided goal.
|
15
|
+
# @raise [Leap::NoSolutionError] Leap could not compute the decision's goal on this subject instance given its characteristics and compliance constraint.
|
16
|
+
def method_missing(*args, &blk)
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,7 +1,11 @@
|
|
1
1
|
module Leap
|
2
|
+
# Ideally Leap subjects will provide methods that return a curated hash of attributes suitable for Leap decisions and indicate them with the <tt>:with</tt> option on <tt>Leap::Subject#decide</tt>.
|
3
|
+
# If this type of method is not available, or if it is not properly indicated, Leap will fall back to using the stopgap in this module.
|
2
4
|
module ImplicitAttributes
|
5
|
+
# Provides an articifial attributes hash constructed from the object's instance variables.
|
6
|
+
# @return [Hash]
|
3
7
|
def _leap_implicit_attributes
|
4
|
-
Hash[*instance_variables.map { |variable| variable.to_s.delete('@').to_sym }.zip(instance_variables.map { |variable| instance_variable_get variable }).flatten]
|
8
|
+
Hash[*instance_variables.map { |variable| variable.to_s.delete('@').to_sym }.zip(instance_variables.map { |variable| instance_variable_get variable }).flatten].except(:deliberations)
|
5
9
|
end
|
6
10
|
end
|
7
11
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
|
3
|
+
module Leap
|
4
|
+
# Raised when a Leap solution cannot be found.
|
5
|
+
#
|
6
|
+
# If this is raised unexpectedly, try removing compliance constraints or double-checking that you have enough quorums within mainline committees to provide conclusions given any combination of input data.
|
7
|
+
class NoSolutionError < ArgumentError;
|
8
|
+
# Create the excpetion
|
9
|
+
def initialize(options = {})
|
10
|
+
@goal = options[:goal]
|
11
|
+
@deliberation = options[:deliberation]
|
12
|
+
|
13
|
+
if @goal
|
14
|
+
help = "No solution was found for \"#{@goal}\"."
|
15
|
+
else
|
16
|
+
help = "No solution was found."
|
17
|
+
end
|
18
|
+
|
19
|
+
if @deliberation
|
20
|
+
help << " (#{deliberation_report})"
|
21
|
+
end
|
22
|
+
|
23
|
+
super help
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# A report on the deliberation proceedings, for debugging purposes.
|
29
|
+
def deliberation_report
|
30
|
+
@deliberation.characteristics.keys.sort_by(&:to_s).map do |characteristic|
|
31
|
+
statement = "#{characteristic}: "
|
32
|
+
if report = @deliberation.reports.find { |r| r.committee.name == characteristic }
|
33
|
+
statement << report.quorum.name.humanize.downcase
|
34
|
+
else
|
35
|
+
statement << 'provided as input'
|
36
|
+
end
|
37
|
+
end.join ', '
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/leap/quorum.rb
CHANGED
@@ -1,8 +1,25 @@
|
|
1
1
|
module Leap
|
2
|
+
# A basic building block of a Leap decision.
|
3
|
+
#
|
4
|
+
# A quorum encapsulates a specific methodology for drawing a conclusion based on a set of input information.
|
2
5
|
class Quorum
|
3
|
-
|
4
|
-
|
5
|
-
attr_reader :name
|
6
|
+
|
7
|
+
# The name of the quorum
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
# Quorum attribute requirements, as defined by the <tt>:needs</tt> option
|
11
|
+
attr_reader :requirements
|
12
|
+
|
13
|
+
# Useful attributes, as defined by the <tt>:wants</tt> option
|
14
|
+
attr_reader :supplements
|
15
|
+
|
16
|
+
# The quorum's methodology, as a Ruby closure.
|
17
|
+
attr_reader :process
|
18
|
+
|
19
|
+
# Protocols with which this quorum complies.
|
20
|
+
attr_reader :compliance
|
21
|
+
|
22
|
+
# (see Leap::Committee#quorum)
|
6
23
|
def initialize(name, options, blk)
|
7
24
|
@name = name
|
8
25
|
@requirements = Array.wrap options[:needs]
|
@@ -11,41 +28,31 @@ module Leap
|
|
11
28
|
@process = blk
|
12
29
|
end
|
13
30
|
|
31
|
+
# Do the provided characteristics satisfy the quorum's requirements?
|
32
|
+
# @param [Hash] characteristics
|
33
|
+
# @return [TrueClass, NilClass]
|
14
34
|
def satisfied_by?(characteristics)
|
15
35
|
(requirements - characteristics.keys).empty?
|
16
36
|
end
|
17
37
|
|
38
|
+
# Does the quorum comply with the given set of protocols?
|
39
|
+
# @param [Array] guideines The list of protocols we're for which we're checking compliance.
|
40
|
+
# @return [TrueClass, NilClass]
|
18
41
|
def complies_with?(guidelines)
|
19
42
|
(guidelines - compliance).empty?
|
20
43
|
end
|
21
44
|
|
45
|
+
# Perform the quorum's methodology using the given characteristics.
|
46
|
+
# @param [Hash] characteristics
|
47
|
+
# @return The methodology's result
|
22
48
|
def acknowledge(characteristics, considerations)
|
23
49
|
considerations.unshift characteristics
|
24
50
|
process.call(*considerations[0...process.arity])
|
25
51
|
end
|
26
52
|
|
53
|
+
# All characteristics either needed or wanted by the quorum.
|
27
54
|
def characteristics
|
28
55
|
requirements + supplements
|
29
56
|
end
|
30
|
-
|
31
|
-
def as_json(*)
|
32
|
-
{
|
33
|
-
'name' => name.to_s,
|
34
|
-
'requirements' => requirements.map(&:to_s),
|
35
|
-
'appreciates' => supplements.map(&:to_s),
|
36
|
-
'complies' => compliance.map(&:to_s)
|
37
|
-
}
|
38
|
-
end
|
39
|
-
|
40
|
-
def to_xml(options = {})
|
41
|
-
super options do |xml|
|
42
|
-
xml.quorum do |quorum_block|
|
43
|
-
quorum_block.name name.to_s, :type => 'string'
|
44
|
-
array_to_xml(quorum_block, :requirements, requirements)
|
45
|
-
array_to_xml(quorum_block, :appreciates, supplements, 'supplement')
|
46
|
-
array_to_xml(quorum_block, :complies, compliance, 'compliance')
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
57
|
end
|
51
58
|
end
|
data/lib/leap/report.rb
CHANGED
@@ -1,41 +1,27 @@
|
|
1
1
|
module Leap
|
2
|
+
# Encapsulates a committee's report; that is, the conclusion of its selected quorum, along with administrative details.
|
2
3
|
class Report
|
3
|
-
include XmlSerializer
|
4
|
-
|
5
|
-
attr_reader :subject, :committee, :conclusion, :quorum
|
6
4
|
|
7
|
-
|
5
|
+
# The committee that produced the report
|
6
|
+
attr_reader :committee
|
7
|
+
|
8
|
+
# The committee's conclusion
|
9
|
+
attr_reader :conclusion
|
10
|
+
|
11
|
+
# The quorum whose methodology provided the conclusion.
|
12
|
+
attr_reader :quorum
|
13
|
+
|
14
|
+
# Create a new report.
|
15
|
+
#
|
16
|
+
# This is generally called in the midst of <tt>Leap::Committee#report</tt>
|
17
|
+
# @param [Leap::Committee] The committee that produced the report.
|
18
|
+
# @param [Hash] report A single-pair hash containing the responsible quorum and its conclusion.
|
19
|
+
# @raise [ArgumentError] Raised for anonymous reports, or if the report is not made properly.
|
20
|
+
def initialize(committee, report)
|
8
21
|
raise ArgumentError, 'Reports must identify themselves' unless committee.is_a?(::Leap::Committee)
|
9
|
-
@subject = subject
|
10
22
|
@committee = committee
|
11
23
|
raise ArgumentError, 'Please report with quorum => conclusion' unless report.is_a?(Hash) and report.length == 1
|
12
24
|
@quorum, @conclusion = report.first
|
13
25
|
end
|
14
|
-
|
15
|
-
def formatted_conclusion
|
16
|
-
return @formatted_conclusion unless @formatted_conclusion.nil?
|
17
|
-
if characteristic = subject.class.characteristics[committee.name]
|
18
|
-
@formatted_conclusion = characteristic.display committee.name => conclusion
|
19
|
-
end
|
20
|
-
@formatted_conclusion ||= conclusion
|
21
|
-
end
|
22
|
-
|
23
|
-
def as_json(*)
|
24
|
-
{
|
25
|
-
'committee' => committee.as_json,
|
26
|
-
'conclusion' => formatted_conclusion,
|
27
|
-
'quorum' => quorum.as_json
|
28
|
-
}
|
29
|
-
end
|
30
|
-
|
31
|
-
def to_xml(options = {})
|
32
|
-
super options do |xml|
|
33
|
-
xml.report do |report_block|
|
34
|
-
committee.to_xml(options.merge :skip_instruct => true, :builder => report_block)
|
35
|
-
report_block.conclusion formatted_conclusion, :type => formatted_conclusion.class.to_s.downcase
|
36
|
-
quorum.to_xml(options.merge :skip_instruct => true, :builder => report_block)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
26
|
end
|
41
27
|
end
|
data/lib/leap/subject.rb
CHANGED
@@ -1,19 +1,66 @@
|
|
1
1
|
module Leap
|
2
|
+
# In Leap lingo, Subject refers to the host class, instances of which we're trying to arrive at conclusions about.
|
3
|
+
#
|
4
|
+
# It is within Subjects that we establish decision-making strategies using <tt>#decide</tt>
|
5
|
+
# @see #decide
|
2
6
|
module Subject
|
7
|
+
# Establishes the <tt>@decisions</tt> class instance variable for accumulating Leap decisions, along with an accessor for their deliberations.
|
8
|
+
# Also injects <tt>Leap::ImplicitAttributes</tt>, which provides a low-budget attribute curation method.
|
9
|
+
# @see Leap::Decision
|
10
|
+
# @see Leap::Deliberation
|
11
|
+
# @see Leap::DeliberationsAccessor
|
12
|
+
# @see Leap::ImplicitAttributes
|
3
13
|
def self.extended(base)
|
4
14
|
base.instance_variable_set :@decisions, {}
|
5
|
-
base.send :attr_reader, :deliberations
|
6
15
|
base.send :include, ::Leap::ImplicitAttributes
|
16
|
+
base.send :include, ::Leap::DeliberationsAccessor
|
17
|
+
base.send :include, ::Leap::GoalMethodsDocumentation
|
7
18
|
end
|
19
|
+
|
20
|
+
# Accumulates <tt>Leap::Decision</tt> objects, having been defined with <tt>#decide</tt>.
|
21
|
+
# @see #decide
|
8
22
|
attr_reader :decisions
|
23
|
+
|
24
|
+
# Defines a new <tt>Leap::Decision</tt> on the subject, using a DSL within its block.
|
25
|
+
#
|
26
|
+
# The DSL within the block is primarily composed of <tt>Leap::Decision#committee</tt>.
|
27
|
+
# Using <tt>#decide</tt> will define a new method on instances of the subject class, named after the decision's <tt>goal</tt>,
|
28
|
+
# that <i>computes</i> the decision through a process called deliberation (see <tt>Leap::GoalMethodsDocumentation</tt>).
|
29
|
+
#
|
30
|
+
# @example Defining a Leap decision
|
31
|
+
# class Person
|
32
|
+
# include Leap
|
33
|
+
# decide :lucky_number do
|
34
|
+
# # Decision definition elided (see Leap::Decision#committee)
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# Person.new.lucky_number # => 42
|
39
|
+
#
|
40
|
+
# @param [Symbol] goal The goal of the decision--what is it that we're trying to decide about the subject?
|
41
|
+
# @param [optional, Hash] options
|
42
|
+
# @option [optional, Symbol] with The name of an instance method on the subject that will, when called, provide a Hash of curated attributes about itself. <a href="http://github.com/brighterplanet/charisma">Charisma</a> is great for this. If this option is not provided, Leap will use a low-budget alternative (see <tt>Leap::ImplicitAttributes</tt>)
|
43
|
+
#
|
44
|
+
# @see Leap::Decision
|
45
|
+
# @see Leap::Decision#committee
|
46
|
+
# @see Leap::Deliberation
|
47
|
+
# @see Leap::GoalMethodsDocumentation
|
48
|
+
# @see Leap::ImplicitAttributes
|
9
49
|
def decide(goal, options = {}, &blk)
|
10
50
|
decisions[goal] = ::Leap::Decision.new goal, options
|
11
51
|
Blockenspiel.invoke(blk, decisions[goal])
|
12
52
|
define_method goal do |*considerations|
|
13
53
|
options = considerations.extract_options!
|
14
54
|
@deliberations ||= {}
|
15
|
-
|
16
|
-
|
55
|
+
decision = self.class.decisions[goal]
|
56
|
+
characteristics = send(self.class.decisions[goal].signature_method)
|
57
|
+
considerations.push(options)
|
58
|
+
@deliberations[goal] = decision.make(characteristics, *considerations)
|
59
|
+
if @deliberations[goal][goal].nil?
|
60
|
+
raise ::Leap::NoSolutionError, :goal => goal,
|
61
|
+
:deliberation => @deliberations[goal]
|
62
|
+
end
|
63
|
+
@deliberations[goal][goal]
|
17
64
|
end
|
18
65
|
end
|
19
66
|
end
|
data/lib/leap/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
module Leap
|
2
|
-
VERSION =
|
3
|
-
end
|
1
|
+
module Leap
|
2
|
+
VERSION = "0.5.0"
|
3
|
+
end
|
data/test/helper.rb
CHANGED
@@ -3,7 +3,13 @@ require 'bundler'
|
|
3
3
|
Bundler.setup
|
4
4
|
require 'test/unit'
|
5
5
|
require 'shoulda'
|
6
|
-
require '
|
6
|
+
require 'charisma'
|
7
|
+
require 'active_support/version'
|
8
|
+
%w{
|
9
|
+
active_support/core_ext/hash/except
|
10
|
+
}.each do |active_support_3_requirement|
|
11
|
+
require active_support_3_requirement
|
12
|
+
end if ActiveSupport::VERSION::MAJOR == 3
|
7
13
|
|
8
14
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
9
15
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
@@ -25,7 +31,7 @@ class Person
|
|
25
31
|
@magic_integer = options[:magic_integer] if options[:magic_integer] && options[:magic_integer].is_a?(Integer)
|
26
32
|
end
|
27
33
|
|
28
|
-
include
|
34
|
+
include Charisma
|
29
35
|
|
30
36
|
characterize do
|
31
37
|
has :name
|
@@ -61,7 +67,7 @@ class Person
|
|
61
67
|
end
|
62
68
|
|
63
69
|
committee :magic_float do
|
64
|
-
quorum 'ancient recipe', :needs => :name do |characteristics|
|
70
|
+
quorum 'ancient recipe', :needs => :name, :complies => :zeus do |characteristics|
|
65
71
|
('A'..'Z').to_a.index(characteristics[:name].chars.to_a.first).to_f / ('A'..'Z').to_a.index(characteristics[:name].chars.to_a.last).to_f
|
66
72
|
end
|
67
73
|
end
|
@@ -102,3 +108,14 @@ class Thing
|
|
102
108
|
committee(:anything) {}
|
103
109
|
end
|
104
110
|
end
|
111
|
+
|
112
|
+
class Seamus
|
113
|
+
include Leap
|
114
|
+
decide :can_i_commit_to_that_date do
|
115
|
+
committee :can_i_commit_to_that_date do
|
116
|
+
quorum 'my first instinct', :complies => :bent do |characteristics|
|
117
|
+
:maybe
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
data/test/test_leap.rb
CHANGED
@@ -11,7 +11,6 @@ class TestLeap < Test::Unit::TestCase
|
|
11
11
|
end
|
12
12
|
|
13
13
|
should 'naturally receive an International Psychics Association-compliant lucky number' do
|
14
|
-
@person.lucky_number
|
15
14
|
assert_equal [:ipa], @person.deliberations[:lucky_number].compliance
|
16
15
|
end
|
17
16
|
end
|
@@ -26,22 +25,15 @@ class TestLeap < Test::Unit::TestCase
|
|
26
25
|
end
|
27
26
|
|
28
27
|
should 'nevertheless remember how his lucky number was determined' do
|
29
|
-
@person.lucky_number
|
30
|
-
assert_equal({ :magic_integer => 6, :lucky_number => 36, :age => 5, :litmus => {}}, @person.deliberations[:lucky_number].characteristics)
|
28
|
+
assert_equal(@person.deliberations[:lucky_number].characteristics, { :magic_integer => 6, :lucky_number => 36, :age => 5, :litmus => {}})
|
31
29
|
assert_equal 'ninja style', @person.deliberations[:lucky_number].reports.find{ |r| r.committee.name == :magic_integer }.quorum.name
|
32
30
|
end
|
33
31
|
|
34
|
-
should 'but only as long as it had actually been determined' do
|
35
|
-
assert_nil @person.deliberations
|
36
|
-
end
|
37
|
-
|
38
32
|
should 'only give quorums what they ask for' do
|
39
|
-
@person.lucky_number # make the decision
|
40
33
|
assert_equal({}, @person.deliberations[:lucky_number].reports.find{ |r| r.committee.name == :litmus }.conclusion)
|
41
34
|
end
|
42
35
|
|
43
36
|
should 'not receive an International Psychics Association-compliant lucky number unless he asks for it' do
|
44
|
-
@person.lucky_number
|
45
37
|
assert_equal [], @person.deliberations[:lucky_number].compliance
|
46
38
|
end
|
47
39
|
end
|
@@ -90,15 +82,63 @@ class TestLeap < Test::Unit::TestCase
|
|
90
82
|
end
|
91
83
|
end
|
92
84
|
|
85
|
+
context "A lazy subject" do
|
86
|
+
setup do
|
87
|
+
@thing = Thing.new
|
88
|
+
@thing.anything rescue nil
|
89
|
+
end
|
90
|
+
|
91
|
+
should 'have proper implicit characteristics' do
|
92
|
+
assert_equal Hash.new, @thing.deliberations[:anything].characteristics
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
93
96
|
context "An impossible decision" do
|
94
97
|
setup do
|
95
98
|
@thing = Thing.new
|
96
99
|
end
|
97
100
|
|
98
101
|
should 'be impossible to make' do
|
99
|
-
assert_raise ::Leap::NoSolutionError do
|
102
|
+
exception = assert_raise ::Leap::NoSolutionError do
|
100
103
|
@thing.anything
|
101
104
|
end
|
105
|
+
|
106
|
+
assert_match(/No solution was found for "anything"/, exception.message)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context "A difficult decision" do
|
111
|
+
setup do
|
112
|
+
@person = Person.new :name => 'Bozo'
|
113
|
+
end
|
114
|
+
|
115
|
+
should 'provide details about its apparent impossibility' do
|
116
|
+
exception = assert_raise ::Leap::NoSolutionError do
|
117
|
+
@person.lucky_number :comply => :zeus
|
118
|
+
end
|
119
|
+
|
120
|
+
assert_match(/No solution was found for "lucky_number"/, exception.message)
|
121
|
+
assert_match(/magic_float: ancient recipe, name: provided as input/, exception.message)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'Seamus deciding about whether he can commit to a date' do
|
126
|
+
setup do
|
127
|
+
@seamus = Seamus.new
|
128
|
+
end
|
129
|
+
|
130
|
+
should 'work for most people' do
|
131
|
+
assert_equal :maybe, @seamus.can_i_commit_to_that_date
|
132
|
+
end
|
133
|
+
|
134
|
+
should 'work for BenT, who is easygoing' do
|
135
|
+
assert_equal :maybe, @seamus.can_i_commit_to_that_date(:comply => :bent)
|
136
|
+
end
|
137
|
+
|
138
|
+
should 'never work for andy' do
|
139
|
+
assert_raise ::Leap::NoSolutionError do
|
140
|
+
@seamus.can_i_commit_to_that_date(:comply => :andy)
|
141
|
+
end
|
102
142
|
end
|
103
143
|
end
|
104
144
|
end
|