fact_checker 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://secure.travis-ci.org/alexis/fact_checker.png?branch=master)](http://travis-ci.org/alexis/fact_checker)
|
2
|
+
[![Code Climate](https://codeclimate.com/github/alexis/fact_checker.png)](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
|
+
|