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
@@ -8,7 +8,6 @@
8
8
  # models:
9
9
  # <%= project.path %>/<%= policy.path %>:
10
10
  # attributes:
11
- # base:
12
- <% attributes.each do |item| -%>
11
+ <% ["base"].concat(attributes.map(&:name)).sort.each do |item| -%>
13
12
  # <%= item %>:
14
13
  <% end -%>
@@ -16,18 +16,45 @@
16
16
 
17
17
  <%= " " * tabs %># Implements the following policy (invariant):
18
18
  <%= " " * tabs %>#
19
- <%= " " * tabs %># @todo: describe the policy
19
+ <%= " " * tabs %># @todo Describe the policy
20
20
  <%= " " * tabs %>#
21
21
  <%= " " * tabs %># @example
22
- <%= " " * tabs %># <%= policy.type %>.new(<%= attributes.join(", ") %>)
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 %># @param [@todo] <%= item %>
26
- <%= " " * tabs %># @todo: describe the attribute
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` or `validate` declarations.
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| -%>
@@ -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 { described_class.new <%= attributes.join(", ") %> }
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
- context "when @todo: describe the context" do
22
+ describe "#<%= item.name %>" do
16
23
 
17
- it { is_expected.to be_valid }
24
+ it "is initialized" do
25
+ expect(subject.<%= item.name %>).to eq <%= item.name %>
26
+ end
18
27
 
19
- end # context
20
- <% attributes.each do |item| -%>
28
+ end # describe #<%= item.name %>
29
+ <% end -%>
21
30
 
22
- context "when <%= item %> @todo" do
31
+ describe "#valid?" do
23
32
 
24
- # let(:<%= item %>) { @todo }
25
- it { is_expected.not_to be_valid }
33
+ context "when @todo: describe the context" do
26
34
 
27
- end # context
28
- <% end -%>
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 %>
@@ -2,140 +2,100 @@
2
2
 
3
3
  module Policy
4
4
 
5
- # Adds features for the object to follow external policies
5
+ # Interface for the class that follows policies
6
6
  module Follower
7
7
 
8
- require_relative "follower/names"
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
- # @!attribute [r] followed_policies
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
- # @param [Class] policy
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
- # @option [#to_sym] :as
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 [undefined]
39
- def follow_policy(policy, *attributes, as: nil)
40
- object = FollowedPolicy.new(__policies__, policy, as, *attributes)
41
- followed_policies.add object
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
- # Changes the namespace for applied policies
24
+ # Declares policies to be followed
45
25
  #
46
- # @example For Policies::Finances::TransferConsistency
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 [Module] namespace
28
+ # @param [Symbol, Array<Symbol>] names
52
29
  #
53
- # @yield the block in the current scope
54
- #
55
- # @return [undefined]
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
- private
64
-
65
- def __policies__
66
- @__policies__ ||= self
34
+ :follows_policies
67
35
  end
68
36
 
69
- end
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
- # Syntax shugar for the {#follow_policies!} with one argument
47
+ # Checks whether an object follows all its policies or subset of policies
91
48
  #
92
- # @param [#to_sym] name
93
- # the name of the policy to follow
49
+ # @overload follow_policies?()
50
+ # Checks whether an object follows all registered policies
94
51
  #
95
- # @raise (see #follow_policies!)
52
+ # @overload follow_policies?(names)
53
+ # Checks whether an object follows given policies
96
54
  #
97
- # @return [undefined]
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
- # Mutates the object by adding new #errors
57
+ # @raise [Policy::Follower::NameError]
58
+ # if the policy is not registered by name
105
59
  #
106
- # @param (see #follow_policies!)
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 [Boolean]
65
+ # @return [true] if no exception being raised
109
66
  def follow_policies?(*names)
110
- follow_policies!(*names)
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
- # Syntax shugar for the {#follow_policies?} with one argument
74
+ # Checks whether an object follows given policy
117
75
  #
118
- # @param (see #follow_policy!)
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(ClassMethods).__send__(:include, Validations)
88
+ klass.instance_eval { extend ClassMethods }
131
89
  end
132
90
 
133
- def followed_policies
134
- @followed_policies ||= self.class.followed_policies
91
+ private
92
+
93
+ def __validate_policy__(policy)
94
+ fail ViolationError.new(self, policy) unless policy.valid?
135
95
  end
136
96
 
137
- def collect_errors_from(exception)
138
- exception.messages.each { |text| errors.add :base, text }
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