policy 1.2.0 → 2.0.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 +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
|