tram-policy 0.4.0 → 1.0.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: 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