tram-policy 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -1
- data/README.md +17 -27
- data/lib/tram/policy.rb +14 -11
- data/lib/tram/policy/inflector.rb +2 -2
- data/lib/tram/policy/validator.rb +14 -4
- data/spec/fixtures/customer_policy.rb +1 -8
- data/spec/tram/policy_spec.rb +12 -11
- data/tram-policy.gemspec +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 23e70750c9bc2efd4c816ae3e9dfb609ffe5d35f
|
4
|
+
data.tar.gz: 4ef00e68121a27bd67d58a883098a7732f4129f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed5ab03576fcc938f987c3908b739f026508a6be32f0e8c6b14492054538fe4731a366bc4425875771784160a7e007eadb8fafdb73376ee470ea50760a881251
|
7
|
+
data.tar.gz: 320d14ef10562cd8a78e682a0d9989f9d9a3c15e60509862800ce7fc2fb185dc66d52345c19645e997d3ca34e670b4c91e8e3f6955f0e40e733d32e61b2d68f9
|
data/CHANGELOG.md
CHANGED
@@ -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 :
|
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
|
-
|
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
|
-
|
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
|
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 :
|
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
|
-
|
158
|
-
|
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
|
-
|
301
|
-
|
302
|
-
|
289
|
+
tram-policy:
|
290
|
+
user/readiness_policy:
|
291
|
+
blank_name: translation missing
|
292
|
+
blank_email: translation missing
|
303
293
|
```
|
304
294
|
|
305
295
|
```ruby
|
data/lib/tram/policy.rb
CHANGED
@@ -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
|
-
|
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
|
38
|
-
@
|
38
|
+
def scope
|
39
|
+
@scope ||= ["tram-policy", *Inflector.underscore(name)]
|
39
40
|
end
|
40
41
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
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
|
-
|
111
|
-
self.class.send(:validators).each do |name, opts|
|
114
|
+
self.class.send(:all).each do |validator|
|
112
115
|
size = errors.count
|
113
|
-
|
114
|
-
break if (errors.count > size) &&
|
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
|
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
|
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
|
-
@
|
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
|
data/spec/tram/policy_spec.rb
CHANGED
@@ -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).
|
23
|
-
expect(user).to receive(:email).
|
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).
|
30
|
-
expect(user).to receive(:email).
|
31
|
-
expect(user).to receive(:
|
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).
|
41
|
-
expect(user).not_to receive(:email)
|
43
|
+
expect(user).not_to receive(:login)
|
42
44
|
|
43
|
-
Test::
|
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(:
|
50
|
-
expect(user).to receive(:email).once.ordered
|
51
|
+
expect(user).to receive(:login)
|
51
52
|
|
52
|
-
Test::
|
53
|
+
Test::AdminPolicy.new(user)
|
53
54
|
end
|
54
55
|
end
|
55
56
|
end
|
data/tram-policy.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |gem|
|
2
2
|
gem.name = "tram-policy"
|
3
|
-
gem.version = "0.
|
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.
|
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-
|
12
|
+
date: 2017-08-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: dry-initializer
|