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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/Guardfile +4 -4
  3. data/README.md +177 -78
  4. data/config/metrics/flay.yml +1 -1
  5. data/config/metrics/roodi.yml +2 -2
  6. data/lib/policy.rb +52 -33
  7. data/lib/policy/base.rb +122 -0
  8. data/lib/policy/base/and.rb +38 -0
  9. data/lib/policy/base/negator.rb +52 -0
  10. data/lib/policy/base/node.rb +59 -0
  11. data/lib/policy/base/not.rb +42 -0
  12. data/lib/policy/base/or.rb +39 -0
  13. data/lib/policy/base/xor.rb +39 -0
  14. data/lib/policy/cli.rb +8 -3
  15. data/lib/policy/cli/attribute.rb +49 -0
  16. data/lib/policy/cli/locale.erb +1 -2
  17. data/lib/policy/cli/policy.erb +33 -6
  18. data/lib/policy/cli/spec.erb +31 -11
  19. data/lib/policy/follower.rb +54 -94
  20. data/lib/policy/follower/name_error.rb +53 -0
  21. data/lib/policy/follower/policies.rb +104 -0
  22. data/lib/policy/follower/violation_error.rb +60 -0
  23. data/lib/policy/version.rb +2 -2
  24. data/policy.gemspec +2 -3
  25. data/spec/support/composer.rb +28 -0
  26. data/spec/tests/lib/policy/base/and_spec.rb +62 -0
  27. data/spec/tests/lib/policy/base/negator_spec.rb +49 -0
  28. data/spec/tests/lib/policy/base/not_spec.rb +50 -0
  29. data/spec/tests/lib/policy/base/or_spec.rb +62 -0
  30. data/spec/tests/lib/policy/base/xor_spec.rb +73 -0
  31. data/spec/tests/lib/policy/base_spec.rb +123 -0
  32. data/spec/tests/lib/policy/cli/attribute_spec.rb +52 -0
  33. data/spec/tests/{policy → lib/policy}/cli_spec.rb +25 -24
  34. data/spec/tests/lib/policy/follower/name_error_spec.rb +51 -0
  35. data/spec/tests/lib/policy/follower/policies_spec.rb +156 -0
  36. data/spec/tests/lib/policy/follower/violation_error_spec.rb +60 -0
  37. data/spec/tests/lib/policy/follower_spec.rb +153 -0
  38. data/spec/tests/lib/policy_spec.rb +52 -0
  39. metadata +43 -44
  40. data/lib/policy/follower/followed_policies.rb +0 -45
  41. data/lib/policy/follower/followed_policy.rb +0 -104
  42. data/lib/policy/follower/names.rb +0 -29
  43. data/lib/policy/interface.rb +0 -48
  44. data/lib/policy/validations.rb +0 -28
  45. data/lib/policy/violation_error.rb +0 -52
  46. data/spec/features/follower_spec.rb +0 -95
  47. data/spec/tests/policy/follower/followed_policies_spec.rb +0 -87
  48. data/spec/tests/policy/follower/followed_policy_spec.rb +0 -117
  49. data/spec/tests/policy/follower/names_spec.rb +0 -19
  50. data/spec/tests/policy/follower_spec.rb +0 -220
  51. data/spec/tests/policy/interface_spec.rb +0 -83
  52. data/spec/tests/policy/validations_spec.rb +0 -13
  53. data/spec/tests/policy/violation_error_spec.rb +0 -75
  54. data/spec/tests/policy_spec.rb +0 -35
@@ -1,52 +0,0 @@
1
- # encoding: utf-8
2
-
3
- module Policy
4
-
5
- # An exception to be risen by {Policy::Interface#apply}
6
- class ViolationError < RuntimeError
7
- include Adamantium
8
-
9
- # @!attribute [r] policy
10
- # The violated policy object
11
- #
12
- # @return [Policy::Follower]
13
- attr_reader :policy
14
-
15
- # @!attribute [r] messages
16
- # The list of messages from the broken policy
17
- #
18
- # @return [Array<String>]
19
- attr_reader :messages
20
-
21
- # @!scope class
22
- # @!method new(policy)
23
- # Constructs an exception
24
- #
25
- # @param [Policy::Follower] policy
26
- # the violated policy object
27
- #
28
- # @return [Policy::ViolationError]
29
- def initialize(policy)
30
- @policy = policy.dup
31
- @messages = @policy.messages
32
- end
33
-
34
- # The human-readable description for the exception
35
- #
36
- # @return [String]
37
- def inspect
38
- "#<#{ self }: #{ message }>"
39
- end
40
-
41
- # The human-readable exception message
42
- #
43
- # @return [String]
44
- def message
45
- "#{ policy } violated: #{ messages }"
46
- end
47
-
48
- memoize :policy, :messages
49
-
50
- end # module Follower
51
-
52
- end # module Policy
@@ -1,95 +0,0 @@
1
- # encoding: utf-8
2
-
3
- # The integration test for policy follower
4
- describe Policy::Follower do
5
-
6
- before do
7
-
8
- Transaction = Class.new Struct.new(:sum)
9
-
10
- module Policies
11
-
12
- class Consistency < Policy.new(:debet, :credit)
13
-
14
- validates :debet, :credit, presence: true
15
- validates :sum, numericality: { equal_to: 0 }, allow_nil: true
16
-
17
- private
18
-
19
- def sum
20
- debet && credit && (credit.sum + debet.sum)
21
- end
22
-
23
- end # class Consistency
24
-
25
- end # module Policy
26
-
27
- # The follower class to be tested
28
- class Transfer < Struct.new(:withdrawal, :enrollment)
29
- include Policy::Follower
30
-
31
- use_policies Policies do
32
- follow_policy :Consistency, :withdrawal, :enrollment, as: :consistency
33
- end
34
-
35
- end # class Transfer
36
-
37
- end # before
38
-
39
- let(:withdrawal) { Transaction.new(-100) }
40
- subject { Transfer.new withdrawal, enrollment }
41
-
42
- describe "#follow_policies!" do
43
-
44
- context "when policy is met" do
45
-
46
- let(:enrollment) { Transaction.new(100) }
47
-
48
- it "passes if the policy is met" do
49
- expect { subject.follow_policies! }.not_to raise_error
50
- end
51
-
52
- end # context
53
-
54
- context "when policy is broken" do
55
-
56
- let(:enrollment) { Transaction.new(200) }
57
-
58
- it "fails if the policy is broken" do
59
- expect { subject.follow_policies! }.to raise_error
60
- end
61
-
62
- end # context
63
-
64
- end # describe #follow_policies!
65
-
66
- describe "#follow_policies?" do
67
-
68
- context "when policy is met" do
69
-
70
- let(:enrollment) { Transaction.new(100) }
71
-
72
- it "returns true if the policy is met" do
73
- expect(subject).to be_follow_policies
74
- end
75
-
76
- end # context
77
-
78
- context "when policy is broken" do
79
-
80
- let(:enrollment) { Transaction.new(200) }
81
-
82
- it "returns false if the policy is broken" do
83
- expect(subject).not_to be_follow_policies
84
- end
85
-
86
- end # context
87
-
88
- end # describe #follow_policies!
89
-
90
- after do
91
- %i(Transaction Transfer Policies)
92
- .each { |const| Object.__send__(:remove_const, const) }
93
- end
94
-
95
- end # describe Policy::Follower
@@ -1,87 +0,0 @@
1
- # encoding: utf-8
2
- require "ostruct"
3
-
4
- describe Policy::Follower::FollowedPolicies do
5
-
6
- it "is a hash" do
7
- expect(subject).to be_kind_of Hash
8
- end
9
-
10
- describe "#add" do
11
-
12
- let(:policy) { double :policy, name: :foo }
13
-
14
- it "registers a policy" do
15
- expect { subject.add policy }.to change { subject }.to(foo: policy)
16
- end
17
-
18
- end # describe #add
19
-
20
- describe "#apply_to" do
21
-
22
- let(:follower) { double }
23
-
24
- before do
25
- %i(first second third).each do |item|
26
- subject.add double(name: item, apply_to: nil)
27
- end
28
- end
29
-
30
- shared_examples "applying policies" do |applied_policies = nil|
31
-
32
- before { applied_policies ||= subject.keys }
33
- let(:skipped_policies) { subject.keys - applied_policies }
34
-
35
- it "[applies policies]" do
36
- policies = applied_policies.map(&subject.method(:[]))
37
-
38
- policies.each do |policy|
39
- expect(policy).to receive(:apply_to).with(follower).ordered
40
- end
41
- end
42
-
43
- it "[skips policies]" do
44
- policies = skipped_policies.map(&subject.method(:[]))
45
-
46
- policies.each do |policy|
47
- expect(policy).not_to receive(:apply_to)
48
- end
49
- end
50
-
51
- end # shared examples
52
-
53
- context "by default" do
54
-
55
- after { subject.apply_to follower }
56
-
57
- it_behaves_like "applying policies"
58
-
59
- end # context
60
-
61
- context "with a list of names" do
62
-
63
- after { subject.apply_to follower, :third, :first }
64
-
65
- it_behaves_like "applying policies", %i(third first)
66
-
67
- end # context
68
-
69
- context "with a repetitive names" do
70
-
71
- after { subject.apply_to follower, :third, :third, :third }
72
-
73
- it_behaves_like "applying policies", %i(third third third)
74
-
75
- end # context
76
-
77
- context "with an array of names" do
78
-
79
- after { subject.apply_to follower, %w(third first forth) }
80
-
81
- it_behaves_like "applying policies", %i(third first)
82
-
83
- end # context
84
-
85
- end # describe #apply_to
86
-
87
- end # describe Policy::Follower::FollowedPolicies
@@ -1,117 +0,0 @@
1
- # encoding: utf-8
2
- require "ostruct"
3
-
4
- describe Policy::Follower::FollowedPolicy do
5
-
6
- before { module Namespace; class Policy < ::Policy.new(:foo, :bar); end; end }
7
- after { Object.send :remove_const, :Namespace }
8
-
9
- let(:namespace) { Namespace }
10
- let(:policy_class) { Namespace::Policy }
11
-
12
- describe ".new" do
13
-
14
- shared_examples "refusing wrong number of attributes" do |*list|
15
-
16
- subject { described_class.new nil, policy_class, :policy, *list }
17
-
18
- it "raises ArgumentError" do
19
- expect { subject }.to raise_error(ArgumentError)
20
- end
21
-
22
- it "sets a proper message for the exception" do
23
- begin
24
- subject
25
- rescue => err
26
- expect(err.message).to eq(
27
- "#{ policy_class } requires 2 attribute(s)." \
28
- " #{ list } cannot be assigned."
29
- )
30
- end
31
- end
32
-
33
- end # shared examples
34
-
35
- it_behaves_like "refusing wrong number of attributes", :foo
36
- it_behaves_like "refusing wrong number of attributes", :foo, :bar, :baz
37
-
38
- end # describe .new
39
-
40
- describe "#name" do
41
-
42
- context "when name is set to nil" do
43
-
44
- subject { described_class.new nil, policy_class, nil, :foo, :bar }
45
-
46
- it "assigns uuid" do
47
- expect(subject.name.to_s).to match(/^\h{8}-(\h{4}-){3}\h{12}$/)
48
- end
49
-
50
- it "is a symbol" do
51
- expect(subject.name).to be_kind_of Symbol
52
- end
53
-
54
- end # context
55
-
56
- context "when name is set explicitly" do
57
-
58
- subject { described_class.new nil, policy_class, "policy", :foo, :bar }
59
-
60
- it "returns the symbolized name" do
61
- expect(subject.name).to eq :policy
62
- end
63
-
64
- end # context
65
-
66
- end # describe #name
67
-
68
- describe "#policy" do
69
-
70
- let(:policy_name) { :Policy }
71
-
72
- shared_examples "policy object class" do
73
-
74
- it "returns the constant" do
75
- expect(subject.policy).to eq policy_class
76
- end
77
-
78
- end # context
79
-
80
- it_behaves_like "policy object class" do
81
- subject { described_class.new :foo, policy_class, :foo, :bar, :baz }
82
- end
83
-
84
- it_behaves_like "policy object class" do
85
- subject { described_class.new namespace, policy_name, :foo, :bar, :baz }
86
- end
87
-
88
- end # describe #policy
89
-
90
- describe "#attributes" do
91
-
92
- let(:attributes) { %i(foo bar) }
93
- subject { described_class.new nil, policy_class, :foo, *attributes }
94
-
95
- it "is set by the initializer" do
96
- expect(subject.attributes).to eq attributes
97
- end
98
-
99
- end # describe #attributes
100
-
101
- describe "#apply_to" do
102
-
103
- let(:attributes) { { foo: :bar, bar: :baz } }
104
- let(:follower) { OpenStruct.new(attributes) }
105
-
106
- subject do
107
- described_class.new nil, policy_class, nil, *attributes.keys
108
- end
109
-
110
- it "calls policy_class validation with follower attributes" do
111
- expect(policy_class).to receive(:apply).with(*attributes.values)
112
- subject.apply_to(follower)
113
- end
114
-
115
- end # describe #apply_to
116
-
117
- end # describe Policy::Follower::Policy
@@ -1,19 +0,0 @@
1
- # encoding: utf-8
2
-
3
- describe Policy::Follower::Names do
4
-
5
- describe ".from" do
6
-
7
- shared_examples "normalizing from" do |*source|
8
-
9
- subject { described_class.from(*source) }
10
- it { is_expected.to eq %i(foo bar baz foo) }
11
-
12
- end # shared examples
13
-
14
- it_behaves_like "normalizing from", %w(foo bar baz foo)
15
- it_behaves_like "normalizing from", *%w(foo bar baz foo)
16
-
17
- end # describe .from
18
-
19
- end # describe Policy::Follower::Names
@@ -1,220 +0,0 @@
1
- # encoding: utf-8
2
-
3
- describe Policy::Follower do
4
-
5
- before { Test = Class.new.__send__(:include, described_class) }
6
- after { Object.send :remove_const, :Test }
7
-
8
- let(:test_class) { Test }
9
- subject { test_class.new }
10
-
11
- it "includes Policy::Validations" do
12
- expect(test_class).to include Policy::Validations
13
- end
14
-
15
- describe ".followed_policies" do
16
-
17
- let(:followed_policies) { Policy::Follower::FollowedPolicies }
18
-
19
- it "returns the AppiedPolicies object" do
20
- expect(test_class.followed_policies).to be_kind_of followed_policies
21
- end
22
-
23
- end # describe .followed_policies
24
-
25
- describe ".follow_policy" do
26
-
27
- let(:followed_policy) { Policy::Follower::FollowedPolicy }
28
-
29
- let(:policy) { double name: :foo }
30
- let(:policy_class) { double }
31
- let(:name) { double }
32
- let(:attributes) { [double, double] }
33
-
34
- before { allow(followed_policy).to receive(:new) { policy } }
35
-
36
- context "by default" do
37
-
38
- after { test_class.follow_policy policy_class, *attributes }
39
-
40
- it "creates new followed policy" do
41
- expect(followed_policy)
42
- .to receive(:new)
43
- .with(test_class, policy_class, nil, *attributes)
44
- end
45
-
46
- it "adds new policy to .followed_policies" do
47
- expect(test_class.followed_policies).to receive(:add).with(policy)
48
- end
49
-
50
- end # context
51
-
52
- context "as: name" do
53
-
54
- after { test_class.follow_policy(policy_class, *attributes, as: name) }
55
-
56
- it "creates named followed policy" do
57
- expect(followed_policy)
58
- .to receive(:new)
59
- .with(test_class, policy_class, name, *attributes)
60
- end
61
-
62
- end # context
63
-
64
- context "inside #use_policies" do
65
-
66
- let(:namespace) { double }
67
-
68
- subject do
69
- test_class.use_policies namespace do
70
- follow_policy :foo, as: :bar
71
- end
72
- end
73
-
74
- it "sends the namespace to followed policy" do
75
- expect(followed_policy)
76
- .to receive(:new).with(namespace, :foo, :bar)
77
- subject
78
- end
79
-
80
- it "doesn't change default namespace" do
81
- subject
82
- expect(followed_policy)
83
- .to receive(:new).with(test_class, :foo, :bar)
84
- test_class.follow_policy :foo, as: :bar
85
- end
86
-
87
- end # context
88
-
89
- end # describe .follow_policy
90
-
91
- describe "#follow_policies!" do
92
-
93
- context "without names" do
94
-
95
- after { subject.follow_policies! }
96
-
97
- it "applies .followed_policies to itself" do
98
- expect(test_class.followed_policies)
99
- .to receive(:apply_to).with(subject)
100
- end
101
-
102
- end # context
103
-
104
- context "with names" do
105
-
106
- let(:names) { %i(foo bar baz) }
107
- after { subject.follow_policies!(*names) }
108
-
109
- it "applies .followed_policies to itself with names" do
110
- expect(test_class.followed_policies)
111
- .to receive(:apply_to).with(subject, *names)
112
- end
113
-
114
- end # context
115
-
116
- context "when a ViolationError is raised" do
117
-
118
- let(:error) { Policy::ViolationError.allocate }
119
- let(:messages) { %w(foo bar baz) }
120
-
121
- before do
122
- allow(error).to receive(:messages) { messages }
123
- allow(test_class.followed_policies).to receive(:apply_to) { fail error }
124
- end
125
-
126
- it "populates messages with errors" do
127
- expect { subject.follow_policies! rescue nil }
128
- .to change { subject.__send__(:errors).messages }
129
- .to(base: messages)
130
- end
131
-
132
- it "re-raises the exception" do
133
- expect { subject.follow_policies! }.to raise_error(error)
134
- end
135
-
136
- end # context
137
-
138
- end # describe #follow_policies!
139
-
140
- describe "#follow_policies?" do
141
-
142
- before { allow(subject).to receive(:follow_policies!) }
143
-
144
- it "calls #follow_policies! without names" do
145
- expect(subject).to receive(:follow_policies!)
146
- subject.follow_policies?
147
- end
148
-
149
- it "calls #follow_policies! with names" do
150
- names = %i(foo bar baz)
151
-
152
- expect(subject).to receive(:follow_policies!).with(*names)
153
- subject.follow_policies?(*names)
154
- end
155
-
156
- context "wheh #follow_policies! doesn't raise an error" do
157
-
158
- before { allow(subject).to receive(:follow_policies!) { nil } }
159
-
160
- it "returns true" do
161
- expect(subject.follow_policies?).to eq true
162
- end
163
-
164
- end # context
165
-
166
- context "wheh #folow_policies! raises ViolationError" do
167
-
168
- let(:error) { Policy::ViolationError.allocate }
169
- before { allow(subject).to receive(:follow_policies!) { fail error } }
170
-
171
- it "returns false" do
172
- expect(subject.follow_policies?).to eq false
173
- end
174
-
175
- end # context
176
-
177
- context "wheh #follow_policies! raises RuntimeError" do
178
-
179
- let(:error) { StandardError.allocate }
180
- before { allow(subject).to receive(:follow_policies!) { fail error } }
181
-
182
- it "fails" do
183
- expect { subject.follow_policies? }.to raise_error(error)
184
- end
185
-
186
- end # context
187
-
188
- end # describe #follow_policies?
189
-
190
- describe "#follow_policy!" do
191
-
192
- before { allow(subject).to receive(:follow_policies!) }
193
-
194
- it "is an alias for #follow_policies!" do
195
- expect(subject).to receive(:follow_policies!).with :foo
196
- subject.follow_policy! :foo
197
- end
198
-
199
- it "requires an argument" do
200
- expect(subject.method(:follow_policy!).arity).to eq 1
201
- end
202
-
203
- end # describe #follow_policy!
204
-
205
- describe "#follow_policy?" do
206
-
207
- before { allow(subject).to receive(:follow_policies?) }
208
-
209
- it "is an alias for #follow_policies?" do
210
- expect(subject).to receive(:follow_policies?).with :foo
211
- subject.follow_policy? :foo
212
- end
213
-
214
- it "requires an argument" do
215
- expect(subject.method(:follow_policy?).arity).to eq 1
216
- end
217
-
218
- end # describe #follow_policy?
219
-
220
- end # describe Policy::Follower