leap 0.4.6 → 0.5.0
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/.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
|