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
@@ -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
@@ -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 attributes of the policy (the order is essential).",
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(&:downcase)
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