policy 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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