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