zitdunyet 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +139 -0
- data/Rakefile +11 -0
- data/lib/zitdunyet/checkoff.rb +40 -0
- data/lib/zitdunyet/class_specific.rb +40 -0
- data/lib/zitdunyet/completeness.rb +12 -0
- data/lib/zitdunyet/evaluation.rb +54 -0
- data/lib/zitdunyet/expressions.rb +14 -0
- data/lib/zitdunyet/percent.rb +18 -0
- data/lib/zitdunyet/unit.rb +20 -0
- data/lib/zitdunyet/version.rb +3 -0
- data/lib/zitdunyet.rb +12 -0
- data/spec/lib/zitdunyet/checkoff_spec.rb +54 -0
- data/spec/lib/zitdunyet/class_attr_spec.rb +85 -0
- data/spec/lib/zitdunyet/evaluations_spec.rb +282 -0
- data/spec/lib/zitdunyet/expressions_spec.rb +72 -0
- data/spec/spec_helper.rb +10 -0
- data/zitdunyet.gemspec +23 -0
- metadata +116 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 David Pellegrini
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
# Zitdunyet
|
2
|
+
|
3
|
+
We're all familiar with social websites where you create an account and then are encouraged to provide information about yourself. They often have something when you login that announces "Your profile is 55% complete. Add a photo to reach 70%!" The notion of completeness can be applied to objects of all sort, to processes, and so on.
|
4
|
+
|
5
|
+
This gem provides a general solution for expressing and assessing completeness. It includes:
|
6
|
+
|
7
|
+
+ a DSL for expressing the conditions
|
8
|
+
+ methods to report completeness as True/False or percentage
|
9
|
+
+ a method to provide hints for completion
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'zitdunyet'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install zitdunyet
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
To use the gem, first require the gem and include the Completeness module into the class for which you want to track completeness.
|
28
|
+
|
29
|
+
require 'zitdunyet'
|
30
|
+
|
31
|
+
class UserAccount
|
32
|
+
include Zitdunyet::Completeness
|
33
|
+
|
34
|
+
attr_accessor :name, :birthday, :favorite_color, :friends
|
35
|
+
... etc ...
|
36
|
+
end
|
37
|
+
|
38
|
+
If you'd rather separate concerns, create a Decorator for your model and include the Completeness module there.
|
39
|
+
|
40
|
+
require 'zitdunyet'
|
41
|
+
|
42
|
+
class UserAccountCompleteness
|
43
|
+
include Zitdunyet::Completeness
|
44
|
+
|
45
|
+
def initialize(user_account)
|
46
|
+
@user_accout = user_account
|
47
|
+
end
|
48
|
+
... etc ...
|
49
|
+
end
|
50
|
+
|
51
|
+
### Basic use case
|
52
|
+
|
53
|
+
I'm reminded of Larry Wall's admonition to make simple things easy and difficult things possible. In that spirit, let's start with a simple use case.
|
54
|
+
|
55
|
+
#### Checkoff items
|
56
|
+
|
57
|
+
The conditions for completeness are represented as check-off items. Check-off items are expressed using the #checkoff class macro.
|
58
|
+
|
59
|
+
The arguments are:
|
60
|
+
|
61
|
+
+ label - a String or something that evaluates to a String to identify the item
|
62
|
+
+ amount - how much of the completeness is tied to this item, expressed in percent or units
|
63
|
+
+ options - hint to encourage completion of this item
|
64
|
+
+ logic - a block to evaluate that determines if this item is done. The block is passed _self_ and evaluates to _true_/_false_.
|
65
|
+
|
66
|
+
Let's start with a simple use case in the context of UserAccount.
|
67
|
+
|
68
|
+
checkoff "Name", 30.percent do |s| s.name end
|
69
|
+
checkoff "Birthday", 25.percent do |s| s.birthday end
|
70
|
+
checkoff "Favorite Color", 15.percent do |s| not s.favorite_color.nil? end
|
71
|
+
checkoff "Friends", 30.percent do |s| s.friends.size >= 3 end
|
72
|
+
|
73
|
+
This basically gives credit when name, birthday, and favorite_color have values, and when the user has specified 3 or more friends. Of course, the details of enforcing good values for these fields (e.g., birthdate is actually a Date, friends is an array) is not shown and is your responsibility, not this gem's.
|
74
|
+
|
75
|
+
When writing the checkoff, you may chose to use ()'s and {}'s or break the do ... end over multiple lines if that's more your style. I adopted the style above because it reads well as a DSL. Ruby purists may have another opinion. Any valid Ruby method syntax is fair game, though.
|
76
|
+
|
77
|
+
checkoff("Favorite Color", 15.percent) { |s| s.favorite_color }
|
78
|
+
checkoff "Friends", 30.percent do |s|
|
79
|
+
s.friends.size >= 3
|
80
|
+
end
|
81
|
+
|
82
|
+
#### Testing completeness
|
83
|
+
|
84
|
+
Instances of the class can be tested for completeness by using instance methods defined in Completeness:
|
85
|
+
|
86
|
+
+ #complete? - returns true if all of the check-off items' logic blocks evaluate to true; false otherwise
|
87
|
+
+ #percent_complete - returns a number 0-100 that represents the sum of percentages associated with check-off items that evaluate to true. If the percentages specified for all of the check-off items total 100, then the result is a strict sum of the completed check-off items. If the percentages specified total less than 100, then the returned value is scaled to make up the shortfall.
|
88
|
+
|
89
|
+
#### Hints
|
90
|
+
|
91
|
+
If your UserAccount happens to be incomplete, it would be nice to know what you need to do to complete it.
|
92
|
+
|
93
|
+
+ #hints - returns a hash in which the keys are the labels or hints of the uncompleted steps and the values are the percentages
|
94
|
+
|
95
|
+
By using descriptive labels you can give a decent prompt to the user.
|
96
|
+
|
97
|
+
checkoff "Birthday filled in", 25.percent do |s| s.birthday end
|
98
|
+
checkoff "At least 3 friends", 30.percent do |s| s.friends.size >= 3 end
|
99
|
+
|
100
|
+
Sometimes, though, you may want to be more elaborate in your hinting. #checkoff accepts an optional argument, :hint, for such situations. The hint must be a string or a block that evaluates to a String.
|
101
|
+
|
102
|
+
checkoff "Favorite Color", 15.percent, hint: "What's your favorite color?" do |s| s.favorite_color end
|
103
|
+
checkoff "Friends", 30.percent, hint: lambda {|s| "Add #{3-s.friends.size} friends"} do |s| s.friends.size >= 3 end
|
104
|
+
|
105
|
+
The hint over-rides the label when reported by #hints.
|
106
|
+
|
107
|
+
### Advanced Usage
|
108
|
+
|
109
|
+
Basic usage is fine for most situations, but sometimes you need a little something extra.
|
110
|
+
|
111
|
+
#### Units
|
112
|
+
|
113
|
+
Percentages are easy to understand, but have the annoying property of having to total 100. As noted above, the #percent_complete method is forgiving when your totals fall short, but that may be surprising when you start seeing "43.375% complete" reported when you know all your specified percentages are on the 5's and 10's. Also, checkoff will squawk at you if you attempt to specify a percentage that puts you over 100.
|
114
|
+
|
115
|
+
Instead of percent, the checkoff amount can be expressed in units. This gives you the freedom to specify relative values at whatever scale you like. #percent_complete will calculate the percentages for you.
|
116
|
+
|
117
|
+
checkoff "Step 1", 1.unit do |s| s.step_1_done? end
|
118
|
+
checkoff "Step 2", 1.unit do |s| s.step_2_done? end
|
119
|
+
checkoff "Step 3", 1.unit do |s| s.step_3_done? end
|
120
|
+
|
121
|
+
This strategy will allow you to add a "Step 4" down the road without having to rejigger percentage.
|
122
|
+
|
123
|
+
#### Mixing Percent and Units
|
124
|
+
|
125
|
+
You may mix both percent and units in a series of checkoffs if desired. Let's say you want the user name to always count for 30% regardless of anything else. You could assign it 30% and assign units to the other items. #percent_complete will honor the explicit percents and scale the units into whatever remains.
|
126
|
+
|
127
|
+
#### Inheritance
|
128
|
+
|
129
|
+
Let's say you have a special account that subclasses UserAccount. You want the special account to have additional steps toward completion. No problem. Simply specify the additional checkoff items in the subclass. When evaluating completion, the checkoff items in the UserAccount will be evaluated along with the checkoff items in the subclass.
|
130
|
+
|
131
|
+
In situations where subclassing is a possibility it is good practice to specify the checkoff amounts in units rather than percent. That way the superclass can be complete relative to itself when instantiated, while allowing the subclass to contribute its share when it is instantiated without blowing the 100% barrier.
|
132
|
+
|
133
|
+
## Contributing
|
134
|
+
|
135
|
+
1. Fork it
|
136
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
137
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
138
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
139
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Zitdunyet
|
2
|
+
class Checkoff
|
3
|
+
attr_accessor :label, :percent, :units, :logic, :hint, :stale, :when
|
4
|
+
|
5
|
+
def initialize(label, progress_amount, *opts, &logic)
|
6
|
+
# Fill in the required fields
|
7
|
+
(label.is_a? String) ? self.label = label : (raise ArgumentError.new "Label must be a String")
|
8
|
+
assign_progress(progress_amount)
|
9
|
+
block_given? ? self.logic = logic : (raise ArgumentError.new "Missing the block for assessing completion")
|
10
|
+
|
11
|
+
# Fill in the optional fields if present
|
12
|
+
assign_options(opts) unless opts.empty?
|
13
|
+
end
|
14
|
+
|
15
|
+
def assign_options(opts)
|
16
|
+
options = opts.first
|
17
|
+
self.hint = options.delete(:hint)
|
18
|
+
#self.stale = options.delete(:stale)
|
19
|
+
#self.when = options.delete(:when)
|
20
|
+
raise ArgumentError.new "Unrecognized options: #{options.inspect}" unless options.empty?
|
21
|
+
|
22
|
+
#if self.stale or self.when
|
23
|
+
# raise ArgumentError.new "Specified 'stale' without 'when'" unless self.when
|
24
|
+
# raise ArgumentError.new "Specified 'when' without 'stale'" unless self.stale
|
25
|
+
#end
|
26
|
+
end
|
27
|
+
|
28
|
+
def assign_progress(progress_amount)
|
29
|
+
if progress_amount.is_a? Zitdunyet::Unit
|
30
|
+
self.units=progress_amount.amount
|
31
|
+
elsif progress_amount.is_a? Zitdunyet::Percent
|
32
|
+
self.percent=progress_amount.amount
|
33
|
+
elsif progress_amount.is_a? Numeric
|
34
|
+
self.percent=progress_amount
|
35
|
+
else
|
36
|
+
raise ArgumentError.new "Invalid type for progress amount: #{progress_amount.class}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Zitdunyet
|
2
|
+
module ClassSpecific
|
3
|
+
|
4
|
+
def checklist
|
5
|
+
checklist = self.superclass.respond_to?(:checklist) ? self.superclass.send(:checklist) : []
|
6
|
+
@checklist ? checklist + @checklist : checklist
|
7
|
+
end
|
8
|
+
|
9
|
+
def checklist_add(item)
|
10
|
+
@checklist = (@checklist || []) << item
|
11
|
+
end
|
12
|
+
|
13
|
+
def percentage
|
14
|
+
percentage = self.superclass.respond_to?(:percentage) ? self.superclass.send(:percentage) : 0
|
15
|
+
percentage + (@percentage || 0)
|
16
|
+
end
|
17
|
+
|
18
|
+
def percentage=(amount)
|
19
|
+
@percentage = amount
|
20
|
+
end
|
21
|
+
|
22
|
+
def percentage_add(amount)
|
23
|
+
@percentage = (@percentage || 0) + amount
|
24
|
+
end
|
25
|
+
|
26
|
+
def units
|
27
|
+
units = self.superclass.respond_to?(:units) ? self.superclass.send(:units) : 0
|
28
|
+
units + (@units || 0)
|
29
|
+
end
|
30
|
+
|
31
|
+
def units=(amount)
|
32
|
+
@units = amount
|
33
|
+
end
|
34
|
+
|
35
|
+
def units_add(amount)
|
36
|
+
@units = (@units || 0) + amount
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Zitdunyet
|
2
|
+
module Evaluation
|
3
|
+
|
4
|
+
def complete?
|
5
|
+
self.class.checklist.each { |item| return false unless item.logic.call(self) }
|
6
|
+
true
|
7
|
+
end
|
8
|
+
|
9
|
+
def percent_complete
|
10
|
+
# Scale the percentages if they don't add up to 100. When units are part of the mix, scale the units to fit into
|
11
|
+
# the percentage slice leftover after totaling the percentages.
|
12
|
+
unit_percentage = 0
|
13
|
+
pct_percentage = 1
|
14
|
+
if self.class.percentage < 100
|
15
|
+
unit_percentage = ((100 - self.class.percentage) / self.class.units.to_f) if (self.class.units > 0)
|
16
|
+
pct_percentage = (100 / self.class.percentage.to_f) if (self.class.units == 0)
|
17
|
+
elsif self.class.percentage > 100
|
18
|
+
pct_percentage = (100 / self.class.percentage.to_f) if (self.class.units == 0)
|
19
|
+
end
|
20
|
+
|
21
|
+
completed_pct = 0
|
22
|
+
completed_units = 0
|
23
|
+
complete = true
|
24
|
+
@hints = {}
|
25
|
+
self.class.checklist.each do |item|
|
26
|
+
if item.logic.call(self)
|
27
|
+
if item.percent
|
28
|
+
completed_pct += item.percent
|
29
|
+
else
|
30
|
+
completed_units += item.units if item.units
|
31
|
+
end
|
32
|
+
else
|
33
|
+
complete = false
|
34
|
+
hint = item.hint || item.label
|
35
|
+
@hints[hint] = item.percent ? item.percent * pct_percentage : item.units * unit_percentage
|
36
|
+
end
|
37
|
+
end
|
38
|
+
complete ? 100 : (completed_pct * pct_percentage) + (completed_units * unit_percentage)
|
39
|
+
end
|
40
|
+
|
41
|
+
def hints
|
42
|
+
percent_complete unless @hints
|
43
|
+
hints = {}
|
44
|
+
@hints.each_pair do |hint, pct|
|
45
|
+
if hint.respond_to? :call
|
46
|
+
hints[hint.call(self)] = pct
|
47
|
+
else
|
48
|
+
hints[hint.to_s] = pct
|
49
|
+
end
|
50
|
+
end
|
51
|
+
hints
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Zitdunyet
|
2
|
+
module Expressions
|
3
|
+
|
4
|
+
def checkoff(label, progress_amount, *opts, &logic)
|
5
|
+
# Create an object to represent the check-off item
|
6
|
+
choff = Checkoff.new(label, progress_amount, *opts, &logic)
|
7
|
+
checklist_add choff
|
8
|
+
percentage_add(choff.percent) if choff.percent
|
9
|
+
units_add(choff.units) if choff.units
|
10
|
+
raise RangeError.new "Percentage total (#{percentage}) exceeds 100%" if percentage > 100
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
data/lib/zitdunyet.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "zitdunyet/version"
|
2
|
+
require "zitdunyet/percent"
|
3
|
+
require "zitdunyet/unit"
|
4
|
+
require "zitdunyet/checkoff"
|
5
|
+
require "zitdunyet/class_specific"
|
6
|
+
require "zitdunyet/expressions"
|
7
|
+
require "zitdunyet/evaluation"
|
8
|
+
require "zitdunyet/completeness"
|
9
|
+
|
10
|
+
module Zitdunyet
|
11
|
+
# Your code goes here...
|
12
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Initialization" do
|
4
|
+
|
5
|
+
context "Assigning the label" do
|
6
|
+
it "should accept a String" do
|
7
|
+
label = "The Label"
|
8
|
+
co = Zitdunyet::Checkoff.new(label, 0) { "nuthin'" }
|
9
|
+
co.label.should == label
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should reject anything besides a String" do
|
13
|
+
label = 1
|
14
|
+
expect { Zitdunyet::Checkoff.new(label, 10) { "nuthin'" } }.to raise_error(ArgumentError)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context "Assigning progress amount" do
|
19
|
+
it "should accept Unit" do
|
20
|
+
amount = 10.units
|
21
|
+
co = Zitdunyet::Checkoff.new("The Label", amount) { "nuthin'" }
|
22
|
+
co.units.should == amount.amount
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should accept a Percent" do
|
26
|
+
amount = 10.percent
|
27
|
+
co = Zitdunyet::Checkoff.new("The Label", amount) { "nuthin'" }
|
28
|
+
co.percent.should == amount.amount
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should treat Numeric as percentage" do
|
32
|
+
amount = 10
|
33
|
+
co = Zitdunyet::Checkoff.new("The Label", amount) { "nuthin'" }
|
34
|
+
co.percent.should == amount
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should reject invalid type" do
|
38
|
+
amount = "ten"
|
39
|
+
expect { Zitdunyet::Checkoff.new("The Label", amount) { "nuthin'" } }.to raise_error(ArgumentError)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "Specifying the completion logic" do
|
44
|
+
it "should accept an anonymous block" do
|
45
|
+
text = "this is a test"
|
46
|
+
co = Zitdunyet::Checkoff.new("The Label", 10) { text }
|
47
|
+
co.logic.call.should == text
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should raise an exception if the block is missing" do
|
51
|
+
expect { Zitdunyet::Checkoff.new("The Label", 10) }.to raise_error(ArgumentError)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Class Attr" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
begin
|
7
|
+
Object.send(:remove_const, :Baz)
|
8
|
+
rescue
|
9
|
+
end
|
10
|
+
begin
|
11
|
+
Object.send(:remove_const, :Foo)
|
12
|
+
rescue
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
after(:each) do
|
17
|
+
puts "= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ="
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:foo) do
|
21
|
+
class Foo
|
22
|
+
extend Zitdunyet::ClassSpecific
|
23
|
+
|
24
|
+
def add(item)
|
25
|
+
self.class.checklist_add item
|
26
|
+
self.class.units_add 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def chklst
|
30
|
+
self.class.checklist
|
31
|
+
end
|
32
|
+
|
33
|
+
def units
|
34
|
+
self.class.units
|
35
|
+
end
|
36
|
+
|
37
|
+
def dump
|
38
|
+
puts "Foo#dump: checklist=#{self.class.checklist}, units=#{self.class.units}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
Foo.new
|
43
|
+
end
|
44
|
+
|
45
|
+
let(:baz) do
|
46
|
+
class Baz < Foo
|
47
|
+
def dump
|
48
|
+
puts "Baz#dump: checklist=#{self.class.checklist}, units=#{self.class.units}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
Baz.new
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should initialize with empty array and zeros" do
|
56
|
+
foo.chklst.should be_empty
|
57
|
+
foo.chklst.should have(0).items
|
58
|
+
foo.units.should == 0
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should define class-specific attrs" do
|
62
|
+
foo.dump
|
63
|
+
foo.add("Hello")
|
64
|
+
foo.dump
|
65
|
+
foo.chklst.should have(1).items
|
66
|
+
foo.units.should == 1
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should support inheritance" do
|
70
|
+
foo.dump
|
71
|
+
foo.add("Hello")
|
72
|
+
foo.dump
|
73
|
+
foo.chklst.should have(1).items
|
74
|
+
|
75
|
+
baz.dump
|
76
|
+
baz.add("World")
|
77
|
+
baz.dump
|
78
|
+
baz.chklst.should have(2).items
|
79
|
+
baz.units.should == 2
|
80
|
+
|
81
|
+
foo.dump
|
82
|
+
foo.chklst.should have(1).items
|
83
|
+
foo.units.should == 1
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,282 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Evaluations" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
begin
|
7
|
+
Object.send(:remove_const, :Baz)
|
8
|
+
rescue
|
9
|
+
end
|
10
|
+
begin
|
11
|
+
Object.send(:remove_const, :Foo)
|
12
|
+
rescue
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "basics" do
|
17
|
+
it "should evaluate true" do
|
18
|
+
class Foo
|
19
|
+
include Zitdunyet::Completeness
|
20
|
+
checkoff "Step One", 8.percent do true end
|
21
|
+
end
|
22
|
+
|
23
|
+
foo = Foo.new
|
24
|
+
foo.complete?.should be_true
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should evaluate false" do
|
28
|
+
class Foo
|
29
|
+
include Zitdunyet::Completeness
|
30
|
+
checkoff "Step One", 8.percent do false end
|
31
|
+
end
|
32
|
+
|
33
|
+
foo = Foo.new
|
34
|
+
foo.complete?.should be_false
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should evaluate the condition for self" do
|
38
|
+
class Foo
|
39
|
+
include Zitdunyet::Completeness
|
40
|
+
checkoff "Step One", 60.units do |s| s.blork end
|
41
|
+
checkoff "Step Two", 40.units do |s| s.count >= 3 end
|
42
|
+
|
43
|
+
def blork
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
def count
|
48
|
+
3
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
foo = Foo.new
|
53
|
+
foo.complete?.should be_true
|
54
|
+
foo.percent_complete.should == 100
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "Percentages" do
|
59
|
+
|
60
|
+
it "should evaluate 0 percent when no checkoff items are done" do
|
61
|
+
class Foo
|
62
|
+
include Zitdunyet::Completeness
|
63
|
+
checkoff "Step One", 60.percent do false end
|
64
|
+
checkoff "Step Two", 40.percent do false end
|
65
|
+
end
|
66
|
+
|
67
|
+
foo = Foo.new
|
68
|
+
foo.percent_complete.should == 0
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should evaluate intermediate percentage when some checkoff items are done" do
|
72
|
+
class Foo
|
73
|
+
include Zitdunyet::Completeness
|
74
|
+
checkoff "Step One", 60.percent do true end
|
75
|
+
checkoff "Step Two", 40.percent do false end
|
76
|
+
end
|
77
|
+
|
78
|
+
foo = Foo.new
|
79
|
+
foo.percent_complete.should == 60
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should evaluate 100 percent when all checkoff items are done" do
|
83
|
+
class Foo
|
84
|
+
include Zitdunyet::Completeness
|
85
|
+
checkoff "Step One", 60.percent do true end
|
86
|
+
checkoff "Step Two", 40.percent do true end
|
87
|
+
end
|
88
|
+
|
89
|
+
foo = Foo.new
|
90
|
+
foo.percent_complete.should == 100
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should evaluate fractional percentage when specified" do
|
94
|
+
class Foo
|
95
|
+
include Zitdunyet::Completeness
|
96
|
+
checkoff "Step One", 60.5.percent do true end
|
97
|
+
checkoff "Step Two", 39.5.percent do false end
|
98
|
+
end
|
99
|
+
|
100
|
+
foo = Foo.new
|
101
|
+
foo.percent_complete.should == 60.5
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should scale the percentage when the specified percentages don't total 100" do
|
105
|
+
class Foo
|
106
|
+
include Zitdunyet::Completeness
|
107
|
+
checkoff "Step One", 40.percent do true end
|
108
|
+
checkoff "Step Two", 40.percent do false end
|
109
|
+
end
|
110
|
+
|
111
|
+
foo = Foo.new
|
112
|
+
foo.percent_complete.should == 50
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should scale the percentage when the specified percentages exceed 100" do
|
116
|
+
pending "a decision on how to handle excess percentage"
|
117
|
+
class Foo
|
118
|
+
include Zitdunyet::Completeness
|
119
|
+
checkoff "Step One", 60.percent do true end
|
120
|
+
end
|
121
|
+
|
122
|
+
class Baz < Foo
|
123
|
+
include Zitdunyet::Completeness
|
124
|
+
checkoff "Step Two", 60.percent do false end
|
125
|
+
end
|
126
|
+
|
127
|
+
baz = Baz.new
|
128
|
+
baz.percent_complete.should == 50
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
context "Units" do
|
134
|
+
|
135
|
+
it "should evaluate 0 percent when no checkoff items are done" do
|
136
|
+
class Foo
|
137
|
+
include Zitdunyet::Completeness
|
138
|
+
checkoff "Step One", 60.units do false end
|
139
|
+
checkoff "Step Two", 40.units do false end
|
140
|
+
end
|
141
|
+
|
142
|
+
foo = Foo.new
|
143
|
+
foo.percent_complete.should == 0
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should evaluate intermediate percentage when some checkoff items are done" do
|
147
|
+
class Foo
|
148
|
+
include Zitdunyet::Completeness
|
149
|
+
checkoff "Step One", 60.units do true end
|
150
|
+
checkoff "Step Two", 40.units do false end
|
151
|
+
end
|
152
|
+
|
153
|
+
foo = Foo.new
|
154
|
+
foo.percent_complete.should == 60
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should evaluate 100 percent when all checkoff items are done" do
|
158
|
+
class Foo
|
159
|
+
include Zitdunyet::Completeness
|
160
|
+
checkoff "Step One", 60.units do true end
|
161
|
+
checkoff "Step Two", 40.units do true end
|
162
|
+
end
|
163
|
+
|
164
|
+
foo = Foo.new
|
165
|
+
foo.percent_complete.should == 100
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should evaluate to fractional percents when required" do
|
169
|
+
class Foo
|
170
|
+
include Zitdunyet::Completeness
|
171
|
+
checkoff "Step One", 1.units do true end
|
172
|
+
checkoff "Step Two", 199.units do false end
|
173
|
+
end
|
174
|
+
|
175
|
+
foo = Foo.new
|
176
|
+
foo.percent_complete.should == 0.5
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context "Mix of Percent and Units" do
|
181
|
+
|
182
|
+
it "should evaluate 0 percent when no checkoff items are done" do
|
183
|
+
class Foo
|
184
|
+
include Zitdunyet::Completeness
|
185
|
+
checkoff "Step One", 60.percent do false end
|
186
|
+
checkoff "Step Two", 40.units do false end
|
187
|
+
end
|
188
|
+
|
189
|
+
foo = Foo.new
|
190
|
+
foo.percent_complete.should == 0
|
191
|
+
end
|
192
|
+
|
193
|
+
it "should evaluate intermediate percentage when some checkoff items are done" do
|
194
|
+
class Foo
|
195
|
+
include Zitdunyet::Completeness
|
196
|
+
checkoff "Step One", 60.percent do true end
|
197
|
+
checkoff "Step Two", 40.units do false end
|
198
|
+
end
|
199
|
+
|
200
|
+
foo = Foo.new
|
201
|
+
foo.percent_complete.should == 60
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should evaluate scale the units to fit remaining percentage" do
|
205
|
+
class Foo
|
206
|
+
include Zitdunyet::Completeness
|
207
|
+
checkoff "Step One", 60.units do true end
|
208
|
+
checkoff "Step Two", 40.percent do false end
|
209
|
+
end
|
210
|
+
|
211
|
+
foo = Foo.new
|
212
|
+
foo.percent_complete.should == 60
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should evaluate 100 percent when all checkoff items are done" do
|
216
|
+
class Foo
|
217
|
+
include Zitdunyet::Completeness
|
218
|
+
checkoff "Step One", 60.percent do true end
|
219
|
+
checkoff "Step Two", 40.units do true end
|
220
|
+
end
|
221
|
+
|
222
|
+
foo = Foo.new
|
223
|
+
foo.percent_complete.should == 100
|
224
|
+
end
|
225
|
+
|
226
|
+
it "should evaluate to fractional percents when required" do
|
227
|
+
class Foo
|
228
|
+
include Zitdunyet::Completeness
|
229
|
+
checkoff "Step One", 1.units do true end
|
230
|
+
checkoff "Step Two", 1.units do false end
|
231
|
+
checkoff "Step Three", 99.percent do false end
|
232
|
+
end
|
233
|
+
|
234
|
+
foo = Foo.new
|
235
|
+
foo.percent_complete.should == 0.5
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
context "Hints" do
|
240
|
+
|
241
|
+
it "should provide a hint and percentage for uncompleted checkoff items" do
|
242
|
+
class Foo
|
243
|
+
include Zitdunyet::Completeness
|
244
|
+
checkoff "Step One", 60.units do false end
|
245
|
+
checkoff "Step Two", 40.units, hint: "Add a widget" do false end
|
246
|
+
end
|
247
|
+
|
248
|
+
foo = Foo.new
|
249
|
+
foo.hints.should have(2).items
|
250
|
+
one = foo.hints.delete("Step One")
|
251
|
+
one.should == 60
|
252
|
+
two = foo.hints.delete("Add a widget")
|
253
|
+
two.should == 40
|
254
|
+
end
|
255
|
+
|
256
|
+
it "should evaluate a hint when it is callable" do
|
257
|
+
class Foo
|
258
|
+
include Zitdunyet::Completeness
|
259
|
+
checkoff "Step One", 60.units, hint: lambda {|s| s.blork} do false end
|
260
|
+
checkoff "Step Two", 40.units, hint: lambda {|s| "Add #{5-s.count} widgets"} do false end
|
261
|
+
|
262
|
+
def blork
|
263
|
+
"Raise the blork coefficient by 30%"
|
264
|
+
end
|
265
|
+
|
266
|
+
def count
|
267
|
+
3
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
foo = Foo.new
|
272
|
+
foo.hints.should have(2).items
|
273
|
+
puts foo.hints.inspect
|
274
|
+
one = foo.hints.delete("Raise the blork coefficient by 30%")
|
275
|
+
one.should == 60
|
276
|
+
two = foo.hints.delete("Add 2 widgets")
|
277
|
+
two.should == 40
|
278
|
+
end
|
279
|
+
|
280
|
+
end
|
281
|
+
|
282
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Expressions" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
begin
|
7
|
+
Object.send(:remove_const, :Foo)
|
8
|
+
rescue
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context "Basics" do
|
13
|
+
it "should accept a minimal expression" do
|
14
|
+
class Foo
|
15
|
+
include Zitdunyet::Completeness
|
16
|
+
checkoff "Step One", 8.percent do true end
|
17
|
+
end
|
18
|
+
|
19
|
+
Foo.checklist.first.label.should == "Step One"
|
20
|
+
Foo.checklist.first.percent.should == 8
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "Percentages" do
|
25
|
+
|
26
|
+
it "should reject percentage exceeding 100" do
|
27
|
+
expect {
|
28
|
+
class Foo
|
29
|
+
include Zitdunyet::Completeness
|
30
|
+
checkoff "Step One", 101.percent do true end
|
31
|
+
end
|
32
|
+
}.to raise_error(RangeError)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should reject cumulative percentage exceeding 100" do
|
36
|
+
expect {
|
37
|
+
class Foo
|
38
|
+
include Zitdunyet::Completeness
|
39
|
+
checkoff "Step One", 8.percent do true end
|
40
|
+
checkoff "Step Two", 93.percent do true end
|
41
|
+
end
|
42
|
+
}.to raise_error(RangeError)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should accept percentage exactly 100" do
|
46
|
+
class Foo
|
47
|
+
include Zitdunyet::Completeness
|
48
|
+
checkoff "Step One", 100.percent do true end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should accept cumulative percentage exactly 100" do
|
53
|
+
class Foo
|
54
|
+
include Zitdunyet::Completeness
|
55
|
+
checkoff "Step One", 8.percent do true end
|
56
|
+
checkoff "Step Two", 92.percent do true end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "Hints" do
|
62
|
+
it "should accept a hint" do
|
63
|
+
class Foo
|
64
|
+
include Zitdunyet::Completeness
|
65
|
+
checkoff "Step One", 8.percent, hint: "Add a widget" do true end
|
66
|
+
end
|
67
|
+
|
68
|
+
Foo.checklist.first.hint.should == "Add a widget"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
$TESTING=true
|
2
|
+
$:.push File.join(File.dirname(__FILE__), '..', 'lib')
|
3
|
+
|
4
|
+
require "zitdunyet"
|
5
|
+
#require "factory_girl"
|
6
|
+
#require File.join(File.dirname(__FILE__), "support", "models", "base.rb")
|
7
|
+
#Dir.glob(File.join(File.dirname(__FILE__), "support", "**", "*.rb")).each {|f| require f}
|
8
|
+
#
|
9
|
+
## TODO: (dcp) why isn't the file loading automatically? should load from spec/factories/*.rb by default.
|
10
|
+
#Dir.glob(File.join(File.dirname(__FILE__), "factories", "**", "*.rb")).each {|f| require f}
|
data/zitdunyet.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/zitdunyet/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = "zitdunyet"
|
6
|
+
gem.version = Zitdunyet::VERSION
|
7
|
+
gem.authors = ["David Pellegrini"]
|
8
|
+
gem.email = ["david.pellegrini@spoke.com"]
|
9
|
+
gem.description = %q{Evaluate how done or complete a process or entity is.}
|
10
|
+
gem.summary = %q{DSL to describe the steps to completion and evaluation engine to compute done-ness.}
|
11
|
+
gem.homepage = ""
|
12
|
+
|
13
|
+
gem.rubyforge_project = "zitdunyet"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($\)
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency "rake"
|
21
|
+
gem.add_development_dependency "rspec"
|
22
|
+
gem.add_development_dependency "factory_girl"
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zitdunyet
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- David Pellegrini
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2012-07-18 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rake
|
17
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0"
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: rspec
|
28
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: factory_girl
|
39
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: "0"
|
45
|
+
type: :development
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *id003
|
48
|
+
description: Evaluate how done or complete a process or entity is.
|
49
|
+
email:
|
50
|
+
- david.pellegrini@spoke.com
|
51
|
+
executables: []
|
52
|
+
|
53
|
+
extensions: []
|
54
|
+
|
55
|
+
extra_rdoc_files: []
|
56
|
+
|
57
|
+
files:
|
58
|
+
- .gitignore
|
59
|
+
- Gemfile
|
60
|
+
- LICENSE
|
61
|
+
- README.md
|
62
|
+
- Rakefile
|
63
|
+
- lib/zitdunyet.rb
|
64
|
+
- lib/zitdunyet/checkoff.rb
|
65
|
+
- lib/zitdunyet/class_specific.rb
|
66
|
+
- lib/zitdunyet/completeness.rb
|
67
|
+
- lib/zitdunyet/evaluation.rb
|
68
|
+
- lib/zitdunyet/expressions.rb
|
69
|
+
- lib/zitdunyet/percent.rb
|
70
|
+
- lib/zitdunyet/unit.rb
|
71
|
+
- lib/zitdunyet/version.rb
|
72
|
+
- spec/lib/zitdunyet/checkoff_spec.rb
|
73
|
+
- spec/lib/zitdunyet/class_attr_spec.rb
|
74
|
+
- spec/lib/zitdunyet/evaluations_spec.rb
|
75
|
+
- spec/lib/zitdunyet/expressions_spec.rb
|
76
|
+
- spec/spec_helper.rb
|
77
|
+
- zitdunyet.gemspec
|
78
|
+
homepage: ""
|
79
|
+
licenses: []
|
80
|
+
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
hash: -697234430046674194
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
version: "0"
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
hash: -697234430046674194
|
101
|
+
segments:
|
102
|
+
- 0
|
103
|
+
version: "0"
|
104
|
+
requirements: []
|
105
|
+
|
106
|
+
rubyforge_project: zitdunyet
|
107
|
+
rubygems_version: 1.8.24
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: DSL to describe the steps to completion and evaluation engine to compute done-ness.
|
111
|
+
test_files:
|
112
|
+
- spec/lib/zitdunyet/checkoff_spec.rb
|
113
|
+
- spec/lib/zitdunyet/class_attr_spec.rb
|
114
|
+
- spec/lib/zitdunyet/evaluations_spec.rb
|
115
|
+
- spec/lib/zitdunyet/expressions_spec.rb
|
116
|
+
- spec/spec_helper.rb
|