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 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
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
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
- def_fact :good_job, if: ->(p) { p.job.good? }
16
- def_fact :good_family, if: ->(p) { p.family.good? }
17
- def_fact :is_healthy, if: ->(p) { p.health.good? }
18
- def_fact :is_happy => [:is_healthy, :good_family, :good_job], if: ->(p) { ! p.too_clever? }
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(:job => good_job, :family => good_family, :health => :good, :intellect => :too_clever)
24
- p.good_job? # => true
25
- p.good_family? # => true
26
- p.is_healthy? # => true
27
- p.fact_possible?(:is_happy) # => true (dependency satisfied)
28
- p.is_happy? # => false
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
- p.possible_facts - p.accomplished_facts # => [:happy]
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 = ">= 1.8.7"
24
- s.required_rubygems_version = ">= 1.3.5"
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', '~> 2.9.0'
29
- s.add_development_dependency 'simplecov', '~> 0.6.1'
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
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module FactChecker
4
- VERSION = '0.0.3'
4
+ VERSION = '0.1.0'
5
5
  end
data/lib/fact_checker.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'fact_checker/base'
4
3
  require 'fact_checker/version'
5
- require 'fact_checker/boot'
4
+ require 'fact_checker/result'
5
+ require 'fact_checker/mixin'
@@ -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
+