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/base.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Policy
|
4
|
+
|
5
|
+
# Policy object class interface
|
6
|
+
#
|
7
|
+
# Includes <tt>ActiveModel::Validation</tt> and a list of methods
|
8
|
+
# to compose policies.
|
9
|
+
module Base
|
10
|
+
|
11
|
+
# Composes the policy with others by AND method
|
12
|
+
#
|
13
|
+
# @overload and(*others)
|
14
|
+
# Returns a composition of the policy with other policies
|
15
|
+
#
|
16
|
+
# The composition is valid when all policies are valid.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# composition = one_policy.and(another)
|
20
|
+
# one_policy.valid? # => true
|
21
|
+
# another.valid? # => false
|
22
|
+
# composition.valid? # => false
|
23
|
+
#
|
24
|
+
# @param [Policy::Base, Array<Policy::Base>] others
|
25
|
+
# other policies to compose the current policy with
|
26
|
+
#
|
27
|
+
# @return [Policy::Base] the composed policy
|
28
|
+
#
|
29
|
+
# @overload and()
|
30
|
+
# Returns a negator object expecting negation of another policy
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# composition = one_policy.and.not(another)
|
34
|
+
# one_policy.valid? # => true
|
35
|
+
# another.valid? # => false
|
36
|
+
# composition.valid? # => true
|
37
|
+
#
|
38
|
+
# @return [#not]
|
39
|
+
def and(*others)
|
40
|
+
compose And, others
|
41
|
+
end
|
42
|
+
|
43
|
+
# Composes the policy with others by OR method
|
44
|
+
#
|
45
|
+
# @overload or(*others)
|
46
|
+
# Returns a composition of the policy with other policies
|
47
|
+
#
|
48
|
+
# The composition is valid when any policy is valid.
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
# composition = one_policy.or(another)
|
52
|
+
# one_policy.valid? # => true
|
53
|
+
# another.valid? # => false
|
54
|
+
# composition.valid? # => true
|
55
|
+
#
|
56
|
+
# @param [Policy::Base, Array<Policy::Base>] others
|
57
|
+
# other policies to compose the current policy with
|
58
|
+
#
|
59
|
+
# @return [Policy::Base] the composed policy
|
60
|
+
#
|
61
|
+
# @overload or()
|
62
|
+
# Returns a negator object expecting negation of another policy
|
63
|
+
#
|
64
|
+
# @example
|
65
|
+
# composition = one_policy.or.not(another)
|
66
|
+
# one_policy.valid? # => false
|
67
|
+
# another.valid? # => false
|
68
|
+
# composition.valid? # => true
|
69
|
+
#
|
70
|
+
# @return [#not]
|
71
|
+
def or(*others)
|
72
|
+
compose Or, others
|
73
|
+
end
|
74
|
+
|
75
|
+
# Composes the policy with others by XOR method
|
76
|
+
#
|
77
|
+
# @overload xor(another)
|
78
|
+
# Returns a composition of the policy with other policies
|
79
|
+
#
|
80
|
+
# The composition is valid when both valid and invalid policies
|
81
|
+
# are present
|
82
|
+
#
|
83
|
+
# @example
|
84
|
+
# composition = one_policy.xor(another)
|
85
|
+
# one_policy.valid? # => true
|
86
|
+
# another.valid? # => true
|
87
|
+
# composition.valid? # => false
|
88
|
+
#
|
89
|
+
# @param [Policy::Base, Array<Policy::Base>] others
|
90
|
+
# other policies to compose the current policy with
|
91
|
+
#
|
92
|
+
# @return [Policy::Base] the composed policy
|
93
|
+
#
|
94
|
+
# @overload xor()
|
95
|
+
# Returns a negator object expecting negation of another policy
|
96
|
+
#
|
97
|
+
# @example
|
98
|
+
# composition = one_policy.xor.not(another)
|
99
|
+
# one_policy.valid? # => false
|
100
|
+
# another.valid? # => false
|
101
|
+
# composition.valid? # => true
|
102
|
+
#
|
103
|
+
# @return [#not]
|
104
|
+
def xor(*others)
|
105
|
+
compose Xor, others
|
106
|
+
end
|
107
|
+
|
108
|
+
# @private
|
109
|
+
def self.included(klass)
|
110
|
+
klass.instance_eval { include ActiveModel::Validations }
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def compose(composer, policies)
|
116
|
+
return composer.new(self, *policies) if policies.any?
|
117
|
+
Negator.new(self, composer)
|
118
|
+
end
|
119
|
+
|
120
|
+
end # module Base
|
121
|
+
|
122
|
+
end # module Policy
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Policy
|
4
|
+
|
5
|
+
module Base
|
6
|
+
|
7
|
+
# Composition of two policies
|
8
|
+
#
|
9
|
+
# The policy is valid if both its parts are valid.
|
10
|
+
# {#valid?} method picks errors from its parts in case any is invalid.
|
11
|
+
#
|
12
|
+
# @example (see #valid?)
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
class And < Node
|
16
|
+
|
17
|
+
# Checks whether every policy is valid
|
18
|
+
#
|
19
|
+
# Mutates the policy by adding {#errors} from its invalid part.
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# first.valid? # => true
|
23
|
+
# second.valid? # => false
|
24
|
+
#
|
25
|
+
# composition = Policy::Base::And.new(first, second)
|
26
|
+
# composition.valid? # => false
|
27
|
+
# composition.errors == second.errors # => true
|
28
|
+
#
|
29
|
+
# @return [Boolean]
|
30
|
+
def valid?
|
31
|
+
super { return true unless any_invalid? }
|
32
|
+
end
|
33
|
+
|
34
|
+
end # class And
|
35
|
+
|
36
|
+
end # module Base
|
37
|
+
|
38
|
+
end # module Policy
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Policy
|
4
|
+
|
5
|
+
module Base
|
6
|
+
|
7
|
+
# Composes a policy with an argument of its {#not} method
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class Negator
|
11
|
+
|
12
|
+
# @!scope class
|
13
|
+
# @!method new(policy, composer)
|
14
|
+
# Creates the negator object, expecting {#not} method call
|
15
|
+
#
|
16
|
+
# @param [Policy::Base] policy
|
17
|
+
# the policy to be composed with negations of other policies
|
18
|
+
# @param [Class] composer
|
19
|
+
# the composer for policies
|
20
|
+
#
|
21
|
+
# @return [Policy::Base::Negator]
|
22
|
+
def initialize(policy, composer)
|
23
|
+
@policy = policy
|
24
|
+
@composer = composer
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns a composition of the {#policy} with negations of other policies
|
28
|
+
#
|
29
|
+
# @param [Policy::Base, Array<Policy::Base>] policies
|
30
|
+
#
|
31
|
+
# @return [Policy::Base]
|
32
|
+
def not(*policies)
|
33
|
+
composer.new policy, *policies.flat_map(&Not.method(:new))
|
34
|
+
end
|
35
|
+
|
36
|
+
# @!attribute [r] policy
|
37
|
+
# The the policy to be composed with negations of other policies
|
38
|
+
#
|
39
|
+
# @return [Policy::Base]
|
40
|
+
attr_reader :policy
|
41
|
+
|
42
|
+
# @!attribute [r] composer
|
43
|
+
# The the composer for policies
|
44
|
+
#
|
45
|
+
# @return [Class]
|
46
|
+
attr_reader :composer
|
47
|
+
|
48
|
+
end # class Negator
|
49
|
+
|
50
|
+
end # module Base
|
51
|
+
|
52
|
+
end # module Policy
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Policy
|
4
|
+
|
5
|
+
module Base
|
6
|
+
|
7
|
+
# The base class for composite policies
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class Node
|
11
|
+
include Policy::Base
|
12
|
+
|
13
|
+
# @!scope class
|
14
|
+
# @!method new(*policies)
|
15
|
+
# Creates the node (composite policy) from its parts
|
16
|
+
#
|
17
|
+
# @param [<Policy::Base>, Array<Policy::Base>] policies
|
18
|
+
#
|
19
|
+
# @return [Policy::Base::Node]
|
20
|
+
def initialize(*policies)
|
21
|
+
@policies = policies
|
22
|
+
end
|
23
|
+
|
24
|
+
# @!attribute [r] policies
|
25
|
+
# The parts of the composite policy
|
26
|
+
#
|
27
|
+
# @return [Array<Policy::Base>]
|
28
|
+
attr_reader :policies
|
29
|
+
|
30
|
+
# @abstract
|
31
|
+
# Validates a composed policy and picks errors from its parts
|
32
|
+
#
|
33
|
+
# @return [false]
|
34
|
+
def valid?
|
35
|
+
errors.clear
|
36
|
+
yield # returns true if a composition valid
|
37
|
+
policies.each(&method(:pick_errors_from))
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def any_valid?
|
44
|
+
policies.detect(&:valid?)
|
45
|
+
end
|
46
|
+
|
47
|
+
def any_invalid?
|
48
|
+
policies.detect(&:invalid?)
|
49
|
+
end
|
50
|
+
|
51
|
+
def pick_errors_from(item)
|
52
|
+
item.errors.full_messages.each { |text| errors.add :base, text }
|
53
|
+
end
|
54
|
+
|
55
|
+
end # class Node
|
56
|
+
|
57
|
+
end # module Base
|
58
|
+
|
59
|
+
end # module Policy
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Policy
|
2
|
+
|
3
|
+
module Base
|
4
|
+
|
5
|
+
# Negation of given policy
|
6
|
+
#
|
7
|
+
# It is invalid when given policy is valid.
|
8
|
+
# Doesn't add any {#errors}.
|
9
|
+
#
|
10
|
+
# @example (see #valid?)
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
class Not < Node
|
14
|
+
|
15
|
+
# @!scope class
|
16
|
+
# @!method new(policy)
|
17
|
+
# Creates the policy negation
|
18
|
+
#
|
19
|
+
# @param [Array<Policy::Base>] policy
|
20
|
+
#
|
21
|
+
# @return [Policy::Base::Node]
|
22
|
+
def initialize(_)
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
# Checks whether negated policy is invalid
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# negation = Policy::Base::Not.new(some_policy)
|
30
|
+
# some_policy.valid? # => true
|
31
|
+
# negation.valid? # => false
|
32
|
+
#
|
33
|
+
# @return [Boolean]
|
34
|
+
def valid?
|
35
|
+
super { return true if any_invalid? }
|
36
|
+
end
|
37
|
+
|
38
|
+
end # class Not
|
39
|
+
|
40
|
+
end # module Base
|
41
|
+
|
42
|
+
end # module Policy
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Policy
|
4
|
+
|
5
|
+
module Base
|
6
|
+
|
7
|
+
# Composition of two policies
|
8
|
+
#
|
9
|
+
# The policy is valid if any of its parts is valid.
|
10
|
+
# {#valid?} method picks errors from its parts in case both are invalid.
|
11
|
+
#
|
12
|
+
# @example (see #valid?)
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
class Or < Node
|
16
|
+
|
17
|
+
# Checks whether any part of composition is valid
|
18
|
+
#
|
19
|
+
# Mutates the policy by adding {#errors} in case all parts are invalid.
|
20
|
+
# Doesn't add {#errors} if any policy is valid.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# first.valid? # => true
|
24
|
+
# second.valid? # => false
|
25
|
+
#
|
26
|
+
# composition = Policy::Base::Or.new(first, second)
|
27
|
+
# composition.valid? # => true
|
28
|
+
# composition.errors.empty? # => true
|
29
|
+
#
|
30
|
+
# @return [Boolean]
|
31
|
+
def valid?
|
32
|
+
super { return true if any_valid? }
|
33
|
+
end
|
34
|
+
|
35
|
+
end # class Or
|
36
|
+
|
37
|
+
end # module Base
|
38
|
+
|
39
|
+
end # module Policy
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Policy
|
4
|
+
|
5
|
+
module Base
|
6
|
+
|
7
|
+
# Composition of two policies
|
8
|
+
#
|
9
|
+
# The policy is valid if one of its parts is valid, while another is not.
|
10
|
+
# {#valid?} method picks errors from its parts in case both are invalid.
|
11
|
+
#
|
12
|
+
# @example (see #valid?)
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
class Xor < Node
|
16
|
+
|
17
|
+
# Checks if there is both valid and invalid parts are present
|
18
|
+
#
|
19
|
+
# Mutates the policy by adding {#errors} if all parts are invalid.
|
20
|
+
# Doesn't add {#errors} if any part is valid.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# first.valid? # => false
|
24
|
+
# second.valid? # => false
|
25
|
+
#
|
26
|
+
# composition = Policy::Base::Xor.new(first, second)
|
27
|
+
# composition.valid? # => false
|
28
|
+
# composition.errors.empty? # => false
|
29
|
+
#
|
30
|
+
# @return [Boolean]
|
31
|
+
def valid?
|
32
|
+
super { return true if any_valid? && any_invalid? }
|
33
|
+
end
|
34
|
+
|
35
|
+
end # class Xor
|
36
|
+
|
37
|
+
end # module Base
|
38
|
+
|
39
|
+
end # module Policy
|
data/lib/policy/cli.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
|
2
3
|
require "hexx-cli"
|
3
4
|
|
5
|
+
require_relative "cli/attribute"
|
6
|
+
|
4
7
|
module Policy
|
5
8
|
|
6
9
|
# Scaffolds a policy with a specification and translations
|
10
|
+
#
|
11
|
+
# @api private
|
7
12
|
class CLI < Hexx::CLI::Base
|
8
13
|
|
9
14
|
# @private
|
@@ -24,9 +29,9 @@ module Policy
|
|
24
29
|
class_option(
|
25
30
|
:attributes,
|
26
31
|
aliases: %w(-a),
|
27
|
-
banner: " debet credit",
|
32
|
+
banner: " debet{Transaction} credit{Transaction}",
|
28
33
|
default: %w(),
|
29
|
-
desc: "The list of
|
34
|
+
desc: "The list of policy object's attributes.",
|
30
35
|
type: :array
|
31
36
|
)
|
32
37
|
|
@@ -77,7 +82,7 @@ module Policy
|
|
77
82
|
end
|
78
83
|
|
79
84
|
def attributes
|
80
|
-
@attributes ||= options[:attributes].map(
|
85
|
+
@attributes ||= options[:attributes].map(&Attribute.method(:new))
|
81
86
|
end
|
82
87
|
|
83
88
|
def locales
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Policy
|
4
|
+
|
5
|
+
class CLI < Hexx::CLI::Base
|
6
|
+
|
7
|
+
# Decorates the string with #name and #type methods for described attribute
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class Attribute
|
11
|
+
|
12
|
+
# @!scope class
|
13
|
+
# @!method new(source)
|
14
|
+
# Creates the decorator
|
15
|
+
#
|
16
|
+
# @param [String] source
|
17
|
+
#
|
18
|
+
# @return [Policy::CLI::Attribute]
|
19
|
+
|
20
|
+
# @private
|
21
|
+
def initialize(source)
|
22
|
+
@source = source
|
23
|
+
end
|
24
|
+
|
25
|
+
# The name of the attribute
|
26
|
+
#
|
27
|
+
# @return [String]
|
28
|
+
def name
|
29
|
+
@type ||= begin
|
30
|
+
value = @source[/^\w+/]
|
31
|
+
value ? value.snake_case : "@todo"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# The type of the attribute
|
36
|
+
#
|
37
|
+
# @return [String]
|
38
|
+
def type
|
39
|
+
@type ||= begin
|
40
|
+
value = @source[/(?<=\{)(.+)(?=\})/]
|
41
|
+
value ? Hexx::CLI::Name.new(value).type : "@todo"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end # class Attribute
|
46
|
+
|
47
|
+
end # class CLI
|
48
|
+
|
49
|
+
end # module Policy
|