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 +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
|