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
data/lib/policy/cli/locale.erb
CHANGED
data/lib/policy/cli/policy.erb
CHANGED
@@ -16,18 +16,45 @@
|
|
16
16
|
|
17
17
|
<%= " " * tabs %># Implements the following policy (invariant):
|
18
18
|
<%= " " * tabs %>#
|
19
|
-
<%= " " * tabs %># @todo
|
19
|
+
<%= " " * tabs %># @todo Describe the policy
|
20
20
|
<%= " " * tabs %>#
|
21
21
|
<%= " " * tabs %># @example
|
22
|
-
<%= " " * tabs %># <%= policy.type %>.new(
|
22
|
+
<%= " " * tabs %># <%= project.type %>::<%= policy.type %>.new(
|
23
|
+
<% attributes.each do |item| -%>
|
24
|
+
<%= " " * tabs %># <%= item.name %>: @todo,
|
25
|
+
<% end -%>
|
26
|
+
<%= " " * tabs %># )
|
23
27
|
<%= " " * tabs %>#
|
28
|
+
<%= " " * tabs %>class <%= policy.const %>
|
29
|
+
<%= " " * tabs %> include Policy::Base
|
30
|
+
|
31
|
+
<%= " " * tabs %> # @!scope class
|
32
|
+
<%= " " * tabs %> # @!method new(attributes)
|
33
|
+
<%= " " * tabs %> # Creates the policy object with a list of attributes
|
34
|
+
<%= " " * tabs %> #
|
35
|
+
<%= " " * tabs %> # @param [Hash] attributes
|
36
|
+
<%= " " * tabs %> #
|
37
|
+
<%= " " * tabs %> # @return [<%= project.type %>::<%= policy.type %>]
|
38
|
+
<% if attributes.any? -%>
|
39
|
+
|
40
|
+
<%= " " * tabs %> # @private
|
41
|
+
<%= " " * tabs %> def initialize(<%= attributes.map { |item| "#{ item.name }:" }.join(", ") -%>)
|
24
42
|
<% attributes.each do |item| -%>
|
25
|
-
<%= " " * tabs
|
26
|
-
|
43
|
+
<%= " " * tabs %> @<%= item.name %> = <%= item.name %>
|
44
|
+
<% end -%>
|
45
|
+
<%= " " * tabs %> end
|
46
|
+
<% attributes.each do |item| -%>
|
47
|
+
|
48
|
+
<%= " " * tabs %> # # @!attribute [r] <%= item.name %>
|
49
|
+
<%= " " * tabs %> # # @todo Describe the attribute
|
50
|
+
<%= " " * tabs %> # #
|
51
|
+
<%= " " * tabs %> # # @return [<%= item.type %>]
|
52
|
+
<%= " " * tabs %> # attr_reader :<%= item.name %>
|
53
|
+
<% end -%>
|
27
54
|
<% end -%>
|
28
|
-
<%= " " * tabs %>class <%= policy.const %> < Hexx::Policy.new(<%= attributes.map { |item| ":#{ item }" }.join(", ") %>)
|
29
55
|
|
30
|
-
<%= " " * tabs %> # Define necessary validations using `validates`
|
56
|
+
<%= " " * tabs %> # Define necessary validations using either `validates`
|
57
|
+
<%= " " * tabs %> # or `validate` declarations.
|
31
58
|
|
32
59
|
<%= " " * tabs %>end # class <%= policy.const %>
|
33
60
|
<% policy.namespaces.reverse.each do |item| -%>
|
data/lib/policy/cli/spec.erb
CHANGED
@@ -7,24 +7,44 @@ describe <%= project.type %>::<%= policy.type %> do
|
|
7
7
|
|
8
8
|
# default attributes for a valid policy
|
9
9
|
<% attributes.each do |item| -%>
|
10
|
-
# let(:<%= item %>) { @todo }
|
10
|
+
# let(:<%= item.name %>) { @todo }
|
11
11
|
<% end -%>
|
12
12
|
|
13
|
-
subject
|
13
|
+
subject do
|
14
|
+
described_class.new(
|
15
|
+
<% attributes.each do |item| -%>
|
16
|
+
<%= item.name %>: <%= item.name %>,
|
17
|
+
<% end -%>
|
18
|
+
)
|
19
|
+
end
|
20
|
+
<% attributes.each do |item| -%>
|
14
21
|
|
15
|
-
|
22
|
+
describe "#<%= item.name %>" do
|
16
23
|
|
17
|
-
it
|
24
|
+
it "is initialized" do
|
25
|
+
expect(subject.<%= item.name %>).to eq <%= item.name %>
|
26
|
+
end
|
18
27
|
|
19
|
-
end #
|
20
|
-
<%
|
28
|
+
end # describe #<%= item.name %>
|
29
|
+
<% end -%>
|
21
30
|
|
22
|
-
|
31
|
+
describe "#valid?" do
|
23
32
|
|
24
|
-
|
25
|
-
it { is_expected.not_to be_valid }
|
33
|
+
context "when @todo: describe the context" do
|
26
34
|
|
27
|
-
|
28
|
-
|
35
|
+
it { is_expected.to be_valid }
|
36
|
+
|
37
|
+
end # context
|
38
|
+
<% attributes.each do |item| -%>
|
39
|
+
|
40
|
+
context "when the #<%= item.name %> is @todo" do
|
41
|
+
|
42
|
+
# before { allow(subject).to receive(:<%= item.name %>) { @todo } }
|
43
|
+
it { is_expected.to be_invalid }
|
44
|
+
|
45
|
+
end # context
|
46
|
+
<% end -%>
|
47
|
+
|
48
|
+
end # describe #valid?
|
29
49
|
|
30
50
|
end # describe <%= policy.type %>
|
data/lib/policy/follower.rb
CHANGED
@@ -2,140 +2,100 @@
|
|
2
2
|
|
3
3
|
module Policy
|
4
4
|
|
5
|
-
#
|
5
|
+
# Interface for the class that follows policies
|
6
6
|
module Follower
|
7
7
|
|
8
|
-
|
9
|
-
require_relative "follower/followed_policy"
|
10
|
-
require_relative "follower/followed_policies"
|
11
|
-
|
12
|
-
# Methods to be added to the class the module is included to
|
13
|
-
#
|
14
|
-
# @private
|
8
|
+
# Methods to be added to the follower class
|
15
9
|
module ClassMethods
|
16
10
|
|
17
|
-
#
|
18
|
-
# The collection of policies to be followed by instances of the class
|
19
|
-
#
|
20
|
-
# @return [Policy::Follower::FollowedPolicies]
|
21
|
-
#
|
22
|
-
# @private
|
23
|
-
def followed_policies
|
24
|
-
@followed_policies ||= FollowedPolicies.new
|
25
|
-
end
|
26
|
-
|
27
|
-
# Adds a policy to the list of {#followed_policies}
|
11
|
+
# Declares a policy to be followed
|
28
12
|
#
|
29
|
-
#
|
30
|
-
# the policy object klass
|
31
|
-
# @param [Array<#to_sym>] attributes
|
32
|
-
# the list of attributes of the instance the policy should be applied to
|
13
|
+
# Mutates the object by adding an item to its {#policies}.
|
33
14
|
#
|
34
|
-
# @
|
35
|
-
# the name for the policy to be used for selecting it
|
36
|
-
# in {#follow_policies!} and {#follow_policies?} methods
|
15
|
+
# @param [Symbol] name
|
37
16
|
#
|
38
|
-
# @return [
|
39
|
-
def
|
40
|
-
|
41
|
-
|
17
|
+
# @return [:follows_policy] the name of the method
|
18
|
+
def follows_policy(name)
|
19
|
+
policies.add name
|
20
|
+
|
21
|
+
:follows_policy
|
42
22
|
end
|
43
23
|
|
44
|
-
#
|
24
|
+
# Declares policies to be followed
|
45
25
|
#
|
46
|
-
#
|
47
|
-
# use_policies Policies::Finances do
|
48
|
-
# apply_policy :TransferConstistency, :debet, :credit
|
49
|
-
# end
|
26
|
+
# Mutates the object by adding items to its {#policies}.
|
50
27
|
#
|
51
|
-
# @param [
|
28
|
+
# @param [Symbol, Array<Symbol>] names
|
52
29
|
#
|
53
|
-
# @
|
54
|
-
|
55
|
-
|
56
|
-
def use_policies(namespace, &block)
|
57
|
-
@__policies__ = namespace
|
58
|
-
instance_eval(&block)
|
59
|
-
ensure
|
60
|
-
@__policies__ = nil
|
61
|
-
end
|
30
|
+
# @return [:follows_policies] the name of the method
|
31
|
+
def follows_policies(*names)
|
32
|
+
names.each(&method(:follows_policy))
|
62
33
|
|
63
|
-
|
64
|
-
|
65
|
-
def __policies__
|
66
|
-
@__policies__ ||= self
|
34
|
+
:follows_policies
|
67
35
|
end
|
68
36
|
|
69
|
-
|
37
|
+
# @!attribute [r] policies
|
38
|
+
# The collection of followed policies
|
39
|
+
#
|
40
|
+
# @return [Policy::Follower::Policies]
|
41
|
+
def policies
|
42
|
+
@policies ||= Policies.new(self)
|
43
|
+
end
|
70
44
|
|
71
|
-
# Checks whether an instance meets selected policies
|
72
|
-
#
|
73
|
-
# Mutates the object by adding new #errors
|
74
|
-
#
|
75
|
-
# @param [Array<#to_sym>] names
|
76
|
-
# the ordered list of names to select policies by
|
77
|
-
# when not names selected all policies will be applied
|
78
|
-
#
|
79
|
-
# @raise [Policy::ViolationError]
|
80
|
-
# unless all selected policies has been met
|
81
|
-
#
|
82
|
-
# @return [undefined]
|
83
|
-
def follow_policies!(*names)
|
84
|
-
followed_policies.apply_to self, *names
|
85
|
-
rescue ViolationError => error
|
86
|
-
collect_errors_from(error)
|
87
|
-
raise
|
88
45
|
end
|
89
46
|
|
90
|
-
#
|
47
|
+
# Checks whether an object follows all its policies or subset of policies
|
91
48
|
#
|
92
|
-
# @
|
93
|
-
#
|
49
|
+
# @overload follow_policies?()
|
50
|
+
# Checks whether an object follows all registered policies
|
94
51
|
#
|
95
|
-
# @
|
52
|
+
# @overload follow_policies?(names)
|
53
|
+
# Checks whether an object follows given policies
|
96
54
|
#
|
97
|
-
#
|
98
|
-
def follow_policy!(name)
|
99
|
-
follow_policies! name
|
100
|
-
end
|
101
|
-
|
102
|
-
# Safely checks whether an instance meets selected policies
|
55
|
+
# @param [#to_sym, Array<#to_sym>] names
|
103
56
|
#
|
104
|
-
#
|
57
|
+
# @raise [Policy::Follower::NameError]
|
58
|
+
# if the policy is not registered by name
|
105
59
|
#
|
106
|
-
# @
|
60
|
+
# @raise [NoMethodError]
|
61
|
+
# if the name not implemented as follower's instance method
|
62
|
+
# @raise [ViolationError]
|
63
|
+
# if the policy is violated by the follower
|
107
64
|
#
|
108
|
-
# @return [
|
65
|
+
# @return [true] if no exception being raised
|
109
66
|
def follow_policies?(*names)
|
110
|
-
|
67
|
+
__policies__
|
68
|
+
.subset(names) # raises NameError
|
69
|
+
.map(&method(:__send__)) # raises NoMethodError
|
70
|
+
.each(&method(:__validate_policy__)) # raises ViolationError
|
111
71
|
true
|
112
|
-
rescue ViolationError
|
113
|
-
false
|
114
72
|
end
|
115
73
|
|
116
|
-
#
|
74
|
+
# Checks whether an object follows given policy
|
117
75
|
#
|
118
|
-
# @param
|
76
|
+
# @param [#to_sym] name
|
77
|
+
# the name of the policy to follow
|
78
|
+
#
|
79
|
+
# @raise (see #follow_policies?)
|
119
80
|
#
|
120
81
|
# @return (see #follow_policies?)
|
121
82
|
def follow_policy?(name)
|
122
83
|
follow_policies? name
|
123
84
|
end
|
124
85
|
|
125
|
-
private
|
126
|
-
|
127
|
-
# @!parse extend Policy::Follower::ClassMethods
|
128
|
-
# @!parse include ActiveModel::Validations
|
86
|
+
# @private
|
129
87
|
def self.included(klass)
|
130
|
-
klass.extend
|
88
|
+
klass.instance_eval { extend ClassMethods }
|
131
89
|
end
|
132
90
|
|
133
|
-
|
134
|
-
|
91
|
+
private
|
92
|
+
|
93
|
+
def __validate_policy__(policy)
|
94
|
+
fail ViolationError.new(self, policy) unless policy.valid?
|
135
95
|
end
|
136
96
|
|
137
|
-
def
|
138
|
-
|
97
|
+
def __policies__
|
98
|
+
self.class.policies
|
139
99
|
end
|
140
100
|
|
141
101
|
end # module Follower
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Policy
|
4
|
+
|
5
|
+
module Follower
|
6
|
+
|
7
|
+
# An exception to be risen when a policy name is not followed by a follower
|
8
|
+
class NameError < RuntimeError
|
9
|
+
|
10
|
+
# @!scope class
|
11
|
+
# @!method new(follower, name)
|
12
|
+
# Constructs an exception for the follower class and the policy name
|
13
|
+
#
|
14
|
+
# @param [Class] follower
|
15
|
+
# @param [#to_sym] name
|
16
|
+
#
|
17
|
+
# @return [Policy::Follower::NameError]
|
18
|
+
def initialize(follower, name)
|
19
|
+
@follower = follower
|
20
|
+
@name = name.to_sym
|
21
|
+
end
|
22
|
+
|
23
|
+
# @!attribute [r] follower
|
24
|
+
# The follower class that doesn't follow given policy
|
25
|
+
#
|
26
|
+
# @return [Class]
|
27
|
+
attr_reader :follower
|
28
|
+
|
29
|
+
# @!attribute [r] name
|
30
|
+
# The name of the policy that is not followed by the follower
|
31
|
+
#
|
32
|
+
# @return [String]
|
33
|
+
attr_reader :name
|
34
|
+
|
35
|
+
# The human-readable exception message
|
36
|
+
#
|
37
|
+
# @return [String]
|
38
|
+
def message
|
39
|
+
"#{ follower.inspect } hasn't registered the policy \"#{ name }\""
|
40
|
+
end
|
41
|
+
|
42
|
+
# The human-readable description for the exception
|
43
|
+
#
|
44
|
+
# @return [String]
|
45
|
+
def inspect
|
46
|
+
"#<#{ self }: #{ message }>"
|
47
|
+
end
|
48
|
+
|
49
|
+
end # class NameError
|
50
|
+
|
51
|
+
end # module Follower
|
52
|
+
|
53
|
+
end # module Policy
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Policy
|
4
|
+
|
5
|
+
module Follower
|
6
|
+
|
7
|
+
# Collection of policy names to be followed
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class Policies
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
# @!scope class
|
14
|
+
# @!method new(follower)
|
15
|
+
# Creates the empty collection for the follower class
|
16
|
+
#
|
17
|
+
# @param [Class] follower
|
18
|
+
#
|
19
|
+
# @return [Policy::Follower::Policies] the collection object
|
20
|
+
def initialize(follower)
|
21
|
+
@follower = follower
|
22
|
+
@names = []
|
23
|
+
end
|
24
|
+
|
25
|
+
# Adds the policy name to the list of followed policies
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# Policies.new.add("some_policy")
|
29
|
+
#
|
30
|
+
# @param [#to_sym] policy
|
31
|
+
#
|
32
|
+
# @return [self] itself
|
33
|
+
def add(policy)
|
34
|
+
name = policy.to_sym
|
35
|
+
names << name unless names.include? name
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
# Checks whether the name is included to the {#names}
|
40
|
+
#
|
41
|
+
# @param [Symbol] name
|
42
|
+
#
|
43
|
+
# @raise [Policy::Followers::NameError] if the name not registered yet
|
44
|
+
#
|
45
|
+
# @return [true]
|
46
|
+
def include?(name)
|
47
|
+
fail(NameError.new(follower, name)) unless names.include?(name)
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the subset of current collection
|
52
|
+
#
|
53
|
+
# @overload subset()
|
54
|
+
# @return [self] itself
|
55
|
+
#
|
56
|
+
# @overload subset(*names)
|
57
|
+
# @example
|
58
|
+
# policies = Policies.new(follower, %i(foo bar baz))
|
59
|
+
# policies.subset(:baz, :foo)
|
60
|
+
# # => #<Policies @names=[:baz, :foo]>
|
61
|
+
#
|
62
|
+
# @param [Symbol, Array<Symbol>] names
|
63
|
+
#
|
64
|
+
# @raise [Policy::Follower::NameError]
|
65
|
+
# if subset contains unregistered policies
|
66
|
+
#
|
67
|
+
# @return [Policy::Follower::Policies]
|
68
|
+
def subset(*names)
|
69
|
+
return self unless names.flatten.any?
|
70
|
+
policies = self.class.new(follower)
|
71
|
+
valid(names).each(&policies.method(:add))
|
72
|
+
|
73
|
+
policies
|
74
|
+
end
|
75
|
+
|
76
|
+
# Iterates through names of the policies
|
77
|
+
#
|
78
|
+
# @return [Enumerator]
|
79
|
+
#
|
80
|
+
# @yieldparam [Symbol] the name of the registered policy
|
81
|
+
def each
|
82
|
+
return to_enum unless block_given?
|
83
|
+
names.each { |name| yield(name) }
|
84
|
+
end
|
85
|
+
|
86
|
+
# @!attribute [r] follower
|
87
|
+
# The class that follows policies
|
88
|
+
#
|
89
|
+
# @return [Class]
|
90
|
+
attr_reader :follower
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
attr_reader :names
|
95
|
+
|
96
|
+
def valid(names)
|
97
|
+
names.flatten.map(&:to_sym).select(&method(:include?))
|
98
|
+
end
|
99
|
+
|
100
|
+
end # class Policies
|
101
|
+
|
102
|
+
end # module Follower
|
103
|
+
|
104
|
+
end # module Policy
|