tram-policy 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8e014f25ceab7b1bdbea2a67a4ba98245986689c
4
- data.tar.gz: 0ed033ca9a5e8a695e78dc8b89e9d927a5373bcb
3
+ metadata.gz: 23e70750c9bc2efd4c816ae3e9dfb609ffe5d35f
4
+ data.tar.gz: 4ef00e68121a27bd67d58a883098a7732f4129f2
5
5
  SHA512:
6
- metadata.gz: 361c6c5af4c893f09d3595eb22ecea7098134c1ddc5ae2854ba9177a35f5a89ea3e6d27142e92f0e66d89b0aa8d17a7373671dc2fbcb5d51e22407a61b33d587
7
- data.tar.gz: a899980e7b03a31998d1e183026d9ad675aa865445f6c28392c3ee13bdd23de9e09b2e90f3cb0dd77f572931605e8fe436f372b71b086a4a7464ac4b6c91e6da
6
+ metadata.gz: ed5ab03576fcc938f987c3908b739f026508a6be32f0e8c6b14492054538fe4731a366bc4425875771784160a7e007eadb8fafdb73376ee470ea50760a881251
7
+ data.tar.gz: 320d14ef10562cd8a78e682a0d9989f9d9a3c15e60509862800ce7fc2fb185dc66d52345c19645e997d3ca34e670b4c91e8e3f6955f0e40e733d32e61b2d68f9
@@ -4,6 +4,37 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
+ ## [0.2.0] - [2017-08-19]
8
+
9
+ ### Added
10
+ - Support for unnamed block validators (@nepalez)
11
+
12
+ In addition to instance methods:
13
+
14
+ validate :some_method
15
+
16
+ You can use a block for validation:
17
+
18
+ validate { errors.add :blank_name if name.blank? }
19
+
20
+ - Support for custom scopes (@nepalez)
21
+
22
+ Just reload private class method `scope`
23
+
24
+ - Support for inheritance (@nepalez)
25
+
26
+ You can inherit the policy class. A subclass will apply all validators of
27
+ superclass before those of its own. Every error message will be translated
28
+ in the scope of policy where it was defined.
29
+
30
+ ### Deleted
31
+ - Reloading of validators
32
+
33
+ To follow Liskov substitube principle we run all validators declared anywhere
34
+ in the policy or its superclasses. Any sub-policy should provide the same
35
+ level of confidence about validity of object(s) under check as any
36
+ of its superclasses.
37
+
7
38
  ## [0.1.1] - [2017-08-04]
8
39
 
9
40
  ### Added
@@ -62,4 +93,5 @@ This is a first public release (@nepalez, @charlie-wasp, @JewelSam, @sergey-chec
62
93
  [0.0.2]: https://github.com/tram-rb/tram-policy/compare/v0.0.1...v0.0.2
63
94
  [0.0.3]: https://github.com/tram-rb/tram-policy/compare/v0.0.2...v0.0.3
64
95
  [0.1.0]: https://github.com/tram-rb/tram-policy/compare/v0.0.3...v0.1.0
65
- [0.1.1]: https://github.com/tram-rb/tram-policy/compare/v0.1.0...v0.1.1
96
+ [0.1.1]: https://github.com/tram-rb/tram-policy/compare/v0.1.0...v0.1.1
97
+ [0.1.2]: https://github.com/tram-rb/tram-policy/compare/v0.1.1...v0.2.0
data/README.md CHANGED
@@ -43,7 +43,7 @@ class Article::ReadinessPolicy < Tram::Policy
43
43
  # define what methods and in what order we should use to validate an article
44
44
  validate :title_presence
45
45
  validate :subtitle_presence
46
- validate :text_presence
46
+ validate { errors.add :empty, field: "text", level: "error" if text.empty? }
47
47
 
48
48
  private
49
49
 
@@ -59,13 +59,6 @@ class Article::ReadinessPolicy < Tram::Policy
59
59
  # Notice that we can set another level
60
60
  errors.add "Subtitle is empty", field: "subtitle", level: "warning"
61
61
  end
62
-
63
- def text_presence
64
- return unless text.empty?
65
- # Adds an error with a translated message. All fields are available
66
- # both as error tags, and I18n translation options
67
- errors.add :empty_text, field: "text", level: "error"
68
- end
69
62
  end
70
63
  ```
71
64
 
@@ -126,12 +119,11 @@ class Article::PublicationPolicy < Tram::Policy
126
119
 
127
120
  def article_readiness
128
121
  # Collects errors tagged by level: "error" from "nested" policy
129
- others = Article::ReadinessPolicy[article].errors.by_tags(level: "error")
122
+ readiness_errors = Article::ReadinessPolicy[article].errors.by_tags(level: "error")
130
123
 
131
124
  # Merges collected errors to the current ones.
132
- # New errors are tagged by source: "readiness".
133
- # Notice the block takes _hashified_ errors.
134
- errors.merge(others) { |hash| hash[:source] = "readiness" }
125
+ # New errors are also tagged by source: "readiness".
126
+ errors.merge(readiness_errors, source: "readiness")
135
127
  end
136
128
 
137
129
  def article_selection
@@ -140,12 +132,16 @@ class Article::PublicationPolicy < Tram::Policy
140
132
  end
141
133
  ```
142
134
 
143
- As mentioned above, sending a symbolic key to the `errors#add` means the key should be translated by [I18n][i18n]. The only magic under the hood is that a scope for the translation is taken from the full name of current class. All tags are available as options:
135
+ As mentioned above, sending a symbolic key to the `errors#add` means the key should be translated by [I18n][i18n]. The only magic under the hood concerns a scope for the translation. By default it is taken from the full name of current class prepended with `"evil.client.errors"`.
136
+
137
+ > You can redefine the scope by reloading private method `.scope` of the policy.
138
+
139
+ All tags are available as options:
144
140
 
145
141
  ```ruby
146
142
  class Article::PublicationPolicy < Tram::Policy
147
143
  # ...
148
- errors.add :empty_text, field: "text", level: "error"
144
+ errors.add :empty, field: "text", level: "error"
149
145
  # ...
150
146
  end
151
147
  ```
@@ -154,8 +150,9 @@ end
154
150
  # /config/locales/en.yml
155
151
  ---
156
152
  en:
157
- article/publication_policy:
158
- empty_text: "Validation %{level}: %{field} is empty"
153
+ tram-policy:
154
+ article/publication_policy:
155
+ empty: "Validation %{level}: %{field} is empty"
159
156
  ```
160
157
 
161
158
  This will provide error message "Validation error: text is empty".
@@ -194,14 +191,6 @@ class Article::ReadinessPolicy < Tram::Policy
194
191
  end
195
192
  ```
196
193
 
197
- ### `if`
198
-
199
- [WIP] Not implemented (coming soon in v0.1.0)
200
-
201
- ### `unless`
202
-
203
- [WIP] Not implemented (coming soon in v0.1.0)
204
-
205
194
  ## RSpec matchers
206
195
 
207
196
  RSpec matchers defined in a file `tram-policy/matcher` (not loaded in runtime).
@@ -297,9 +286,10 @@ end
297
286
  # config/tram-policies.en.yml
298
287
  ---
299
288
  en:
300
- user/readiness_policy:
301
- blank_name: translation missing
302
- blank_email: translation missing
289
+ tram-policy:
290
+ user/readiness_policy:
291
+ blank_name: translation missing
292
+ blank_email: translation missing
303
293
  ```
304
294
 
305
295
  ```ruby
@@ -16,10 +16,11 @@ module Tram
16
16
  # Registers a validator
17
17
  #
18
18
  # @param [#to_sym, Array<#to_sym>] names
19
+ # @option opts [Boolean] :stop_on_failure
19
20
  # @return [self]
20
21
  #
21
- def validate(name, **opts)
22
- validators[name.to_sym] = opts
22
+ def validate(name = nil, **opts, &block)
23
+ local << Validator.new(scope, name, block, opts)
23
24
  self
24
25
  end
25
26
 
@@ -34,13 +35,16 @@ module Tram
34
35
 
35
36
  private
36
37
 
37
- def validators
38
- @validators ||= {}
38
+ def scope
39
+ @scope ||= ["tram-policy", *Inflector.underscore(name)]
39
40
  end
40
41
 
41
- def inherited(klass)
42
- super
43
- validators.each { |name, opts| klass.validate name, opts }
42
+ def local
43
+ @local ||= []
44
+ end
45
+
46
+ def all
47
+ ((self == Tram::Policy) ? [] : superclass.send(:all)) + local
44
48
  end
45
49
  end
46
50
 
@@ -107,11 +111,10 @@ module Tram
107
111
 
108
112
  def initialize(*)
109
113
  super
110
- @__scope__ = ["tram-policy", Inflector.underscore(self.class.name)]
111
- self.class.send(:validators).each do |name, opts|
114
+ self.class.send(:all).each do |validator|
112
115
  size = errors.count
113
- send(name)
114
- break if (errors.count > size) && opts[:stop_on_failure]
116
+ validator.check(self)
117
+ break if (errors.count > size) && validator.stop_on_failure
115
118
  end
116
119
  end
117
120
  end
@@ -6,7 +6,7 @@ class Tram::Policy
6
6
  else
7
7
  module Inflector
8
8
  def self.underscore(name)
9
- name.dup.tap do |n|
9
+ name&.dup&.tap do |n|
10
10
  n.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
11
11
  n.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
12
12
  n.gsub!("::", "/")
@@ -16,7 +16,7 @@ class Tram::Policy
16
16
  end
17
17
 
18
18
  def self.camelize(name)
19
- name.dup.tap do |n|
19
+ name&.dup&.tap do |n|
20
20
  n.gsub!(/(?:\A|_+)(.)/) { $1.upcase }
21
21
  n.gsub!(%r{(?:[/|-]+)(.)}) { "::#{$1.upcase}" }
22
22
  end
@@ -1,16 +1,26 @@
1
1
  class Tram::Policy
2
2
  # Describes a validator
3
3
  class Validator
4
- attr_reader :name, :stop_on_failure
4
+ attr_reader :scope, :name, :block, :stop_on_failure
5
5
 
6
6
  def ==(other)
7
- other.is_a?(self.class) && other.name == name
7
+ other.is_a?(self.class) && name && other.name == name
8
+ end
9
+
10
+ def check(object)
11
+ object.__send__ :instance_variable_set, :@__scope__, scope
12
+ name ? object.__send__(name) : object.instance_exec(&block)
13
+ rescue
14
+ object.__send__ :instance_variable_set, :@__scope__, nil
8
15
  end
9
16
 
10
17
  private
11
18
 
12
- def initialize(name, stop_on_failure: false)
13
- @name = name.to_sym
19
+ def initialize(scope, name, block, stop_on_failure: false)
20
+ @scope = scope
21
+ @name = name&.to_sym
22
+ @block = block
23
+ raise "Provide either method name or a block" unless !name ^ !block
14
24
  @stop_on_failure = stop_on_failure
15
25
  end
16
26
  end
@@ -1,12 +1,5 @@
1
1
  class Test::CustomerPolicy < Tram::Policy
2
2
  option :name
3
3
 
4
- validate :name_presence
5
-
6
- private
7
-
8
- def name_presence
9
- return if name
10
- errors.add :name_presence, field: "name"
11
- end
4
+ validate { errors.add :name_presence, field: "name" unless name }
12
5
  end
@@ -19,16 +19,19 @@ RSpec.describe Tram::Policy do
19
19
 
20
20
  describe ".validate" do
21
21
  it "defines validators to be called by initializer in proper order" do
22
- expect(user).to receive(:name).once.ordered
23
- expect(user).to receive(:email).once.ordered
22
+ expect(user).to receive(:name).ordered
23
+ expect(user).to receive(:email).ordered
24
+ expect(user).to receive(:name).ordered
24
25
 
25
26
  Test::UserPolicy.new(user)
26
27
  end
27
28
 
28
29
  it "preserves order of parent class validators" do
29
- expect(user).to receive(:name).once.ordered
30
- expect(user).to receive(:email).once.ordered
31
- expect(user).to receive(:login).once.ordered
30
+ expect(user).to receive(:name).ordered
31
+ expect(user).to receive(:email).ordered
32
+ expect(user).to receive(:name).ordered
33
+ expect(user).to receive(:login).ordered
34
+ expect(user).to receive(:name).ordered
32
35
 
33
36
  Test::AdminPolicy.new(user)
34
37
  end
@@ -37,19 +40,17 @@ RSpec.describe Tram::Policy do
37
40
  before { Test::UserPolicy.validate :name, stop_on_failure: true }
38
41
 
39
42
  it "stops validation after failure" do
40
- expect(user).to receive(:name).once
41
- expect(user).not_to receive(:email)
43
+ expect(user).not_to receive(:login)
42
44
 
43
- Test::UserPolicy.new(user)
45
+ Test::AdminPolicy.new(user)
44
46
  end
45
47
 
46
48
  it "continues validation after success" do
47
49
  user = double :user, name: "Andy", email: nil, login: nil
48
50
 
49
- expect(user).to receive(:name).once.ordered
50
- expect(user).to receive(:email).once.ordered
51
+ expect(user).to receive(:login)
51
52
 
52
- Test::UserPolicy.new(user)
53
+ Test::AdminPolicy.new(user)
53
54
  end
54
55
  end
55
56
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = "tram-policy"
3
- gem.version = "0.1.1"
3
+ gem.version = "0.2.0"
4
4
  gem.author = ["Viktor Sokolov (gzigzigzeo)", "Andrew Kozin (nepalez)"]
5
5
  gem.email = ["andrew.kozin@gmail.com"]
6
6
  gem.homepage = "https://github.com/tram/tram-policy"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tram-policy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Viktor Sokolov (gzigzigzeo)
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-08-04 00:00:00.000000000 Z
12
+ date: 2017-08-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: dry-initializer