fact_checker 0.0.3 → 0.1.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.
- checksums.yaml +7 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/README.md +102 -17
- data/fact_checker.gemspec +5 -4
- data/lib/fact_checker/mixin.rb +32 -0
- data/lib/fact_checker/result.rb +18 -0
- data/lib/fact_checker/version.rb +1 -1
- data/lib/fact_checker.rb +2 -2
- data/spec/mixin_spec.rb +238 -0
- data/spec/result_spec.rb +29 -0
- data/spec/spec_helper.rb +3 -3
- data/spec/version_spec.rb +9 -0
- metadata +59 -39
- data/.rvmrc +0 -1
- data/examples/README.md +0 -72
- data/examples/basic.rb +0 -60
- data/lib/fact_checker/base.rb +0 -79
- data/lib/fact_checker/boot.rb +0 -31
- data/spec/fact_checker/base_spec.rb +0 -272
- data/spec/fact_checker/version_spec.rb +0 -7
- data/spec/fact_checker_inheritance_spec.rb +0 -63
- data/spec/fact_checker_spec.rb +0 -87
- data/spec/support/classes_with_facts.rb +0 -29
- data/spec/support/shared_facts_behaviour.rb +0 -17
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 49efc644177e73e8127c70768faeb8d3c4f29b54
|
4
|
+
data.tar.gz: 7c91d43ed59898af6d528880e15cd69375eb4940
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 69ae38383592c810071ecef53093fbfc13844eeb30aac26b5679a8757243ffb2cc476cb9a6dd4e2e6297f12ff46a30cc8db554ca8cbc14dd0b342afd060d960d
|
7
|
+
data.tar.gz: 8103359a9eeff6f0d438dffbbaccd8fa33bef1d2b5a3e614a2bea835f10f90250d0eaea9c1c15f50d17b2015c849bcbbe4926714cabec5c709743fc57baf4de0
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
fact_checker
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.0.0
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,10 +1,7 @@
|
|
1
|
-
# Fact Checker
|
1
|
+
# Fact Checker [](http://travis-ci.org/alexis/fact_checker)
|
2
|
+
[](https://codeclimate.com/github/alexis/fact_checker)
|
2
3
|
|
3
|
-
Simple ruby gem to check hierarchically dependent "facts" about objects.
|
4
|
-
|
5
|
-
## Installation
|
6
|
-
|
7
|
-
gem install fact_checker
|
4
|
+
Simple ruby gem to define and check hierarchically dependent "facts" about objects.
|
8
5
|
|
9
6
|
## Synopsys
|
10
7
|
|
@@ -12,20 +9,108 @@
|
|
12
9
|
class Person
|
13
10
|
include FactChecker
|
14
11
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
define_fact(:good_job) { p.job.good? }
|
13
|
+
define_fact(:good_family) { p.family.good? }
|
14
|
+
define_fact(:is_healthy) { p.health.good? }
|
15
|
+
define_fact(:is_happy => [:is_healthy, :good_family, :good_job]) { ! p.too_clever? }
|
19
16
|
|
20
17
|
...
|
21
18
|
end
|
22
19
|
|
23
|
-
p = Person.new(:
|
24
|
-
p.good_job?
|
25
|
-
p.good_family?
|
26
|
-
p.is_healthy?
|
27
|
-
p.
|
28
|
-
p.is_happy?
|
20
|
+
p = Person.new(job: good_job, family: good_family, health: :good, intellect: :too_clever)
|
21
|
+
p.good_job? # => true
|
22
|
+
p.good_family? # => true
|
23
|
+
p.is_healthy? # => true
|
24
|
+
p.is_happy.available? # => true (dependency satisfied)
|
25
|
+
p.is_happy? # => false
|
26
|
+
```
|
27
|
+
|
28
|
+
## Description
|
29
|
+
|
30
|
+
The gem is most usefull when you have something
|
31
|
+
like a checklist, a list of tasks that your users should complete to achieve some goal.
|
32
|
+
|
33
|
+
For example, let's say that in order to publish an article, user have to:
|
34
|
+
|
35
|
+
1. write the article
|
36
|
+
2. name the article (user may complete steps 1 & 2 in any order)
|
37
|
+
3. choose its category
|
38
|
+
4. assign tags to the article (user may complete steps 3 & 4 in any order, but only after steps 1 & 2)
|
39
|
+
5. mark article as ready for review (only after steps 1-3 are completed, but step 4 is not required)
|
40
|
+
6. recieve approvement from one of moderators (all steps 1-5 are required)
|
41
|
+
|
42
|
+
<!--- The imporant thing here - which makes fact_checker worth its use - is that you want to display this
|
43
|
+
checklist for users in a way that they could instantly understand which steps are completed, which
|
44
|
+
is not available yet, and which are ready for action.
|
45
|
+
This means that each step could be in 3 different states: "completed", "ready for action" and "not available".
|
46
|
+
-->
|
47
|
+
|
48
|
+
Using fact_checker that logic could be implemented like this:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
include FactChecker
|
52
|
+
|
53
|
+
define_fact(:step1) { content.present? }
|
54
|
+
define_fact(:step2) { name.present? }
|
55
|
+
define_fact(:step3 => [:step1, :step2]) { category.present? }
|
56
|
+
define_fact(:step4 => [:step1, :step2]) { tag_list.present? }
|
57
|
+
define_fact(:step5 => :step3) { ready_for_approvement? }
|
58
|
+
define_fact(:step6 => [:step4, :step5]) { approved? }
|
59
|
+
|
60
|
+
def state_of(step)
|
61
|
+
return 'completed' if valid?(step)
|
62
|
+
return 'ready_for_action' if available?(step)
|
63
|
+
return 'not_available'
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
Just to compare, a possible alternative implimentation without fact_checker:
|
68
|
+
|
69
|
+
``` ruby
|
70
|
+
def step1_available?
|
71
|
+
true
|
72
|
+
end
|
73
|
+
def step1_valid?
|
74
|
+
content.present?
|
75
|
+
end
|
76
|
+
def step2_available?
|
77
|
+
true
|
78
|
+
end
|
79
|
+
def step2_valid?
|
80
|
+
name.present?
|
81
|
+
end
|
82
|
+
def step3_available?
|
83
|
+
step1? && step2?
|
84
|
+
end
|
85
|
+
def step3_valid?
|
86
|
+
step3_available? && a.category.present?
|
87
|
+
end
|
88
|
+
def step4_available?
|
89
|
+
step1? && step2?
|
90
|
+
end
|
91
|
+
def step4_valid?
|
92
|
+
step4_available? && tag_list.present?
|
93
|
+
end
|
94
|
+
def step5_available?
|
95
|
+
step3
|
96
|
+
end
|
97
|
+
def step5_valid?
|
98
|
+
step5_available? && a.ready_for_approvement?
|
99
|
+
end
|
100
|
+
def step6_available?
|
101
|
+
step4? && step5
|
102
|
+
end
|
103
|
+
def step6_valid?
|
104
|
+
step6_available? && a.approved?
|
105
|
+
end
|
29
106
|
|
30
|
-
|
107
|
+
def state_of(step)
|
108
|
+
return 'completed' if self.send(step + '_valid?')
|
109
|
+
return 'ready_for_action' if self.send(step + '_available?')
|
110
|
+
return 'not_available'
|
111
|
+
end
|
31
112
|
```
|
113
|
+
|
114
|
+
## Installation
|
115
|
+
|
116
|
+
gem install fact_checker
|
data/fact_checker.gemspec
CHANGED
@@ -20,11 +20,12 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
21
|
s.require_paths = ["lib"]
|
22
22
|
|
23
|
-
s.required_ruby_version
|
24
|
-
s.required_rubygems_version =
|
23
|
+
s.required_ruby_version = '>= 1.9.2'
|
24
|
+
s.required_rubygems_version = '>= 1.3.5'
|
25
25
|
|
26
26
|
s.add_development_dependency 'rake'
|
27
27
|
s.add_development_dependency 'bundler'
|
28
|
-
s.add_development_dependency 'rspec',
|
29
|
-
s.add_development_dependency 'simplecov'
|
28
|
+
s.add_development_dependency 'rspec', '~> 2.14'
|
29
|
+
s.add_development_dependency 'simplecov'
|
30
|
+
s.add_development_dependency 'debugger'
|
30
31
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module FactChecker
|
4
|
+
def self.included(klass)
|
5
|
+
klass.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def define_fact(arg, &block)
|
10
|
+
fail ArgumentError, 'block not supplied' unless block
|
11
|
+
|
12
|
+
name, dependencies = arg.is_a?(Hash) ? arg.to_a.flatten(1) : [arg, []]
|
13
|
+
|
14
|
+
(@fact_checker_facts ||= []) << name.to_sym
|
15
|
+
|
16
|
+
define_method(name) do
|
17
|
+
dependencies_satisfied = [*dependencies].all?{ |dep_name| send(dep_name).valid? }
|
18
|
+
Result.new(dependencies_satisfied, instance_eval(&block))
|
19
|
+
end
|
20
|
+
|
21
|
+
define_method("#{name}?") do
|
22
|
+
send(name).valid?
|
23
|
+
end
|
24
|
+
|
25
|
+
private name, "#{name}?" if name[0] == '_'
|
26
|
+
end
|
27
|
+
|
28
|
+
def facts
|
29
|
+
ancestors.reverse.map{ |klass| klass.instance_eval{ @fact_checker_facts } }.compact.flatten(1)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module FactChecker
|
4
|
+
class Result
|
5
|
+
def initialize(dependency_state, requirement_state)
|
6
|
+
@dependency_state = dependency_state
|
7
|
+
@requirement_state = requirement_state
|
8
|
+
end
|
9
|
+
|
10
|
+
def valid?
|
11
|
+
@dependency_state && @requirement_state
|
12
|
+
end
|
13
|
+
|
14
|
+
def available?
|
15
|
+
@dependency_state
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/fact_checker/version.rb
CHANGED
data/lib/fact_checker.rb
CHANGED
data/spec/mixin_spec.rb
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe FactChecker do
|
6
|
+
context "when included into a class" do
|
7
|
+
|
8
|
+
let(:klass) { Class.new { include FactChecker } }
|
9
|
+
|
10
|
+
describe ".facts" do
|
11
|
+
it "exists" do
|
12
|
+
expect(klass.superclass.methods).to_not include(:facts)
|
13
|
+
expect(klass.methods).to include(:facts)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "returns [] when no facts were defined" do
|
17
|
+
expect(klass.facts).to eq([])
|
18
|
+
end
|
19
|
+
|
20
|
+
it "returns list of all defined facts names" do
|
21
|
+
klass.class_eval do
|
22
|
+
define_fact(:x) {}
|
23
|
+
define_fact(:y => [:x, :x1]) {}
|
24
|
+
end
|
25
|
+
|
26
|
+
expect(klass.facts).to eq([:x, :y])
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns independent results for independent classes" do
|
30
|
+
klass1 = Class.new { include FactChecker; define_fact(:x) {} }
|
31
|
+
klass2 = Class.new { include FactChecker; define_fact(:y) {} }
|
32
|
+
expect(klass1.facts).to eq([:x])
|
33
|
+
expect(klass2.facts).to eq([:y])
|
34
|
+
end
|
35
|
+
|
36
|
+
it "returns one-way dependent results for superclass and subclasses" do
|
37
|
+
klass1 = Class.new { include FactChecker; define_fact(:x) {} }
|
38
|
+
klass2 = Class.new(klass1) { define_fact(:y) {} }
|
39
|
+
klass3 = Class.new(klass2) { define_fact(:z) {} }
|
40
|
+
klass1.class_eval { define_fact(:q) {} }
|
41
|
+
|
42
|
+
expect(klass1.facts).to eq([:x, :q])
|
43
|
+
expect(klass2.facts).to eq([:x, :q, :y])
|
44
|
+
expect(klass3.facts).to eq([:x, :q, :y, :z])
|
45
|
+
end
|
46
|
+
|
47
|
+
it "returns symbols even when facts were defined with string names" do
|
48
|
+
klass.define_fact('x') {}
|
49
|
+
expect(klass.facts).to eq([:x])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
describe ".define_fact" do
|
55
|
+
it "exists" do
|
56
|
+
expect(klass.superclass.methods).to_not include(:define_fact)
|
57
|
+
expect(klass.methods).to include(:define_fact)
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when called with no arguments" do
|
61
|
+
it "raises an ArgumentError" do
|
62
|
+
expect{klass.define_fact}.to raise_error(ArgumentError)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "when called with (:name)" do
|
67
|
+
it "raises an ArgumentError" do
|
68
|
+
expect{klass.define_fact(:x)}.to raise_error(ArgumentError)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "when called with (:name, &block)" do
|
73
|
+
it "behaves as if it was called with (:name => [], &block)" do
|
74
|
+
klass.define_fact(:x) { :result }
|
75
|
+
expect(klass.instance_methods).to include(:x)
|
76
|
+
expect(klass.instance_methods).to include(:x?)
|
77
|
+
expect(klass.new.x.valid?).to be(:result)
|
78
|
+
expect(klass.new.x.available?).to be(true)
|
79
|
+
expect(klass.new.x?).to be(:result)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "when called with (:name => :dependency_name, &block)" do
|
84
|
+
it "behaves as if it was called with (:name => [:dependency_name], &block)" do
|
85
|
+
klass.define_fact(:y) { false }
|
86
|
+
klass.define_fact(:x => :y) { :result }
|
87
|
+
expect(klass.instance_methods).to include(:x)
|
88
|
+
expect(klass.instance_methods).to include(:x?)
|
89
|
+
expect(klass.new.x.valid?).to be(false)
|
90
|
+
expect(klass.new.x.available?).to be(false)
|
91
|
+
expect(klass.new.x?).to be(false)
|
92
|
+
|
93
|
+
klass.define_fact(:y) { true }
|
94
|
+
klass.define_fact(:x => :y) { :result }
|
95
|
+
expect(klass.new.x.valid?).to be(:result)
|
96
|
+
expect(klass.new.x.available?).to be(true)
|
97
|
+
expect(klass.new.x?).to be(:result)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "when called with (:name => dependency_names, &block)" do
|
102
|
+
it "defines an instance method #name" do
|
103
|
+
expect(klass.instance_methods).to_not include(:x)
|
104
|
+
klass.define_fact(:x => []) {}
|
105
|
+
expect(klass.instance_methods).to include(:x)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "defines an instance method #name?" do
|
109
|
+
expect(klass.instance_methods).to_not include(:x?)
|
110
|
+
klass.define_fact(:x => []) {}
|
111
|
+
expect(klass.instance_methods).to include(:x?)
|
112
|
+
end
|
113
|
+
|
114
|
+
context "and the newly defined instance methods:" do
|
115
|
+
let(:instance) { klass.new }
|
116
|
+
|
117
|
+
describe "#<name>" do
|
118
|
+
it "returns an object of FactChecker::Result" do
|
119
|
+
klass.define_fact(:x => []) {}
|
120
|
+
expect(instance.x).to be_a_kind_of(FactChecker::Result)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "evaluates block in the context of the current instance" do
|
124
|
+
klass.define_fact(:x => []) { check? }
|
125
|
+
klass.class_eval { def check?; end }
|
126
|
+
expect(instance).to receive(:check?).and_call_original
|
127
|
+
instance.x
|
128
|
+
end
|
129
|
+
|
130
|
+
context "when the :name fact has blank dependencies" do
|
131
|
+
before(:each) { klass.define_fact(:x => []) {:result} }
|
132
|
+
|
133
|
+
it "returns result with .valid? == block.call" do
|
134
|
+
expect(instance.x.valid?).to be(:result)
|
135
|
+
end
|
136
|
+
|
137
|
+
it "returns result with .available? == true" do
|
138
|
+
expect(instance.x.available?).to be_true
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
context "when the :name fact has valid dependencies" do
|
143
|
+
before(:each) do
|
144
|
+
klass.class_eval do
|
145
|
+
define_fact(:x) { true }
|
146
|
+
define_fact(:y) { :truthy }
|
147
|
+
define_fact(:z => [:x, :y]) { :result }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
it "returns result with .valid? == block.call" do
|
152
|
+
expect(instance.z.valid?).to be(:result)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "returns result with .available? == true" do
|
156
|
+
expect(instance.z.available?).to be_true
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context "when the :name fact has at least 1 invalid dependency" do
|
161
|
+
before(:each) do
|
162
|
+
klass.class_eval do
|
163
|
+
define_fact(:x) { true }
|
164
|
+
define_fact(:y) { false }
|
165
|
+
define_fact(:z => [:x, :y]) { :something }
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
it "returns result with .valid? == false" do
|
170
|
+
expect(instance.z.valid?).to be(false)
|
171
|
+
end
|
172
|
+
|
173
|
+
it "returns result with .available? == false" do
|
174
|
+
expect(instance.z.available?).to be(false)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context "when the :name fact has dependencies inherited from a superclass" do
|
179
|
+
it "return correct results" do
|
180
|
+
klass1 = Class.new(klass)
|
181
|
+
klass.define_fact(:x) { true }
|
182
|
+
klass.define_fact(:y) { false }
|
183
|
+
klass1.define_fact(:z1 => [:x, :y]) { true }
|
184
|
+
klass1.define_fact(:z2 => [:x]) { true }
|
185
|
+
klass1.define_fact(:z3 => [:x]) { false }
|
186
|
+
instance = klass1.new
|
187
|
+
|
188
|
+
expect(instance.z1.valid?).to be(false)
|
189
|
+
expect(instance.z1.available?).to be(false)
|
190
|
+
expect(instance.z2.valid?).to be(true)
|
191
|
+
expect(instance.z3.available?).to be(true)
|
192
|
+
expect(instance.z3.valid?).to be(false)
|
193
|
+
expect(instance.z2.available?).to be(true)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe "#<name>?" do
|
199
|
+
it "delegates to name.valid?" do
|
200
|
+
klass.define_fact(:x) {}
|
201
|
+
result = double(:valid? => :yeah!)
|
202
|
+
expect(instance).to receive(:x).and_return(result)
|
203
|
+
expect(instance.x?).to be(:yeah!)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
context "when called with ('name' => string_deppendency_names, &block)" do
|
210
|
+
it "defines an instance method #name" do
|
211
|
+
expect(klass.instance_methods).to_not include(:x)
|
212
|
+
klass.define_fact('x' => []) {}
|
213
|
+
expect(klass.instance_methods).to include(:x)
|
214
|
+
end
|
215
|
+
|
216
|
+
it "defines an instance method #name?" do
|
217
|
+
expect(klass.instance_methods).to_not include(:x?)
|
218
|
+
klass.define_fact('x' => []) {}
|
219
|
+
expect(klass.instance_methods).to include(:x?)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
context "when called with (:_underscored_name => dependency_names, &block)" do
|
224
|
+
it "marks new #_underscored_name method as private" do
|
225
|
+
klass.define_fact(:_x) {}
|
226
|
+
expect{ klass.new._x }.to raise_error
|
227
|
+
end
|
228
|
+
|
229
|
+
it "marks new #_undersrored_name? method as private" do
|
230
|
+
klass.define_fact(:_x) {}
|
231
|
+
expect{ klass.new._x? }.to raise_error
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|