tram-policy 0.4.0 → 1.0.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: 7ffe8d541eb55eb7ba031f7578515523884d74a9
4
- data.tar.gz: b0e194a4dcd4682c79526a024b60f574b32014ba
3
+ metadata.gz: 54f65bae74cb48be0cc7d481cb3abd8528f43749
4
+ data.tar.gz: 4a100e49ec40986bc4f8781304169e6ecd2d5baf
5
5
  SHA512:
6
- metadata.gz: 9413e5c3d0bf479d3022ce75bbbcd639fee2ffa6632883859419b724ff7cb9b04c792b4118837f854714611b2db1756ca3294ead1767fc9ab669c8c7e0b8806c
7
- data.tar.gz: 4be7c88f9c2e1bdb45c70c45c90367afe2b1a8616837ca121a07a769521c8e1c1e3bb29224678d2425a49473db4b6549f4d71b9d9ca0e3e8f2eb0d3715e19138
6
+ metadata.gz: 107d99cdd79742b7ae85b5b1f8783f5eb6114c864cbfb7732251c2a4542261effec81ec171d61dbc0da6787ca576efc581dae43f76f75ef4a16d2d7934edea9f
7
+ data.tar.gz: 3dc2b45735369a9097b525b1681ea8dee24ed84675bc8cfde67b1f87394647f345abedd45db4c12d3725d3cd56df8193e70b8c92e5ec62e9e1c14970097c6ee8
data/CHANGELOG.md CHANGED
@@ -4,6 +4,27 @@ 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
+ ## [1.0.0] - [2018-02-17]
8
+
9
+ ### Changed
10
+ - RSpec matchers does't use blocks any more (nepalez)
11
+
12
+ Instead of
13
+
14
+ ```ruby
15
+ expect { policy }.to be_invalid_at level: "error"
16
+ ```
17
+
18
+ use the simpler syntax
19
+
20
+ ```ruby
21
+ expect(policy).to be_invalid_at level: "error"
22
+ ```
23
+
24
+ ### Deleted
25
+ - Deprecated methods (nepalez)
26
+ - RSpec shared examples (nepalez)
27
+
7
28
  ## [0.4.0] - [2018-02-17]
8
29
 
9
30
  This is beta-release before the first stable version 1.0.0.
data/README.md CHANGED
@@ -285,123 +285,30 @@ require "tram/policy/rspec"
285
285
  ```ruby
286
286
  # spec/policies/user/readiness_policy_spec.rb
287
287
  RSpec.describe User::ReadinessPolicy do
288
- let(:user) { build :user } # <- expected a factory
289
-
290
288
  subject(:policy) { described_class[email: "user@example.com"] }
291
289
 
292
- it "is invalid with 'error' level" do
293
- expect { policy }.to be_invalid_at level: "error"
294
- end
290
+ let(:user) { build :user } # <- expected a factory
295
291
 
296
- it "is not invalid with 'info' level" do
297
- expect { policy }.not_to be_invalid_at level: "info"
298
- end
292
+ it { is_expected.to be_invalid }
293
+ it { is_expected.to be_invalid_at level: "error" }
294
+ it { is_expected.to be_valid_at level: "info" }
299
295
  end
300
296
  ```
301
297
 
302
- **Notice** that you have to wrap policy into block `{ policy }`. This is because the matcher checks not only the presence of an error, but also ensures its message is translated to all available locales (`I18n.available_locales`). The block containing a policy will be executed separately for every such language.
298
+ The matcher checks not only the presence of an error, but also ensures that you provided translation of any message to any available locale (`I18n.available_locales`).
303
299
 
304
300
  ## Generators
305
301
 
306
- The gem provides simple tool for scaffolding new policy along with RSpec test template.
302
+ The gem provides simple tool for scaffolding new policy along with its RSpec test template and translations.
307
303
 
308
304
  ```shell
309
305
  $ tram-policy user/readiness_policy -p user -o admin -v name_present:blank_name email_present:blank_email
310
306
  ```
311
307
 
312
- This will generate a policy class with specification compatible to both [RSpec][rspec] and [FactoryGirl][factory-girl]:
313
-
314
-
315
- ```ruby
316
- # app/policies/user/readiness_policy.rb
317
-
318
- # TODO: describe the policy, its subject and context
319
- class User::ReadinessPolicy < Tram::Policy
320
- # TODO: add default values (default: -> { ... }),
321
- # coercers (type: proc(&:to_s)),
322
- # and optional arguments (optional: true)
323
- # when necessary
324
- param :user
325
- option :admin
326
-
327
- validate :name_present
328
- validate :email_present
329
-
330
- private
331
-
332
- def name_present
333
- # TODO: define a condition
334
- return if true
335
- # TODO: add necessary tags
336
- errors.add :blank_name
337
- end
338
-
339
- def email_present
340
- # TODO: define a condition
341
- return if true
342
- # TODO: add necessary tags
343
- errors.add :blank_email
344
- end
345
- end
346
- ```
347
-
348
- ```yaml
349
- # config/tram-policies.en.yml
350
- ---
351
- en:
352
- tram-policy:
353
- user/readiness_policy:
354
- blank_name: translation missing
355
- blank_email: translation missing
356
- ```
357
-
358
- ```ruby
359
- # spec/policies/user/readiness_policy_spec.rb
360
- require "spec_helper"
361
- # TODO: move it to spec_helper
362
- require "tram/policy/rspec"
363
-
364
- RSpec.describe User::ReadinessPolicy, ".[]" do
365
- # TODO: either remove this line, or set another source for locales to check
366
- let(:available_locales) { I18n.available_locales }
367
- let(:user) { FactoryGirl.build :user }
368
-
369
- it "is valid with proper arguments" do
370
- expect { described_class[user] }.to be_valid
371
- end
372
-
373
- # TODO: check the description
374
- it "is invalid when not name_present" do
375
- # TODO: modify some arguments
376
- user = nil
377
- # TODO: add necessary tags to focus the condition
378
- expect { described_class[user] }.to be_invalid_at
379
- end
380
-
381
- # TODO: check the description
382
- it "is invalid when not email_present" do
383
- # TODO: modify some arguments
384
- user = nil
385
- # TODO: add necessary tags to focus the condition
386
- expect { described_class[user] }.to be_invalid_at
387
- end
388
- end
389
- ```
390
-
391
- Then you should go through all TODO-s and add necessary details.
392
-
393
- Later you can copy-paste examples to provide more edge case for testing your policies.
394
-
395
- Notice that RSpec matcher `be_invalid_at` checks at once:
396
-
397
- - that an error is added to the policy
398
- - that the error has given tags
399
- - that the error is translated to every available locale
400
-
401
- Its negation (`not_to be_invalid_at`) checks that no errors added with given tags.
402
- When called without tags, it checks that the policy is valid as a whole.
308
+ This will generate a policy class with specification compatible to both [RSpec][rspec] and [FactoryBot][factory_bot].
403
309
 
404
- Both matchers provide a full description for the essence of the failure.
310
+ Under the keys `-p` and `-o` define params and options of the policy.
311
+ Key `-v` should contain validation methods along with their error message keys.
405
312
 
406
313
  ## Installation
407
314
 
@@ -443,4 +350,4 @@ The gem is available as open source under the terms of the [MIT License](http://
443
350
  [dry-initializer]: http://dry-rb.org/gems/dry-initializer/
444
351
  [i18n]: https://github.com/svenfuchs/i18n
445
352
  [rspec]: http://rspec.info/
446
- [factory-girl]: https://github.com/thoughtbot/factory_girl
353
+ [factory_bot]: https://github.com/thoughtbot/factory_bot
@@ -44,30 +44,6 @@ class Tram::Policy
44
44
  key.is_a?(Symbol) ? I18n.t(*item) : key.to_s
45
45
  end
46
46
 
47
- # @deprecated
48
- # Converts the error to a hash of message and tags
49
- #
50
- # @return [Hash<Symbol, Object>]
51
- #
52
- def to_h
53
- warn "[DEPRECATED] The method Tram::Policy::Error#to_h" \
54
- " will be removed in the v1.0.0.."
55
-
56
- tags.reject { |k| k == :scope }.merge(message: message)
57
- end
58
-
59
- # @deprecated
60
- # The full message (message and tags info)
61
- #
62
- # @return [String]
63
- #
64
- def full_message
65
- warn "[DEPRECATED] The method Tram::Policy::Error#full_message" \
66
- " will be removed in the v1.0.0."
67
-
68
- [message, tags].reject(&:empty?).join(" ")
69
- end
70
-
71
47
  # Fetches an option
72
48
  #
73
49
  # @param [#to_sym] tag
@@ -50,20 +50,6 @@ class Tram::Policy
50
50
  self.class.new(policy, list)
51
51
  end
52
52
 
53
- # @deprecated
54
- # @!method by_tags(tags)
55
- # Selects errors filtered by key and tags
56
- #
57
- # @param [Hash<Symbol, Object>] tags List of options to filter by
58
- # @return [Array<Tram::Policy::Error>]
59
- #
60
- def by_tags(**tags)
61
- warn "[DEPRECATED] The method Tram::Policy::Errors#by_tags" \
62
- " will be removed in the v1.0.0. Use method #filter instead."
63
-
64
- filter(tags).to_a
65
- end
66
-
67
53
  # @!method empty?
68
54
  # Checks whether a collection is empty
69
55
  #
@@ -89,18 +75,6 @@ class Tram::Policy
89
75
  @set.map(&:message).sort
90
76
  end
91
77
 
92
- # @deprecated
93
- # List of error descriptions
94
- #
95
- # @return [Array<String>]
96
- #
97
- def full_messages
98
- warn "[DEPRECATED] The method Tram::Policy::Errors#full_messages" \
99
- " will be removed in the v1.0.0."
100
-
101
- map(&:full_message)
102
- end
103
-
104
78
  # @!method merge(other, options)
105
79
  # Merges other collection to the current one and returns new collection
106
80
  # with the current scope
@@ -3,24 +3,22 @@ require "spec_helper"
3
3
  require "tram/policy/rspec"
4
4
 
5
5
  RSpec.describe <%= klass %>, ".[]" do
6
- # TODO: either remove this line, or set another source for available locales
7
- let(:available_locales) { I18n.available_locales }
6
+ subject(:policy) { described_class[<%= policy_signature %>] }
7
+
8
8
  <% (parsed_params + parsed_options).each do |name| -%>
9
9
  let(:<%= name %>) { FactoryGirl.build :<%= name %> }
10
10
  <% end -%>
11
11
 
12
- it "is valid with proper arguments" do
13
- expect { described_class[<%= policy_signature %>] }.not_to be_invalid_at
14
- end
12
+ it { is_expected.not_to be_invalid }
15
13
  <% parsed_validators.each do |v| %>
16
- # TODO: fix default description
17
- it "is invalid when <%= "not " if v[:key] == v[:name] %><%= v[:key] %>" do
14
+ # TODO: fix context description
15
+ context "when <%= "not " if v[:key] == v[:name] %><%= v[:key] %>" do
18
16
  # TODO: modify some arguments
19
17
  <% (parsed_params + parsed_options).each do |name| -%>
20
- <%= name %> = nil
18
+ let(:<%= name %>) { nil }
21
19
  <% end -%>
22
20
  # TODO: add necessary tags to focus the condition
23
- expect { described_class[<%= policy_signature %>] }.to be_invalid_at
21
+ it { is_expected.to be_invalid_at }
24
22
  end
25
23
  <% end -%>
26
24
  end
@@ -1,125 +1,76 @@
1
1
  require "rspec"
2
2
 
3
- # Checks that a block provides policy that has errors under given tags
4
- # It also check that selected messages has translations to all available locales
5
- #
6
- # @example
7
- # subject(:policy) { UserPolicy[name: nil] }
8
- # expect { policy }.to be_invalid_at field: "name", level: "error"
9
- #
10
- # You have to wrap expectation to a block called for available locales.
11
- #
12
3
  RSpec::Matchers.define :be_invalid_at do |**tags|
13
- supports_block_expectations
14
-
15
- # ****************************************************************************
16
- # Result collectors for all available locations
17
- # ****************************************************************************
18
-
19
- attr_accessor :policy
20
-
21
- def errors
22
- @errors ||= {}
4
+ def locales
5
+ @locales ||= I18n.available_locales
23
6
  end
24
7
 
25
- def tags
26
- @tags ||= {}
27
- end
28
-
29
- # ****************************************************************************
30
- # Helpers to provide results for all locales
31
- # ****************************************************************************
32
-
33
- def prepare_localized_results(policy_block, tags, locale)
34
- I18n.locale = locale
35
- local_policy = policy_block.call
36
- self.policy = local_policy.inspect
37
- errors[locale] = local_policy&.errors&.filter(tags)&.map do |error|
38
- { message: error.message, tags: error.options } # translate immediately
8
+ def check(policy, tags)
9
+ @errors ||= policy.errors.filter(tags).map do |error|
10
+ { item: error.item }.tap do |obj|
11
+ locales.each { |l| obj[l] = I18n.with_locale(l) { error.message } }
12
+ end
39
13
  end
40
14
  end
41
15
 
42
- def prepare_results(policy_block, tags)
43
- original = I18n.locale
44
- I18n.available_locales.each do |locale|
45
- prepare_localized_results(policy_block, tags, locale)
46
- end
47
- ensure
48
- I18n.locale = original
49
- end
50
-
51
- # ****************************************************************************
52
- # Checkers for collected results
53
- # ****************************************************************************
54
-
55
- # Checks if selected errors are present in all available locales
56
- def errored?
57
- errors.values.map(&:any?).reduce(true, &:&) == true
58
- end
59
-
60
- # Checks if selected errors are absent in all available locales
61
- def not_errored?
62
- errors.values.map(&:empty?).reduce(true, &:&) == true
63
- end
16
+ attr_reader :errors
64
17
 
65
- # Checks if all collected errors are translated
66
- def translated?
67
- texts = errors.values.flatten.map { |err| err[:message] }
68
- texts.select { |text| text.start_with?("translation missing") }.empty?
18
+ def missed_translations
19
+ @missed_translations ||= \
20
+ errors.flat_map { |rec| rec.values_at(*locales) }
21
+ .select { |message| message.start_with? "translation missing" }
69
22
  end
70
23
 
71
24
  def report_errors
72
- text = "Actual errors:\n"
73
- errors.each do |locale, local_errors|
74
- text << " #{locale}:\n"
75
- local_errors&.each { |err| text << " - #{err.values.join(" ")}\n" }
25
+ locales.each_with_object("Actual errors:\n") do |loc, text|
26
+ text << " #{loc}:\n"
27
+ errors.each { |err| text << " - #{err[loc]} #{err[:item]}\n" }
76
28
  end
77
- text
78
29
  end
79
30
 
80
- # ****************************************************************************
81
- # Positive matcher
82
- # ****************************************************************************
31
+ match do |policy|
32
+ check(policy, tags)
33
+ errors.any? && missed_translations.empty?
34
+ end
83
35
 
84
- match do |policy_block|
85
- prepare_results(policy_block, tags)
86
- errored? && translated?
36
+ match_when_negated do |policy|
37
+ check(policy, tags)
38
+ errors.empty?
87
39
  end
88
40
 
89
- failure_message do |_|
41
+ failure_message do |policy|
42
+ desc = tags.any? ? " with tags: #{tags}" : ""
90
43
  text = "The policy: #{policy}\n"
91
- text << "should have had errors with tags: #{tags}, "
92
- text << "whose messages are translated in all available locales.\n"
44
+ text << " should have had errors#{desc},"
45
+ text << " whose messages are translated in all available locales.\n"
93
46
  text << report_errors
94
47
  text
95
48
  end
96
49
 
97
- # ****************************************************************************
98
- # Negative matcher
99
- # ****************************************************************************
100
-
101
- match_when_negated do |policy_block|
102
- prepare_results(policy_block, tags)
103
- not_errored?
104
- end
105
-
106
- failure_message_when_negated do |_|
107
- text = "#{policy}\nshould not have had any error with tags: #{tags}.\n"
50
+ failure_message_when_negated do |policy|
51
+ desc = tags.any? ? " with tags: #{tags}" : ""
52
+ text = "#{policy}\nshould not have had any error#{desc}.\n"
108
53
  text << report_errors
109
54
  text
110
55
  end
111
56
  end
112
57
 
113
- RSpec.shared_examples :invalid_policy do |condition = nil, **tags|
114
- constraint = "with tags: #{tags}" if tags.any?
115
- it ["is invalid", condition, constraint].compact.join(" ") do
116
- expect { subject }.to be_invalid_at(tags)
58
+ RSpec::Matchers.define :be_invalid do
59
+ match do |policy|
60
+ expect(policy).to be_invalid_at
61
+ end
62
+
63
+ match_when_negated do |policy|
64
+ expect(policy).not_to be_invalid_at
117
65
  end
118
66
  end
119
67
 
120
- RSpec.shared_examples :valid_policy do |condition = nil, **tags|
121
- constraint = "with tags: #{tags}" if tags.any?
122
- it ["is valid", condition, constraint].compact.join(" ") do
123
- expect { subject }.not_to be_invalid_at(tags)
68
+ RSpec::Matchers.define :be_valid_at do |**tags|
69
+ match do |policy|
70
+ expect(policy).not_to be_invalid_at(tags)
71
+ end
72
+
73
+ match_when_negated do |policy|
74
+ expect(policy).to be_invalid_at(tags)
124
75
  end
125
76
  end
@@ -1,62 +1,49 @@
1
1
  RSpec.describe "RSpec support:" do
2
+ subject { Test::CustomerPolicy[name: nil] }
3
+
2
4
  before do
3
5
  I18n.available_locales = %i[en]
4
6
  I18n.backend.store_translations :en, yaml_fixture_file("en.yml")["en"]
5
7
  load_fixture "customer_policy.rb"
6
8
  end
7
9
 
8
- subject { Test::CustomerPolicy[name: nil] }
9
-
10
10
  describe "to be_invalid_at" do
11
11
  it "passes when some translated error present w/o tags constraint" do
12
- expect do
13
- expect { subject }.to be_invalid_at
14
- end.not_to raise_error
12
+ expect { expect(subject).to be_invalid_at }.not_to raise_error
15
13
  end
16
14
 
17
15
  it "passes when some translated error present under given tags" do
18
- expect do
19
- expect { subject }.to be_invalid_at field: "name"
20
- end.not_to raise_error
16
+ expect { expect(subject).to be_invalid_at field: "name" }
17
+ .not_to raise_error
21
18
  end
22
19
 
23
20
  it "fails when no errors present under given tags" do
24
- expect do
25
- expect { subject }.to be_invalid_at field: "email"
26
- end.to raise_error RSpec::Expectations::ExpectationNotMetError
21
+ expect { expect(subject).to be_invalid_at field: "email" }
22
+ .to raise_error RSpec::Expectations::ExpectationNotMetError
27
23
  end
28
24
 
29
25
  it "fails when some translations are absent" do
30
26
  I18n.available_locales = %i[ru en]
31
27
 
32
- expect do
33
- expect { subject }.to be_invalid_at field: "name"
34
- end.to raise_error RSpec::Expectations::ExpectationNotMetError
28
+ expect { expect(subject).to be_invalid_at field: "name" }
29
+ .to raise_error RSpec::Expectations::ExpectationNotMetError
35
30
  end
36
31
  end
37
32
 
38
33
  describe "not_to be_invalid_at" do
39
34
  it "passes when no errors present under given tags" do
40
- expect do
41
- expect { subject }.not_to be_invalid_at field: "email"
42
- end.not_to raise_error
35
+ expect { expect(subject).not_to be_invalid_at field: "email" }
36
+ .not_to raise_error
43
37
  end
44
38
 
45
39
  it "fails when some error present under given tags" do
46
- expect do
47
- expect { subject }.not_to be_invalid_at field: "name"
48
- end.to raise_error RSpec::Expectations::ExpectationNotMetError
40
+ expect { expect(subject).not_to be_invalid_at field: "name" }
41
+ .to raise_error RSpec::Expectations::ExpectationNotMetError
49
42
  end
50
43
 
51
44
  it "fails when some error present w/o tags constraint" do
52
- expect do
53
- expect { subject }.not_to be_invalid_at
54
- end.to raise_error RSpec::Expectations::ExpectationNotMetError
45
+ expect { expect(subject).not_to be_invalid_at }
46
+ .to raise_error RSpec::Expectations::ExpectationNotMetError
55
47
  end
56
48
  end
57
-
58
- describe "shared examples" do
59
- it_behaves_like :invalid_policy
60
- it_behaves_like :valid_policy, field: "email"
61
- end
62
49
  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.4.0"
3
+ gem.version = "1.0.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"
@@ -15,7 +15,7 @@ Gem::Specification.new do |gem|
15
15
  gem.required_ruby_version = ">= 2.3"
16
16
 
17
17
  gem.add_runtime_dependency "dry-initializer", "~> 2.0"
18
- gem.add_runtime_dependency "i18n", "~> 0.8"
18
+ gem.add_runtime_dependency "i18n", "~> 1.0"
19
19
 
20
20
  gem.add_development_dependency "rake", "> 10"
21
21
  gem.add_development_dependency "rspec", "~> 3.0"
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.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Viktor Sokolov (gzigzigzeo)
@@ -31,14 +31,14 @@ dependencies:
31
31
  requirements:
32
32
  - - "~>"
33
33
  - !ruby/object:Gem::Version
34
- version: '0.8'
34
+ version: '1.0'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: '0.8'
41
+ version: '1.0'
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: rake
44
44
  requirement: !ruby/object:Gem::Requirement