policy 1.2.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Guardfile +4 -4
- data/README.md +177 -78
- data/config/metrics/flay.yml +1 -1
- data/config/metrics/roodi.yml +2 -2
- data/lib/policy.rb +52 -33
- data/lib/policy/base.rb +122 -0
- data/lib/policy/base/and.rb +38 -0
- data/lib/policy/base/negator.rb +52 -0
- data/lib/policy/base/node.rb +59 -0
- data/lib/policy/base/not.rb +42 -0
- data/lib/policy/base/or.rb +39 -0
- data/lib/policy/base/xor.rb +39 -0
- data/lib/policy/cli.rb +8 -3
- data/lib/policy/cli/attribute.rb +49 -0
- data/lib/policy/cli/locale.erb +1 -2
- data/lib/policy/cli/policy.erb +33 -6
- data/lib/policy/cli/spec.erb +31 -11
- data/lib/policy/follower.rb +54 -94
- data/lib/policy/follower/name_error.rb +53 -0
- data/lib/policy/follower/policies.rb +104 -0
- data/lib/policy/follower/violation_error.rb +60 -0
- data/lib/policy/version.rb +2 -2
- data/policy.gemspec +2 -3
- data/spec/support/composer.rb +28 -0
- data/spec/tests/lib/policy/base/and_spec.rb +62 -0
- data/spec/tests/lib/policy/base/negator_spec.rb +49 -0
- data/spec/tests/lib/policy/base/not_spec.rb +50 -0
- data/spec/tests/lib/policy/base/or_spec.rb +62 -0
- data/spec/tests/lib/policy/base/xor_spec.rb +73 -0
- data/spec/tests/lib/policy/base_spec.rb +123 -0
- data/spec/tests/lib/policy/cli/attribute_spec.rb +52 -0
- data/spec/tests/{policy → lib/policy}/cli_spec.rb +25 -24
- data/spec/tests/lib/policy/follower/name_error_spec.rb +51 -0
- data/spec/tests/lib/policy/follower/policies_spec.rb +156 -0
- data/spec/tests/lib/policy/follower/violation_error_spec.rb +60 -0
- data/spec/tests/lib/policy/follower_spec.rb +153 -0
- data/spec/tests/lib/policy_spec.rb +52 -0
- metadata +43 -44
- data/lib/policy/follower/followed_policies.rb +0 -45
- data/lib/policy/follower/followed_policy.rb +0 -104
- data/lib/policy/follower/names.rb +0 -29
- data/lib/policy/interface.rb +0 -48
- data/lib/policy/validations.rb +0 -28
- data/lib/policy/violation_error.rb +0 -52
- data/spec/features/follower_spec.rb +0 -95
- data/spec/tests/policy/follower/followed_policies_spec.rb +0 -87
- data/spec/tests/policy/follower/followed_policy_spec.rb +0 -117
- data/spec/tests/policy/follower/names_spec.rb +0 -19
- data/spec/tests/policy/follower_spec.rb +0 -220
- data/spec/tests/policy/interface_spec.rb +0 -83
- data/spec/tests/policy/validations_spec.rb +0 -13
- data/spec/tests/policy/violation_error_spec.rb +0 -75
- data/spec/tests/policy_spec.rb +0 -35
@@ -0,0 +1,60 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Policy
|
4
|
+
|
5
|
+
module Follower
|
6
|
+
|
7
|
+
# An exception to be risen when a follower violates its policy
|
8
|
+
class ViolationError < RuntimeError
|
9
|
+
|
10
|
+
# @!scope class
|
11
|
+
# @!method new(follower, policy)
|
12
|
+
# Constructs an exception for the policy violated by the follower
|
13
|
+
#
|
14
|
+
# @param [Object] follower
|
15
|
+
# @param [#errors] policy
|
16
|
+
#
|
17
|
+
# @return [Policy::ViolationError]
|
18
|
+
def initialize(follower, policy)
|
19
|
+
@follower = follower
|
20
|
+
@policy = policy
|
21
|
+
end
|
22
|
+
|
23
|
+
# @!attribute [r] follower
|
24
|
+
# The follower object that causes the exception
|
25
|
+
#
|
26
|
+
# @return [Object]
|
27
|
+
attr_reader :follower
|
28
|
+
|
29
|
+
# @!attribute [r] policy
|
30
|
+
# The violated policy object
|
31
|
+
#
|
32
|
+
# @return [Policy::Base]
|
33
|
+
attr_reader :policy
|
34
|
+
|
35
|
+
# Returns the list of policy errors
|
36
|
+
#
|
37
|
+
# @return [ActiveModel::Errors]
|
38
|
+
def errors
|
39
|
+
policy.errors
|
40
|
+
end
|
41
|
+
|
42
|
+
# The human-readable exception message
|
43
|
+
#
|
44
|
+
# @return [String]
|
45
|
+
def message
|
46
|
+
"#{ follower.inspect } violates the policy #{ policy }"
|
47
|
+
end
|
48
|
+
|
49
|
+
# The human-readable description for the exception
|
50
|
+
#
|
51
|
+
# @return [String]
|
52
|
+
def inspect
|
53
|
+
"#<#{ self }: #{ message }>"
|
54
|
+
end
|
55
|
+
|
56
|
+
end # class ViolationError
|
57
|
+
|
58
|
+
end # module Follower
|
59
|
+
|
60
|
+
end # module Policy
|
data/lib/policy/version.rb
CHANGED
data/policy.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |gem|
|
|
9
9
|
gem.email = "andrew.kozin@gmail.com"
|
10
10
|
gem.homepage = "https://github.com/nepalez/policy"
|
11
11
|
gem.summary = "Policy Objects for Ruby."
|
12
|
-
gem.description = "A
|
12
|
+
gem.description = "A small library implementing the Policy Object pattern."
|
13
13
|
gem.license = "MIT"
|
14
14
|
|
15
15
|
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
@@ -20,8 +20,7 @@ Gem::Specification.new do |gem|
|
|
20
20
|
|
21
21
|
gem.required_ruby_version = "~> 2.0"
|
22
22
|
gem.add_runtime_dependency "activemodel", ">= 3.1"
|
23
|
-
gem.
|
24
|
-
gem.add_runtime_dependency "hexx-cli", "~> 0.0"
|
23
|
+
gem.add_development_dependency "hexx-cli", "~> 0.0"
|
25
24
|
gem.add_development_dependency "hexx-rspec", "~> 0.3"
|
26
25
|
|
27
26
|
end # Gem::Specification
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Mocks a valid policy object
|
4
|
+
#
|
5
|
+
# @return [Policy::Base::Node]
|
6
|
+
def valid_policy
|
7
|
+
policy = Policy::Base::Node.new
|
8
|
+
allow(policy).to receive(:valid?) { true }
|
9
|
+
allow(policy).to receive(:invalid?) { false }
|
10
|
+
policy
|
11
|
+
end
|
12
|
+
|
13
|
+
# Mocks an invalid policy object
|
14
|
+
#
|
15
|
+
# @return [Policy::Base::Node]
|
16
|
+
def invalid_policy(error: "error")
|
17
|
+
policy = Policy::Base::Node.new
|
18
|
+
|
19
|
+
add_errors = lambda do
|
20
|
+
policy.errors.clear
|
21
|
+
policy.errors.add(:base, error)
|
22
|
+
end
|
23
|
+
|
24
|
+
allow(policy).to receive(:valid?) { add_errors.call && false }
|
25
|
+
allow(policy).to receive(:invalid?) { add_errors.call && true }
|
26
|
+
|
27
|
+
policy
|
28
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe Policy::Base::And do
|
4
|
+
|
5
|
+
# defines mock builders #valid_policy and #invalid_policy
|
6
|
+
require "support/composer"
|
7
|
+
|
8
|
+
let(:one) { valid_policy }
|
9
|
+
let(:two) { valid_policy }
|
10
|
+
let(:three) { valid_policy }
|
11
|
+
|
12
|
+
subject { described_class.new one, two, three }
|
13
|
+
|
14
|
+
describe ".new" do
|
15
|
+
|
16
|
+
it "creates a Node" do
|
17
|
+
expect(subject).to be_kind_of Policy::Base::Node
|
18
|
+
end
|
19
|
+
|
20
|
+
end # new
|
21
|
+
|
22
|
+
describe "#policies" do
|
23
|
+
|
24
|
+
it "is initialized" do
|
25
|
+
expect(subject.policies).to contain_exactly one, two, three
|
26
|
+
end
|
27
|
+
|
28
|
+
end # describe #policies
|
29
|
+
|
30
|
+
describe "#valid?" do
|
31
|
+
|
32
|
+
context "when all the policies are valid" do
|
33
|
+
|
34
|
+
it "returns true" do
|
35
|
+
expect(subject).to be_valid
|
36
|
+
end
|
37
|
+
|
38
|
+
it "clears previous errors" do
|
39
|
+
subject.errors.add :base, :subject
|
40
|
+
expect { subject.valid? }.to change { subject.errors.blank? }.to true
|
41
|
+
end
|
42
|
+
|
43
|
+
end # context
|
44
|
+
|
45
|
+
context "when any policy is invalid" do
|
46
|
+
|
47
|
+
let(:three) { invalid_policy error: "three" }
|
48
|
+
let!(:result) { subject.valid? }
|
49
|
+
|
50
|
+
it "returns false" do
|
51
|
+
expect(result).to eq false
|
52
|
+
end
|
53
|
+
|
54
|
+
it "picks up error from invalid policy" do
|
55
|
+
expect(subject.errors.full_messages).to contain_exactly "three"
|
56
|
+
end
|
57
|
+
|
58
|
+
end # context
|
59
|
+
|
60
|
+
end # describe #valid?
|
61
|
+
|
62
|
+
end # describe Policy::Base::Not
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe Policy::Base::Negator do
|
4
|
+
|
5
|
+
let(:composer) { Policy::Base::Node }
|
6
|
+
let(:policy) { double :policy }
|
7
|
+
|
8
|
+
subject { described_class.new policy, composer }
|
9
|
+
|
10
|
+
describe "#policy" do
|
11
|
+
|
12
|
+
it "is initialized" do
|
13
|
+
expect(subject.policy).to eq policy
|
14
|
+
end
|
15
|
+
|
16
|
+
end # describe #policy
|
17
|
+
|
18
|
+
describe "#composer" do
|
19
|
+
|
20
|
+
it "is initialized" do
|
21
|
+
expect(subject.composer).to eq composer
|
22
|
+
end
|
23
|
+
|
24
|
+
end # describe #composer
|
25
|
+
|
26
|
+
describe "#not" do
|
27
|
+
|
28
|
+
let(:one) { double :one }
|
29
|
+
|
30
|
+
let(:result) { subject.not(one) }
|
31
|
+
|
32
|
+
it "creates a composer object" do
|
33
|
+
expect(result).to be_kind_of composer
|
34
|
+
end
|
35
|
+
|
36
|
+
it "sends the policy to the composer" do
|
37
|
+
expect(result.policies).to include policy
|
38
|
+
end
|
39
|
+
|
40
|
+
it "sends the negated arguments to the composer" do
|
41
|
+
not_one = double :not_one
|
42
|
+
expect(Policy::Base::Not).to receive(:new).with(one).and_return(not_one)
|
43
|
+
|
44
|
+
expect(result.policies).to include not_one
|
45
|
+
end
|
46
|
+
|
47
|
+
end # describe #not
|
48
|
+
|
49
|
+
end # describe Policy::Base::Negator
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe Policy::Base::Not do
|
4
|
+
|
5
|
+
# defines mock builders #valid_policy and #invalid_policy
|
6
|
+
require "support/composer"
|
7
|
+
|
8
|
+
let(:policy) { valid_policy }
|
9
|
+
subject { described_class.new policy }
|
10
|
+
|
11
|
+
it "is a Node" do
|
12
|
+
expect(subject).to be_kind_of Policy::Base::Node
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#policies" do
|
16
|
+
|
17
|
+
it "is initialized" do
|
18
|
+
expect(subject.policies).to eq [policy]
|
19
|
+
end
|
20
|
+
|
21
|
+
end # describe #policies
|
22
|
+
|
23
|
+
describe "#valid?" do
|
24
|
+
|
25
|
+
context "when the policy is valid" do
|
26
|
+
|
27
|
+
it "returns false" do
|
28
|
+
expect(subject).not_to be_valid
|
29
|
+
end
|
30
|
+
|
31
|
+
end # context
|
32
|
+
|
33
|
+
context "when the policy is invalid" do
|
34
|
+
|
35
|
+
let(:policy) { invalid_policy }
|
36
|
+
|
37
|
+
it "returns true" do
|
38
|
+
expect(subject).to be_valid
|
39
|
+
end
|
40
|
+
|
41
|
+
it "clears previous errors" do
|
42
|
+
subject.errors.add :base, :subject
|
43
|
+
expect { subject.valid? }.to change { subject.errors.blank? }.to true
|
44
|
+
end
|
45
|
+
|
46
|
+
end # context
|
47
|
+
|
48
|
+
end # describe #valid?
|
49
|
+
|
50
|
+
end # describe Policy::Base::Not
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe Policy::Base::Or do
|
4
|
+
|
5
|
+
# defines mock builders #valid_policy and #invalid_policy
|
6
|
+
require "support/composer"
|
7
|
+
|
8
|
+
let(:one) { invalid_policy error: "one" }
|
9
|
+
let(:two) { invalid_policy error: "two" }
|
10
|
+
let(:three) { valid_policy }
|
11
|
+
|
12
|
+
subject { described_class.new one, two, three }
|
13
|
+
|
14
|
+
describe ".new" do
|
15
|
+
|
16
|
+
it "creates a Node" do
|
17
|
+
expect(subject).to be_kind_of Policy::Base::Node
|
18
|
+
end
|
19
|
+
|
20
|
+
end # new
|
21
|
+
|
22
|
+
describe "#policies" do
|
23
|
+
|
24
|
+
it "is initialized" do
|
25
|
+
expect(subject.policies).to contain_exactly one, two, three
|
26
|
+
end
|
27
|
+
|
28
|
+
end # describe #policies
|
29
|
+
|
30
|
+
describe "#valid?" do
|
31
|
+
|
32
|
+
context "when any policy is valid" do
|
33
|
+
|
34
|
+
it "returns true" do
|
35
|
+
expect(subject).to be_valid
|
36
|
+
end
|
37
|
+
|
38
|
+
it "clears previous errors" do
|
39
|
+
subject.errors.add :base, :subject
|
40
|
+
expect { subject.valid? }.to change { subject.errors.blank? }.to true
|
41
|
+
end
|
42
|
+
|
43
|
+
end # context
|
44
|
+
|
45
|
+
context "when all policies are invalid" do
|
46
|
+
|
47
|
+
let(:three) { invalid_policy error: "three" }
|
48
|
+
let!(:result) { subject.valid? }
|
49
|
+
|
50
|
+
it "returns false" do
|
51
|
+
expect(result).to eq false
|
52
|
+
end
|
53
|
+
|
54
|
+
it "picks up errors from all policies" do
|
55
|
+
expect(subject.errors).to contain_exactly "one", "two", "three"
|
56
|
+
end
|
57
|
+
|
58
|
+
end # context
|
59
|
+
|
60
|
+
end # describe #valid?
|
61
|
+
|
62
|
+
end # describe Policy::Base::Not
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe Policy::Base::Xor do
|
4
|
+
|
5
|
+
# defines mock builders #valid_policy and #invalid_policy
|
6
|
+
require "support/composer"
|
7
|
+
|
8
|
+
let(:one) { invalid_policy error: "one" }
|
9
|
+
let(:two) { invalid_policy error: "two" }
|
10
|
+
let(:three) { valid_policy }
|
11
|
+
|
12
|
+
subject { described_class.new one, two, three }
|
13
|
+
|
14
|
+
describe ".new" do
|
15
|
+
|
16
|
+
it "creates a Node" do
|
17
|
+
expect(subject).to be_kind_of Policy::Base::Node
|
18
|
+
end
|
19
|
+
|
20
|
+
end # new
|
21
|
+
|
22
|
+
describe "#policies" do
|
23
|
+
|
24
|
+
it "is initialized" do
|
25
|
+
expect(subject.policies).to contain_exactly one, two, three
|
26
|
+
end
|
27
|
+
|
28
|
+
end # describe #policies
|
29
|
+
|
30
|
+
describe "#valid?" do
|
31
|
+
|
32
|
+
context "when both valid and invalid policies are present" do
|
33
|
+
|
34
|
+
it "returns true" do
|
35
|
+
expect(subject).to be_valid
|
36
|
+
end
|
37
|
+
|
38
|
+
it "clears previous errors" do
|
39
|
+
subject.errors.add :base, :subject
|
40
|
+
expect { subject.valid? }.to change { subject.errors.blank? }.to true
|
41
|
+
end
|
42
|
+
|
43
|
+
end # context
|
44
|
+
|
45
|
+
context "when all policies are valid" do
|
46
|
+
|
47
|
+
let(:one) { valid_policy }
|
48
|
+
let(:two) { valid_policy }
|
49
|
+
|
50
|
+
it "returns false" do
|
51
|
+
expect(subject).not_to be_valid
|
52
|
+
end
|
53
|
+
|
54
|
+
end # context
|
55
|
+
|
56
|
+
context "when all policies are invalid" do
|
57
|
+
|
58
|
+
let(:three) { invalid_policy error: "three" }
|
59
|
+
let!(:result) { subject.valid? }
|
60
|
+
|
61
|
+
it "returns false" do
|
62
|
+
expect(result).to eq false
|
63
|
+
end
|
64
|
+
|
65
|
+
it "picks up errors from all policies" do
|
66
|
+
expect(subject.errors).to contain_exactly "one", "two", "three"
|
67
|
+
end
|
68
|
+
|
69
|
+
end # context
|
70
|
+
|
71
|
+
end # describe #valid?
|
72
|
+
|
73
|
+
end # describe Policy::Base::Not
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe Policy::Base do
|
4
|
+
|
5
|
+
let(:test_class) { Class.new.send(:include, described_class) }
|
6
|
+
subject { test_class.new }
|
7
|
+
|
8
|
+
it "includes ActiveModel::Validations" do
|
9
|
+
expect(test_class).to include ActiveModel::Validations
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:one) { double :one }
|
13
|
+
let(:two) { double :two }
|
14
|
+
|
15
|
+
describe "#and" do
|
16
|
+
|
17
|
+
context "without policies" do
|
18
|
+
|
19
|
+
let(:result) { subject.and }
|
20
|
+
|
21
|
+
it "creates the negator object" do
|
22
|
+
expect(result).to be_kind_of Policy::Base::Negator
|
23
|
+
end
|
24
|
+
|
25
|
+
it "assigns self as a negator policy" do
|
26
|
+
expect(result.policy).to eq subject
|
27
|
+
end
|
28
|
+
|
29
|
+
it "assings And as a negator composer" do
|
30
|
+
expect(result.composer).to eq Policy::Base::And
|
31
|
+
end
|
32
|
+
|
33
|
+
end # context
|
34
|
+
|
35
|
+
context "with policies" do
|
36
|
+
|
37
|
+
let(:result) { subject.and(one, two) }
|
38
|
+
|
39
|
+
it "creates an And composition" do
|
40
|
+
expect(result).to be_kind_of Policy::Base::And
|
41
|
+
end
|
42
|
+
|
43
|
+
it "assigns given parts to the composition" do
|
44
|
+
expect(result.policies).to contain_exactly subject, one, two
|
45
|
+
end
|
46
|
+
|
47
|
+
end # context
|
48
|
+
|
49
|
+
end # describe #and
|
50
|
+
|
51
|
+
describe "#or" do
|
52
|
+
|
53
|
+
context "without policies" do
|
54
|
+
|
55
|
+
let(:result) { subject.or }
|
56
|
+
|
57
|
+
it "creates the negator object" do
|
58
|
+
expect(result).to be_kind_of Policy::Base::Negator
|
59
|
+
end
|
60
|
+
|
61
|
+
it "assigns self as a negator policy" do
|
62
|
+
expect(result.policy).to eq subject
|
63
|
+
end
|
64
|
+
|
65
|
+
it "assings Or as a negator composer" do
|
66
|
+
expect(result.composer).to eq Policy::Base::Or
|
67
|
+
end
|
68
|
+
|
69
|
+
end # context
|
70
|
+
|
71
|
+
context "with policies" do
|
72
|
+
|
73
|
+
let(:result) { subject.or(one, two) }
|
74
|
+
|
75
|
+
it "creates an Or composition" do
|
76
|
+
expect(result).to be_kind_of Policy::Base::Or
|
77
|
+
end
|
78
|
+
|
79
|
+
it "assigns given parts to the composition" do
|
80
|
+
expect(result.policies).to contain_exactly subject, one, two
|
81
|
+
end
|
82
|
+
|
83
|
+
end # context
|
84
|
+
|
85
|
+
end # describe #or
|
86
|
+
|
87
|
+
describe "#xor" do
|
88
|
+
|
89
|
+
context "without policies" do
|
90
|
+
|
91
|
+
let(:result) { subject.xor }
|
92
|
+
|
93
|
+
it "creates the negator object" do
|
94
|
+
expect(result).to be_kind_of Policy::Base::Negator
|
95
|
+
end
|
96
|
+
|
97
|
+
it "assigns self as a negator policy" do
|
98
|
+
expect(result.policy).to eq subject
|
99
|
+
end
|
100
|
+
|
101
|
+
it "assings Xor as a negator composer" do
|
102
|
+
expect(result.composer).to eq Policy::Base::Xor
|
103
|
+
end
|
104
|
+
|
105
|
+
end # context
|
106
|
+
|
107
|
+
context "with policies" do
|
108
|
+
|
109
|
+
let(:result) { subject.xor(one, two) }
|
110
|
+
|
111
|
+
it "creates an Xor composition" do
|
112
|
+
expect(result).to be_kind_of Policy::Base::Xor
|
113
|
+
end
|
114
|
+
|
115
|
+
it "assigns given parts to the composition" do
|
116
|
+
expect(result.policies).to contain_exactly subject, one, two
|
117
|
+
end
|
118
|
+
|
119
|
+
end # context
|
120
|
+
|
121
|
+
end # describe #xor
|
122
|
+
|
123
|
+
end # describe Policy::Base
|