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