tram-policy 0.3.1 → 0.4.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/.travis.yml +2 -1
 - data/CHANGELOG.md +28 -1
 - data/README.md +107 -83
 - data/lib/tram/policy.rb +27 -2
 - data/lib/tram/policy/dsl.rb +22 -1
 - data/lib/tram/policy/error.rb +64 -21
 - data/lib/tram/policy/errors.rb +46 -18
 - data/lib/tram/policy/rspec.rb +5 -3
 - data/lib/tram/policy/validation_error.rb +1 -1
 - data/spec/fixtures/en.yml +2 -0
 - data/spec/spec_helper.rb +7 -3
 - data/spec/tram/policy/error_spec.rb +13 -26
 - data/spec/tram/policy/errors_spec.rb +34 -40
 - data/spec/tram/policy/rspec_spec.rb +1 -4
 - data/spec/tram/policy/validation_error_spec.rb +2 -2
 - 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: 7ffe8d541eb55eb7ba031f7578515523884d74a9
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: b0e194a4dcd4682c79526a024b60f574b32014ba
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 9413e5c3d0bf479d3022ce75bbbcd639fee2ffa6632883859419b724ff7cb9b04c792b4118837f854714611b2db1756ca3294ead1767fc9ab669c8c7e0b8806c
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 4be7c88f9c2e1bdb45c70c45c90367afe2b1a8616837ca121a07a769521c8e1c1e3bb29224678d2425a49473db4b6549f4d71b9d9ca0e3e8f2eb0d3715e19138
         
     | 
    
        data/.travis.yml
    CHANGED
    
    
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -4,6 +4,32 @@ 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.4.0] - [2018-02-17]
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            This is beta-release before the first stable version 1.0.0.
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            It adds methods `#item` and `#items` to policy errors to support lazy translation.
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            It also renames some methods, and deprecate others that will be removed from v1.0.0.
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            ### Added
         
     | 
| 
      
 16 
     | 
    
         
            +
            - `Tram::Policy.root_scope` changes the default root scope ("tram-policy") for I18n (nepalez)
         
     | 
| 
      
 17 
     | 
    
         
            +
            - `Tram::Policy::Error#item` returns an array of [key, tags] which can be sent to I18n.t later (nepalez)
         
     | 
| 
      
 18 
     | 
    
         
            +
            - `Tram::Policy::Error#to_a` as an alias for the `#item` (nepalez)
         
     | 
| 
      
 19 
     | 
    
         
            +
            - `Tram::Policy::Errors#items` returns an array of error items (nepalez)
         
     | 
| 
      
 20 
     | 
    
         
            +
            - `Tram::Policy::Errors#filter` acts like `by_tag` but returns the filtered collection instead of an array (nepalez)
         
     | 
| 
      
 21 
     | 
    
         
            +
            - `Tram::Policy#messages` as a shortcut for `errors.messages` (nepalez)
         
     | 
| 
      
 22 
     | 
    
         
            +
            - `Tram::Policy#items` as a shortcut for `errors.items` (nepalez)
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            ### Changed
         
     | 
| 
      
 25 
     | 
    
         
            +
            - errors are compared by `#to_a` instead of `#to_h` (nepalez)
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            ### Deprecated
         
     | 
| 
      
 28 
     | 
    
         
            +
            - `Tram::Policy::Error#full_message` (nepalez)
         
     | 
| 
      
 29 
     | 
    
         
            +
            - `Tram::Policy::Error#to_h` (nepalez)
         
     | 
| 
      
 30 
     | 
    
         
            +
            - `Tram::Policy::Errors#full_messages` (nepalez)
         
     | 
| 
      
 31 
     | 
    
         
            +
            - `Tram::Policy::Errors#by_tags` (nepalez)
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
       7 
33 
     | 
    
         
             
            ## [0.3.1] - [2018-01-05]
         
     | 
| 
       8 
34 
     | 
    
         | 
| 
       9 
35 
     | 
    
         
             
            ### Fixed
         
     | 
| 
         @@ -114,7 +140,7 @@ The gem is battle-tested for production (in a real commertial project). 
     | 
|
| 
       114 
140 
     | 
    
         | 
| 
       115 
141 
     | 
    
         
             
              Use a multiline version instead of `validate :foo, :bar`:
         
     | 
| 
       116 
142 
     | 
    
         | 
| 
       117 
     | 
    
         
            -
              ```
         
     | 
| 
      
 143 
     | 
    
         
            +
              ```ruby
         
     | 
| 
       118 
144 
     | 
    
         
             
              validate :foo
         
     | 
| 
       119 
145 
     | 
    
         
             
              validate :bar
         
     | 
| 
       120 
146 
     | 
    
         
             
              ```
         
     | 
| 
         @@ -143,3 +169,4 @@ This is a first public release (@nepalez, @charlie-wasp, @JewelSam, @sergey-chec 
     | 
|
| 
       143 
169 
     | 
    
         
             
            [0.2.5]: https://github.com/tram-rb/tram-policy/compare/v0.2.4...v0.2.5
         
     | 
| 
       144 
170 
     | 
    
         
             
            [0.3.0]: https://github.com/tram-rb/tram-policy/compare/v0.2.5...v0.3.0
         
     | 
| 
       145 
171 
     | 
    
         
             
            [0.3.1]: https://github.com/tram-rb/tram-policy/compare/v0.3.0...v0.3.1
         
     | 
| 
      
 172 
     | 
    
         
            +
            [0.4.0]: https://github.com/tram-rb/tram-policy/compare/v0.3.1...v0.4.0
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -14,12 +14,12 @@ Policy Object Pattern 
     | 
|
| 
       14 
14 
     | 
    
         | 
| 
       15 
15 
     | 
    
         
             
            Policy objects are responsible for context-related validation of objects, or mixes of objects. Here **context-related** means a validation doesn't check whether an object is valid by itself, but whether it is valid for some purpose (context). For example, we could ask if some article is ready (valid) to be published, etc.
         
     | 
| 
       16 
16 
     | 
    
         | 
| 
       17 
     | 
    
         
            -
            There are several well-known interfaces exist for validation like [ActiveModel::Validations][active-model-validation], or its [ActiveRecord][active-record-validation] extension  
     | 
| 
      
 17 
     | 
    
         
            +
            There are several well-known interfaces exist for validation like [ActiveModel::Validations][active-model-validation], or its [ActiveRecord][active-record-validation] extension for Rails, or PORO [Dry::Validation][dry-validation]. All of them focus on providing rich DSL-s for **validation rules**.
         
     | 
| 
       18 
18 
     | 
    
         | 
| 
       19 
19 
     | 
    
         
             
            **Tram::Policy** follows another approach -- it uses simple Ruby methods for validation, but focuses on building both *customizable* and *composable* results of validation, namely their errors.
         
     | 
| 
       20 
20 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
            - By **customizable** we mean adding any number of *tags* to  
     | 
| 
       22 
     | 
    
         
            -
            - By **composable** we mean a possibility to merge errors provided by one policy 
     | 
| 
      
 21 
     | 
    
         
            +
            - By **customizable** we mean adding any number of *tags* to errors -- to allow filtering and sorting validation results.
         
     | 
| 
      
 22 
     | 
    
         
            +
            - By **composable** we mean a possibility to merge errors provided by one policy into another, and build nested sets of well-focused policies.
         
     | 
| 
       23 
23 
     | 
    
         | 
| 
       24 
24 
     | 
    
         
             
            Keeping this reasons in mind, let's go to some examples.
         
     | 
| 
       25 
25 
     | 
    
         | 
| 
         @@ -51,22 +51,24 @@ class Article::ReadinessPolicy < Tram::Policy 
     | 
|
| 
       51 
51 
     | 
    
         | 
| 
       52 
52 
     | 
    
         
             
              def title_presence
         
     | 
| 
       53 
53 
     | 
    
         
             
                return unless title.empty?
         
     | 
| 
       54 
     | 
    
         
            -
                # Adds an error with a  
     | 
| 
      
 54 
     | 
    
         
            +
                # Adds an error with a unique key and a set of additional tags
         
     | 
| 
       55 
55 
     | 
    
         
             
                # You can use any tags, not only an attribute/field like in ActiveModel
         
     | 
| 
       56 
     | 
    
         
            -
                errors.add  
     | 
| 
      
 56 
     | 
    
         
            +
                errors.add :blank_title, field: "title", level: "error"
         
     | 
| 
       57 
57 
     | 
    
         
             
              end
         
     | 
| 
       58 
58 
     | 
    
         | 
| 
       59 
59 
     | 
    
         
             
              def subtitle_presence
         
     | 
| 
       60 
60 
     | 
    
         
             
                return unless subtitle.empty?
         
     | 
| 
       61 
61 
     | 
    
         
             
                # Notice that we can set another level
         
     | 
| 
       62 
     | 
    
         
            -
                errors.add  
     | 
| 
      
 62 
     | 
    
         
            +
                errors.add :blank_subtitle, field: "subtitle", level: "warning"
         
     | 
| 
       63 
63 
     | 
    
         
             
              end
         
     | 
| 
       64 
64 
     | 
    
         
             
            end
         
     | 
| 
       65 
65 
     | 
    
         
             
            ```
         
     | 
| 
       66 
66 
     | 
    
         | 
| 
       67 
     | 
    
         
            -
            Because validation is the only responsibility of a policy, we don't need to call it explicitly. 
     | 
| 
      
 67 
     | 
    
         
            +
            Because validation is the only responsibility of a policy, we don't need to call it explicitly.
         
     | 
| 
       68 
68 
     | 
    
         | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
      
 69 
     | 
    
         
            +
            Policy initializer will perform all the checks immediately, memoizing the results into `errors` array. The methods `#valid?`, `#invalid?` and `#validate!` just check those `#errors`.
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
            You should treat an instance immutable.
         
     | 
| 
       70 
72 
     | 
    
         | 
| 
       71 
73 
     | 
    
         
             
            ```ruby
         
     | 
| 
       72 
74 
     | 
    
         
             
            article = Article.new title: "A wonderful article", subtitle: "", text: ""
         
     | 
| 
         @@ -78,35 +80,117 @@ policy.valid?      # => false 
     | 
|
| 
       78 
80 
     | 
    
         
             
            policy.invalid?    # => true
         
     | 
| 
       79 
81 
     | 
    
         
             
            policy.validate!   # raises Tram::Policy::ValidationError
         
     | 
| 
       80 
82 
     | 
    
         | 
| 
       81 
     | 
    
         
            -
            #  
     | 
| 
      
 83 
     | 
    
         
            +
            # And errors
         
     | 
| 
       82 
84 
     | 
    
         
             
            policy.errors.count # => 2 (no subtitle, no text)
         
     | 
| 
       83 
85 
     | 
    
         
             
            policy.errors.filter { |error| error.tags[:level] == "error" }.count # => 1
         
     | 
| 
       84 
86 
     | 
    
         
             
            policy.errors.filter { |error| error.level == "error" }.count # => 1
         
     | 
| 
      
 87 
     | 
    
         
            +
            ```
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
            ## Validation Results
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
            Let look at those errors closer. We define 3 representation of errors:
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
            - error objects (`policy.errors`)
         
     | 
| 
      
 94 
     | 
    
         
            +
            - error items (`policy.items`, `policy.errors.items`, `policy.errors.map(&:item)`)
         
     | 
| 
      
 95 
     | 
    
         
            +
            - error messages (`policy.messages`, `policy.errors.messages`, `policy.errors.map(&:message)`)
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
            Errors by themselves are used for composition (see the next chapter), while `items` and `messages` represent errors for translation.
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
            The difference is the following.
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
            - The `messages` are translated immediately using the current locale.
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
            - The `items` postpone translation for later (for example, you can store them in a database and translate them to the locale of UI by demand).
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
            ### Items
         
     | 
| 
       85 
106 
     | 
    
         | 
| 
       86 
     | 
    
         
            -
             
     | 
| 
       87 
     | 
    
         
            -
            policy.errors.map(&:message) # => ["Subtitle is empty", "Error translation for missed text"]
         
     | 
| 
      
 107 
     | 
    
         
            +
            Error items contain arrays that could be send to I18n.t for translation. We add the default scope from the name of policy, preceeded by the `["tram-policy"]` root namespace.
         
     | 
| 
       88 
108 
     | 
    
         | 
| 
       89 
     | 
    
         
            -
             
     | 
| 
       90 
     | 
    
         
            -
            policy. 
     | 
| 
      
 109 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 110 
     | 
    
         
            +
            policy.items # or policy.errors.items, or policy.errors.map(&:item)
         
     | 
| 
      
 111 
     | 
    
         
            +
            # => [
         
     | 
| 
      
 112 
     | 
    
         
            +
            #      [
         
     | 
| 
      
 113 
     | 
    
         
            +
            #        :blank_title,
         
     | 
| 
      
 114 
     | 
    
         
            +
            #        {
         
     | 
| 
      
 115 
     | 
    
         
            +
            #          scope: ["tram-policy", "article/readiness_policy"]],
         
     | 
| 
      
 116 
     | 
    
         
            +
            #          field: "title",
         
     | 
| 
      
 117 
     | 
    
         
            +
            #          level: "error"
         
     | 
| 
      
 118 
     | 
    
         
            +
            #        }
         
     | 
| 
      
 119 
     | 
    
         
            +
            #      ],
         
     | 
| 
      
 120 
     | 
    
         
            +
            #      ...
         
     | 
| 
      
 121 
     | 
    
         
            +
            #    ]
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
            I18n.t(*policy.items.first)
         
     | 
| 
      
 124 
     | 
    
         
            +
            # => "translation missing: en.tram-policy.article/readiness_policy.blank_title"
         
     | 
| 
      
 125 
     | 
    
         
            +
            ```
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
            You can change the root scope if you will (this could be useful in libraries):
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 130 
     | 
    
         
            +
            class MyGemPolicy < Tram::Policy
         
     | 
| 
      
 131 
     | 
    
         
            +
              scope "mygem", "policies" # inherited by subclasses
         
     | 
| 
      
 132 
     | 
    
         
            +
            end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
            class Article::ReadinessPolicy < MyGemPolicy
         
     | 
| 
      
 135 
     | 
    
         
            +
              # ...
         
     | 
| 
      
 136 
     | 
    
         
            +
            end
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
            # ...
         
     | 
| 
      
 139 
     | 
    
         
            +
            I18n.t(*policy.items.first)
         
     | 
| 
      
 140 
     | 
    
         
            +
            # => "translation missing: en.mygem.policies.article/readiness_policy.blank_title"
         
     | 
| 
      
 141 
     | 
    
         
            +
            ```
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
            ### Messages
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
            Error messages contain translation of `policy.items` in the current locale:
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 148 
     | 
    
         
            +
            policy.messages # or policy.errors.messages, or policy.errors.map(&:message)
         
     | 
| 
      
 149 
     | 
    
         
            +
            # => [
         
     | 
| 
      
 150 
     | 
    
         
            +
            #      "translation missing: en.tram-policy.article/readiness_policy.blank_title",
         
     | 
| 
      
 151 
     | 
    
         
            +
            #      "translation missing: en.tram-policy.article/readiness_policy.blank_subtitle"
         
     | 
| 
      
 152 
     | 
    
         
            +
            #    ]
         
     | 
| 
      
 153 
     | 
    
         
            +
            ```
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
            The messages are translated if the keys are symbolic. Strings are treated as already translated:
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 158 
     | 
    
         
            +
            class Article::ReadinessPolicy < Tram::Policy
         
     | 
| 
      
 159 
     | 
    
         
            +
              # ...
         
     | 
| 
      
 160 
     | 
    
         
            +
              def title_presence
         
     | 
| 
      
 161 
     | 
    
         
            +
                return unless title.empty?
         
     | 
| 
      
 162 
     | 
    
         
            +
                errors.add "Title is absent", field: "title", level: "error"
         
     | 
| 
      
 163 
     | 
    
         
            +
              end
         
     | 
| 
      
 164 
     | 
    
         
            +
            end
         
     | 
| 
       91 
165 
     | 
    
         | 
| 
       92 
     | 
    
         
            -
            #  
     | 
| 
       93 
     | 
    
         
            -
            policy. 
     | 
| 
      
 166 
     | 
    
         
            +
            # ...
         
     | 
| 
      
 167 
     | 
    
         
            +
            policy.messages
         
     | 
| 
       94 
168 
     | 
    
         
             
            # => [
         
     | 
| 
       95 
     | 
    
         
            -
            #       
     | 
| 
       96 
     | 
    
         
            -
            #       
     | 
| 
      
 169 
     | 
    
         
            +
            #      "Title is absent",
         
     | 
| 
      
 170 
     | 
    
         
            +
            #      "translation missing: en.tram-policy.article/readiness_policy.blank_subtitle"
         
     | 
| 
       97 
171 
     | 
    
         
             
            #    ]
         
     | 
| 
      
 172 
     | 
    
         
            +
            ```
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
            ## Partial Validation
         
     | 
| 
       98 
175 
     | 
    
         | 
| 
       99 
     | 
    
         
            -
             
     | 
| 
      
 176 
     | 
    
         
            +
            You can use tags in checkers -- to add condition for errors to ignore
         
     | 
| 
      
 177 
     | 
    
         
            +
             
     | 
| 
      
 178 
     | 
    
         
            +
            ```ruby
         
     | 
| 
       100 
179 
     | 
    
         
             
            policy.valid? { |error| !%w(warning error).include? error.level } # => false
         
     | 
| 
       101 
180 
     | 
    
         
             
            policy.valid? { |error| error.level != "disaster" }               # => true
         
     | 
| 
      
 181 
     | 
    
         
            +
            ```
         
     | 
| 
       102 
182 
     | 
    
         | 
| 
       103 
     | 
    
         
            -
             
     | 
| 
      
 183 
     | 
    
         
            +
            Notice the `invalid?` method takes a block with definitions for errors to count (not ignore)
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
            ```ruby
         
     | 
| 
       104 
186 
     | 
    
         
             
            policy.invalid? { |error| %w(warning error).include? error.level } # => true
         
     | 
| 
       105 
187 
     | 
    
         
             
            policy.invalid? { |error| error.level == "disaster" }              # => false
         
     | 
| 
       106 
188 
     | 
    
         | 
| 
       107 
189 
     | 
    
         
             
            policy.validate! { |error| error.level != "disaster" } # => nil (seems ok)
         
     | 
| 
       108 
190 
     | 
    
         
             
            ```
         
     | 
| 
       109 
191 
     | 
    
         | 
| 
      
 192 
     | 
    
         
            +
            ## Composition of Policies
         
     | 
| 
      
 193 
     | 
    
         
            +
             
     | 
| 
       110 
194 
     | 
    
         
             
            You can use errors in composition of policies:
         
     | 
| 
       111 
195 
     | 
    
         | 
| 
       112 
196 
     | 
    
         
             
            ```ruby
         
     | 
| 
         @@ -121,7 +205,7 @@ class Article::PublicationPolicy < Tram::Policy 
     | 
|
| 
       121 
205 
     | 
    
         | 
| 
       122 
206 
     | 
    
         
             
              def article_readiness
         
     | 
| 
       123 
207 
     | 
    
         
             
                # Collects errors tagged by level: "error" from "nested" policy
         
     | 
| 
       124 
     | 
    
         
            -
                readiness_errors = Article::ReadinessPolicy[article].errors. 
     | 
| 
      
 208 
     | 
    
         
            +
                readiness_errors = Article::ReadinessPolicy[article].errors.filter(level: "error")
         
     | 
| 
       125 
209 
     | 
    
         | 
| 
       126 
210 
     | 
    
         
             
                # Merges collected errors to the current ones.
         
     | 
| 
       127 
211 
     | 
    
         
             
                # New errors are also tagged by source: "readiness".
         
     | 
| 
         @@ -134,32 +218,9 @@ class Article::PublicationPolicy < Tram::Policy 
     | 
|
| 
       134 
218 
     | 
    
         
             
            end
         
     | 
| 
       135 
219 
     | 
    
         
             
            ```
         
     | 
| 
       136 
220 
     | 
    
         | 
| 
       137 
     | 
    
         
            -
             
     | 
| 
       138 
     | 
    
         
            -
             
     | 
| 
       139 
     | 
    
         
            -
            > You can redefine the scope by reloading private method `.scope` of the policy.
         
     | 
| 
       140 
     | 
    
         
            -
             
     | 
| 
       141 
     | 
    
         
            -
            All tags are available as options:
         
     | 
| 
      
 221 
     | 
    
         
            +
            ## Exceptions
         
     | 
| 
       142 
222 
     | 
    
         | 
| 
       143 
     | 
    
         
            -
             
     | 
| 
       144 
     | 
    
         
            -
            class Article::PublicationPolicy < Tram::Policy
         
     | 
| 
       145 
     | 
    
         
            -
              # ...
         
     | 
| 
       146 
     | 
    
         
            -
              errors.add :empty, field: "text", level: "error"
         
     | 
| 
       147 
     | 
    
         
            -
              # ...
         
     | 
| 
       148 
     | 
    
         
            -
            end
         
     | 
| 
       149 
     | 
    
         
            -
            ```
         
     | 
| 
       150 
     | 
    
         
            -
             
     | 
| 
       151 
     | 
    
         
            -
            ```yaml
         
     | 
| 
       152 
     | 
    
         
            -
            # /config/locales/en.yml
         
     | 
| 
       153 
     | 
    
         
            -
            ---
         
     | 
| 
       154 
     | 
    
         
            -
            en:
         
     | 
| 
       155 
     | 
    
         
            -
              tram-policy:
         
     | 
| 
       156 
     | 
    
         
            -
                article/publication_policy:
         
     | 
| 
       157 
     | 
    
         
            -
                  empty: "Validation %{level}: %{field} is empty"
         
     | 
| 
       158 
     | 
    
         
            -
            ```
         
     | 
| 
       159 
     | 
    
         
            -
             
     | 
| 
       160 
     | 
    
         
            -
            This will provide error message "Validation error: text is empty".
         
     | 
| 
       161 
     | 
    
         
            -
             
     | 
| 
       162 
     | 
    
         
            -
            The last thing to say is about exceptions. When you use `validate!` it raises `Tram::Policy::ValidationError` (subclass of `RuntimeError`). Its message is built from selected errors (taking into account a `validation!` filter).
         
     | 
| 
      
 223 
     | 
    
         
            +
            When you use `validate!` it raises `Tram::Policy::ValidationError` (subclass of `RuntimeError`). Its message is built from selected errors (taking into account a `validation!` filter).
         
     | 
| 
       163 
224 
     | 
    
         | 
| 
       164 
225 
     | 
    
         
             
            The exception also carries a backreference to the `policy` that raised it. You can use it to extract either errors, or arguments of the policy during a debugging:
         
     | 
| 
       165 
226 
     | 
    
         | 
| 
         @@ -238,7 +299,7 @@ RSpec.describe User::ReadinessPolicy do 
     | 
|
| 
       238 
299 
     | 
    
         
             
            end
         
     | 
| 
       239 
300 
     | 
    
         
             
            ```
         
     | 
| 
       240 
301 
     | 
    
         | 
| 
       241 
     | 
    
         
            -
            **Notice** that you have to wrap policy into block `{ policy }`. This is because the matcher checks not only 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.
         
     | 
| 
      
 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.
         
     | 
| 
       242 
303 
     | 
    
         | 
| 
       243 
304 
     | 
    
         
             
            ## Generators
         
     | 
| 
       244 
305 
     | 
    
         | 
| 
         @@ -342,43 +403,6 @@ When called without tags, it checks that the policy is valid as a whole. 
     | 
|
| 
       342 
403 
     | 
    
         | 
| 
       343 
404 
     | 
    
         
             
            Both matchers provide a full description for the essence of the failure.
         
     | 
| 
       344 
405 
     | 
    
         | 
| 
       345 
     | 
    
         
            -
            ## To Recap
         
     | 
| 
       346 
     | 
    
         
            -
             
     | 
| 
       347 
     | 
    
         
            -
            The `Tram::Policy` DSL provides the following methods:
         
     | 
| 
       348 
     | 
    
         
            -
             
     | 
| 
       349 
     | 
    
         
            -
            * `.param` and `.option` - class-level methods for policy constructor arguments
         
     | 
| 
       350 
     | 
    
         
            -
            * `.validate` - class-level method to add validators (they will be invoked in the same order as defined)
         
     | 
| 
       351 
     | 
    
         
            -
            * `.[]` - a syntax sugar for `.new`
         
     | 
| 
       352 
     | 
    
         
            -
             
     | 
| 
       353 
     | 
    
         
            -
            * `#errors` - returns an enumerable collection of validation errors
         
     | 
| 
       354 
     | 
    
         
            -
            * `#valid?` - checks whether no errors exist
         
     | 
| 
       355 
     | 
    
         
            -
            * `#invalid?` - checks whether some error exists
         
     | 
| 
       356 
     | 
    
         
            -
            * `#validate!` - raises if some error exist
         
     | 
| 
       357 
     | 
    
         
            -
             
     | 
| 
       358 
     | 
    
         
            -
            Enumerable collection of unique policy `errors` (`Tram::Policy::Errors`) responds to methods:
         
     | 
| 
       359 
     | 
    
         
            -
             
     | 
| 
       360 
     | 
    
         
            -
            * `add` - adds an error to the collection
         
     | 
| 
       361 
     | 
    
         
            -
            * `each` - iterates by the set of errors (support other methods of enumerables)
         
     | 
| 
       362 
     | 
    
         
            -
            * `empty?` - checks whether a collection is emtpy (in addition to enumerable interface)
         
     | 
| 
       363 
     | 
    
         
            -
            * `by_tags` - filters errors that have given tags
         
     | 
| 
       364 
     | 
    
         
            -
            * `messages` - returns an array of messages
         
     | 
| 
       365 
     | 
    
         
            -
            * `full_messages` - returns an array of messages with tags info added (used in exception)
         
     | 
| 
       366 
     | 
    
         
            -
            * `merge` - merges a collection to another one
         
     | 
| 
       367 
     | 
    
         
            -
             
     | 
| 
       368 
     | 
    
         
            -
            Every instance of `Tram::Policy::Error` supports:
         
     | 
| 
       369 
     | 
    
         
            -
             
     | 
| 
       370 
     | 
    
         
            -
            * `#tags` - hash of assigned tags
         
     | 
| 
       371 
     | 
    
         
            -
            * `#message` - the translated message
         
     | 
| 
       372 
     | 
    
         
            -
            * `#full_message` - the message with tags info added
         
     | 
| 
       373 
     | 
    
         
            -
            * `#to_h` - hash of tags and a message
         
     | 
| 
       374 
     | 
    
         
            -
            * `#==` - checks whether an error is equal to another one
         
     | 
| 
       375 
     | 
    
         
            -
            * undefined methods treated as tags
         
     | 
| 
       376 
     | 
    
         
            -
             
     | 
| 
       377 
     | 
    
         
            -
            The instance of `Tram::Policy::ValidationError` responds to:
         
     | 
| 
       378 
     | 
    
         
            -
             
     | 
| 
       379 
     | 
    
         
            -
            * `policy` - returns a policy object that raised an exception
         
     | 
| 
       380 
     | 
    
         
            -
            * other methods defined by the `RuntimeError` class
         
     | 
| 
       381 
     | 
    
         
            -
             
     | 
| 
       382 
406 
     | 
    
         
             
            ## Installation
         
     | 
| 
       383 
407 
     | 
    
         | 
| 
       384 
408 
     | 
    
         
             
            Add this line to your application's Gemfile:
         
     | 
    
        data/lib/tram/policy.rb
    CHANGED
    
    | 
         @@ -15,6 +15,15 @@ module Tram 
     | 
|
| 
       15 
15 
     | 
    
         
             
                extend Dry::Initializer
         
     | 
| 
       16 
16 
     | 
    
         
             
                extend DSL
         
     | 
| 
       17 
17 
     | 
    
         | 
| 
      
 18 
     | 
    
         
            +
                # The scope used for translating error messages
         
     | 
| 
      
 19 
     | 
    
         
            +
                #
         
     | 
| 
      
 20 
     | 
    
         
            +
                # @return [Array<String>]
         
     | 
| 
      
 21 
     | 
    
         
            +
                #
         
     | 
| 
      
 22 
     | 
    
         
            +
                def scope
         
     | 
| 
      
 23 
     | 
    
         
            +
                  Array self.class.scope
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                # @!method t(message, options)
         
     | 
| 
       18 
27 
     | 
    
         
             
                # Translates a message in the scope of current policy
         
     | 
| 
       19 
28 
     | 
    
         
             
                #
         
     | 
| 
       20 
29 
     | 
    
         
             
                # @param  [#to_s] message
         
     | 
| 
         @@ -23,10 +32,10 @@ module Tram 
     | 
|
| 
       23 
32 
     | 
    
         
             
                #
         
     | 
| 
       24 
33 
     | 
    
         
             
                def t(message, **options)
         
     | 
| 
       25 
34 
     | 
    
         
             
                  return message.to_s unless message.is_a? Symbol
         
     | 
| 
       26 
     | 
    
         
            -
                  I18n.t message, scope:  
     | 
| 
      
 35 
     | 
    
         
            +
                  I18n.t message, scope: scope, **options
         
     | 
| 
       27 
36 
     | 
    
         
             
                end
         
     | 
| 
       28 
37 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
                #  
     | 
| 
      
 38 
     | 
    
         
            +
                # The collection of validation errors
         
     | 
| 
       30 
39 
     | 
    
         
             
                #
         
     | 
| 
       31 
40 
     | 
    
         
             
                # @return [Tram::Policy::Errors]
         
     | 
| 
       32 
41 
     | 
    
         
             
                #
         
     | 
| 
         @@ -34,6 +43,22 @@ module Tram 
     | 
|
| 
       34 
43 
     | 
    
         
             
                  @errors ||= Errors.new(self)
         
     | 
| 
       35 
44 
     | 
    
         
             
                end
         
     | 
| 
       36 
45 
     | 
    
         | 
| 
      
 46 
     | 
    
         
            +
                # The array of error items for lazy translation
         
     | 
| 
      
 47 
     | 
    
         
            +
                #
         
     | 
| 
      
 48 
     | 
    
         
            +
                # @return [Array<Array>]
         
     | 
| 
      
 49 
     | 
    
         
            +
                #
         
     | 
| 
      
 50 
     | 
    
         
            +
                def items
         
     | 
| 
      
 51 
     | 
    
         
            +
                  errors.items
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                # The array of error messages translated for the current locale
         
     | 
| 
      
 55 
     | 
    
         
            +
                #
         
     | 
| 
      
 56 
     | 
    
         
            +
                # @return [Array<String>]
         
     | 
| 
      
 57 
     | 
    
         
            +
                #
         
     | 
| 
      
 58 
     | 
    
         
            +
                def messages
         
     | 
| 
      
 59 
     | 
    
         
            +
                  errors.messages
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
       37 
62 
     | 
    
         
             
                # Checks whether the policy is valid
         
     | 
| 
       38 
63 
     | 
    
         
             
                #
         
     | 
| 
       39 
64 
     | 
    
         
             
                # @param  [Proc, nil] filter Block describing **errors to be skipped**
         
     | 
    
        data/lib/tram/policy/dsl.rb
    CHANGED
    
    | 
         @@ -22,12 +22,21 @@ class Tram::Policy 
     | 
|
| 
       22 
22 
     | 
    
         
             
                  new(*args)
         
     | 
| 
       23 
23 
     | 
    
         
             
                end
         
     | 
| 
       24 
24 
     | 
    
         | 
| 
      
 25 
     | 
    
         
            +
                # Sets the root scope of the policy and its subclasses
         
     | 
| 
      
 26 
     | 
    
         
            +
                #
         
     | 
| 
      
 27 
     | 
    
         
            +
                # @param  [String, Array<String>] value
         
     | 
| 
      
 28 
     | 
    
         
            +
                # @return [self]
         
     | 
| 
      
 29 
     | 
    
         
            +
                #
         
     | 
| 
      
 30 
     | 
    
         
            +
                def root_scope(*value)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  tap { @root_scope = value.flatten.map(&:to_s).reject(&:empty?) }
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
       25 
34 
     | 
    
         
             
                # Translation scope for a policy
         
     | 
| 
       26 
35 
     | 
    
         
             
                #
         
     | 
| 
       27 
36 
     | 
    
         
             
                # @return [Array<String>]
         
     | 
| 
       28 
37 
     | 
    
         
             
                #
         
     | 
| 
       29 
38 
     | 
    
         
             
                def scope
         
     | 
| 
       30 
     | 
    
         
            -
                  @scope ||= [ 
     | 
| 
      
 39 
     | 
    
         
            +
                  @scope ||= Array(@root_scope) + [Inflector.underscore(name)]
         
     | 
| 
       31 
40 
     | 
    
         
             
                end
         
     | 
| 
       32 
41 
     | 
    
         | 
| 
       33 
42 
     | 
    
         
             
                # List of validators defined by a policy per se
         
     | 
| 
         @@ -46,5 +55,17 @@ class Tram::Policy 
     | 
|
| 
       46 
55 
     | 
    
         
             
                  parent_validators = self == Tram::Policy ? [] : superclass.validators
         
     | 
| 
       47 
56 
     | 
    
         
             
                  (parent_validators + local_validators).uniq
         
     | 
| 
       48 
57 
     | 
    
         
             
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                private
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                def inherited(klass)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  super
         
     | 
| 
      
 63 
     | 
    
         
            +
                  klass.send :instance_variable_set, :@root_scope, @root_scope
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                def self.extended(klass)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  super
         
     | 
| 
      
 68 
     | 
    
         
            +
                  klass.send :instance_variable_set, :@root_scope, %w[tram-policy]
         
     | 
| 
      
 69 
     | 
    
         
            +
                end
         
     | 
| 
       49 
70 
     | 
    
         
             
              end
         
     | 
| 
       50 
71 
     | 
    
         
             
            end
         
     | 
    
        data/lib/tram/policy/error.rb
    CHANGED
    
    | 
         @@ -6,7 +6,7 @@ class Tram::Policy 
     | 
|
| 
       6 
6 
     | 
    
         
             
              #         from one collection of [Tram::Policy::Errors] to another.
         
     | 
| 
       7 
7 
     | 
    
         
             
              #
         
     | 
| 
       8 
8 
     | 
    
         
             
              class Error
         
     | 
| 
       9 
     | 
    
         
            -
                # @!method self.new(value, opts)
         
     | 
| 
      
 9 
     | 
    
         
            +
                # @!method self.new(value, opts = {})
         
     | 
| 
       10 
10 
     | 
    
         
             
                # Builds an error
         
     | 
| 
       11 
11 
     | 
    
         
             
                #
         
     | 
| 
       12 
12 
     | 
    
         
             
                # If another error is send to the constructor, the error returned unchanged
         
     | 
| 
         @@ -16,65 +16,108 @@ class Tram::Policy 
     | 
|
| 
       16 
16 
     | 
    
         
             
                # @return [Tram::Policy::Error]
         
     | 
| 
       17 
17 
     | 
    
         
             
                #
         
     | 
| 
       18 
18 
     | 
    
         
             
                def self.new(value, **opts)
         
     | 
| 
       19 
     | 
    
         
            -
                   
     | 
| 
       20 
     | 
    
         
            -
                  super
         
     | 
| 
      
 19 
     | 
    
         
            +
                  value.instance_of?(self) ? value : super
         
     | 
| 
       21 
20 
     | 
    
         
             
                end
         
     | 
| 
       22 
21 
     | 
    
         | 
| 
       23 
     | 
    
         
            -
                # @!attribute [r]  
     | 
| 
      
 22 
     | 
    
         
            +
                # @!attribute [r] key
         
     | 
| 
      
 23 
     | 
    
         
            +
                # @return [Symbol, String] error key
         
     | 
| 
      
 24 
     | 
    
         
            +
                attr_reader :key
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                # @!attribute [r] tags
         
     | 
| 
      
 27 
     | 
    
         
            +
                # @return [Hash<Symbol, Object>] error tags
         
     | 
| 
      
 28 
     | 
    
         
            +
                attr_reader :tags
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                # The list of arguments for [I18n.t]
         
     | 
| 
       24 
31 
     | 
    
         
             
                #
         
     | 
| 
       25 
     | 
    
         
            -
                # @return [ 
     | 
| 
      
 32 
     | 
    
         
            +
                # @return [Array]
         
     | 
| 
       26 
33 
     | 
    
         
             
                #
         
     | 
| 
       27 
     | 
    
         
            -
                 
     | 
| 
      
 34 
     | 
    
         
            +
                def item
         
     | 
| 
      
 35 
     | 
    
         
            +
                  [key, tags]
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
                alias to_a item
         
     | 
| 
       28 
38 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
                # The  
     | 
| 
      
 39 
     | 
    
         
            +
                # The text of error message translated to the current locale
         
     | 
| 
       30 
40 
     | 
    
         
             
                #
         
     | 
| 
       31 
41 
     | 
    
         
             
                # @return [String]
         
     | 
| 
       32 
42 
     | 
    
         
             
                #
         
     | 
| 
       33 
     | 
    
         
            -
                def  
     | 
| 
       34 
     | 
    
         
            -
                   
     | 
| 
      
 43 
     | 
    
         
            +
                def message
         
     | 
| 
      
 44 
     | 
    
         
            +
                  key.is_a?(Symbol) ? I18n.t(*item) : key.to_s
         
     | 
| 
       35 
45 
     | 
    
         
             
                end
         
     | 
| 
       36 
46 
     | 
    
         | 
| 
       37 
     | 
    
         
            -
                #  
     | 
| 
      
 47 
     | 
    
         
            +
                # @deprecated
         
     | 
| 
      
 48 
     | 
    
         
            +
                # Converts the error to a hash of message and tags
         
     | 
| 
       38 
49 
     | 
    
         
             
                #
         
     | 
| 
       39 
50 
     | 
    
         
             
                # @return [Hash<Symbol, Object>]
         
     | 
| 
       40 
51 
     | 
    
         
             
                #
         
     | 
| 
       41 
52 
     | 
    
         
             
                def to_h
         
     | 
| 
       42 
     | 
    
         
            -
                   
     | 
| 
      
 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(" ")
         
     | 
| 
       43 
69 
     | 
    
         
             
                end
         
     | 
| 
       44 
70 
     | 
    
         | 
| 
       45 
     | 
    
         
            -
                # Fetches  
     | 
| 
      
 71 
     | 
    
         
            +
                # Fetches an option
         
     | 
| 
       46 
72 
     | 
    
         
             
                #
         
     | 
| 
       47 
73 
     | 
    
         
             
                # @param [#to_sym] tag
         
     | 
| 
       48 
74 
     | 
    
         
             
                # @return [Object]
         
     | 
| 
       49 
75 
     | 
    
         
             
                #
         
     | 
| 
       50 
76 
     | 
    
         
             
                def [](tag)
         
     | 
| 
       51 
     | 
    
         
            -
                   
     | 
| 
      
 77 
     | 
    
         
            +
                  tags[tag.to_sym]
         
     | 
| 
       52 
78 
     | 
    
         
             
                end
         
     | 
| 
       53 
79 
     | 
    
         | 
| 
       54 
     | 
    
         
            -
                # Fetches  
     | 
| 
      
 80 
     | 
    
         
            +
                # Fetches the tag
         
     | 
| 
       55 
81 
     | 
    
         
             
                #
         
     | 
| 
       56 
82 
     | 
    
         
             
                # @param [#to_sym] tag
         
     | 
| 
       57 
83 
     | 
    
         
             
                # @param [Object] default
         
     | 
| 
       58 
84 
     | 
    
         
             
                # @param [Proc] block
         
     | 
| 
       59 
85 
     | 
    
         
             
                # @return [Object]
         
     | 
| 
       60 
86 
     | 
    
         
             
                #
         
     | 
| 
       61 
     | 
    
         
            -
                def fetch(tag, default, &block)
         
     | 
| 
       62 
     | 
    
         
            -
                   
     | 
| 
      
 87 
     | 
    
         
            +
                def fetch(tag, default = Dry::Initializer::UNDEFINED, &block)
         
     | 
| 
      
 88 
     | 
    
         
            +
                  if default == Dry::Initializer::UNDEFINED
         
     | 
| 
      
 89 
     | 
    
         
            +
                    tags.fetch(tag.to_sym, &block)
         
     | 
| 
      
 90 
     | 
    
         
            +
                  else
         
     | 
| 
      
 91 
     | 
    
         
            +
                    tags.fetch(tag.to_sym, default, &block)
         
     | 
| 
      
 92 
     | 
    
         
            +
                  end
         
     | 
| 
       63 
93 
     | 
    
         
             
                end
         
     | 
| 
       64 
94 
     | 
    
         | 
| 
       65 
     | 
    
         
            -
                # Compares an error to another object using method [# 
     | 
| 
      
 95 
     | 
    
         
            +
                # Compares an error to another object using method [#item]
         
     | 
| 
       66 
96 
     | 
    
         
             
                #
         
     | 
| 
       67 
97 
     | 
    
         
             
                # @param  [Object] other Other object to compare to
         
     | 
| 
       68 
98 
     | 
    
         
             
                # @return [Boolean]
         
     | 
| 
       69 
99 
     | 
    
         
             
                #
         
     | 
| 
       70 
100 
     | 
    
         
             
                def ==(other)
         
     | 
| 
       71 
     | 
    
         
            -
                  other.respond_to?(: 
     | 
| 
      
 101 
     | 
    
         
            +
                  other.respond_to?(:to_a) && other.to_a == item
         
     | 
| 
      
 102 
     | 
    
         
            +
                end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                # @!method contain?(some_key = nil, some_tags = {})
         
     | 
| 
      
 105 
     | 
    
         
            +
                # Checks whether the error contain given key and tags
         
     | 
| 
      
 106 
     | 
    
         
            +
                #
         
     | 
| 
      
 107 
     | 
    
         
            +
                # @param [Object] some_key Expected key of the error
         
     | 
| 
      
 108 
     | 
    
         
            +
                # @param [Hash<Symbol, Object>] some_tags Expected tags of the error
         
     | 
| 
      
 109 
     | 
    
         
            +
                # @return [Boolean]
         
     | 
| 
      
 110 
     | 
    
         
            +
                #
         
     | 
| 
      
 111 
     | 
    
         
            +
                def contain?(some_key = nil, **some_tags)
         
     | 
| 
      
 112 
     | 
    
         
            +
                  return false if some_key&.!= key
         
     | 
| 
      
 113 
     | 
    
         
            +
                  some_tags.each { |k, v| return false unless tags[k] == v }
         
     | 
| 
      
 114 
     | 
    
         
            +
                  true
         
     | 
| 
       72 
115 
     | 
    
         
             
                end
         
     | 
| 
       73 
116 
     | 
    
         | 
| 
       74 
117 
     | 
    
         
             
                private
         
     | 
| 
       75 
118 
     | 
    
         | 
| 
       76 
     | 
    
         
            -
                def initialize( 
     | 
| 
       77 
     | 
    
         
            -
                  @ 
     | 
| 
      
 119 
     | 
    
         
            +
                def initialize(key, **tags)
         
     | 
| 
      
 120 
     | 
    
         
            +
                  @key  = key
         
     | 
| 
       78 
121 
     | 
    
         
             
                  @tags = tags
         
     | 
| 
       79 
122 
     | 
    
         
             
                end
         
     | 
| 
       80 
123 
     | 
    
         | 
| 
         @@ -83,7 +126,7 @@ class Tram::Policy 
     | 
|
| 
       83 
126 
     | 
    
         
             
                end
         
     | 
| 
       84 
127 
     | 
    
         | 
| 
       85 
128 
     | 
    
         
             
                def method_missing(name, *args, &block)
         
     | 
| 
       86 
     | 
    
         
            -
                  args.any? || block ? super :  
     | 
| 
      
 129 
     | 
    
         
            +
                  args.any? || block ? super : tags[name]
         
     | 
| 
       87 
130 
     | 
    
         
             
                end
         
     | 
| 
       88 
131 
     | 
    
         
             
              end
         
     | 
| 
       89 
132 
     | 
    
         
             
            end
         
     | 
    
        data/lib/tram/policy/errors.rb
    CHANGED
    
    | 
         @@ -21,12 +21,10 @@ class Tram::Policy 
     | 
|
| 
       21 
21 
     | 
    
         
             
                # @param [Hash<Symbol, Object>] tags Tags to be attached to the message
         
     | 
| 
       22 
22 
     | 
    
         
             
                # @return [self] the collection
         
     | 
| 
       23 
23 
     | 
    
         
             
                #
         
     | 
| 
       24 
     | 
    
         
            -
                def add(message 
     | 
| 
       25 
     | 
    
         
            -
                   
     | 
| 
      
 24 
     | 
    
         
            +
                def add(message, **tags)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  tags = tags.merge(scope: policy.scope) unless tags.key?(:scope)
         
     | 
| 
       26 
26 
     | 
    
         
             
                  raise ArgumentError.new("Error message should be defined") unless message
         
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
                  @set << Tram::Policy::Error.new(@policy.t(message, tags), **tags)
         
     | 
| 
       29 
     | 
    
         
            -
                  self
         
     | 
| 
      
 27 
     | 
    
         
            +
                  tap { @set << Tram::Policy::Error.new(message, **tags) }
         
     | 
| 
       30 
28 
     | 
    
         
             
                end
         
     | 
| 
       31 
29 
     | 
    
         | 
| 
       32 
30 
     | 
    
         
             
                # Iterates by collected errors
         
     | 
| 
         @@ -38,15 +36,32 @@ class Tram::Policy 
     | 
|
| 
       38 
36 
     | 
    
         
             
                  @set.each { |error| yield(error) }
         
     | 
| 
       39 
37 
     | 
    
         
             
                end
         
     | 
| 
       40 
38 
     | 
    
         | 
| 
       41 
     | 
    
         
            -
                # @!method  
     | 
| 
       42 
     | 
    
         
            -
                #  
     | 
| 
      
 39 
     | 
    
         
            +
                # @!method filter(key = nil, tags)
         
     | 
| 
      
 40 
     | 
    
         
            +
                # Filter errors by optional key and tags
         
     | 
| 
       43 
41 
     | 
    
         
             
                #
         
     | 
| 
       44 
     | 
    
         
            -
                # @param  [ 
     | 
| 
       45 
     | 
    
         
            -
                # @ 
     | 
| 
      
 42 
     | 
    
         
            +
                # @param  [#to_s] key The key to filter errors by
         
     | 
| 
      
 43 
     | 
    
         
            +
                # @param  [Hash<Symbol, Object>] tags The list of tags to filter errors by
         
     | 
| 
      
 44 
     | 
    
         
            +
                # @return [Tram::Policy::Errors]
         
     | 
| 
       46 
45 
     | 
    
         
             
                #
         
     | 
| 
       47 
     | 
    
         
            -
                def  
     | 
| 
       48 
     | 
    
         
            -
                   
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
      
 46 
     | 
    
         
            +
                def filter(key = nil, **tags)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  list = each_with_object(Set.new) do |error, obj|
         
     | 
| 
      
 48 
     | 
    
         
            +
                    obj << error if error.contain?(key, tags)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
                  self.class.new(policy, list)
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 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
         
     | 
| 
       50 
65 
     | 
    
         
             
                end
         
     | 
| 
       51 
66 
     | 
    
         | 
| 
       52 
67 
     | 
    
         
             
                # @!method empty?
         
     | 
| 
         @@ -58,6 +73,14 @@ class Tram::Policy 
     | 
|
| 
       58 
73 
     | 
    
         
             
                  block ? !any?(&block) : !any?
         
     | 
| 
       59 
74 
     | 
    
         
             
                end
         
     | 
| 
       60 
75 
     | 
    
         | 
| 
      
 76 
     | 
    
         
            +
                # The array of error items for translation
         
     | 
| 
      
 77 
     | 
    
         
            +
                #
         
     | 
| 
      
 78 
     | 
    
         
            +
                # @return [Array<Array>]
         
     | 
| 
      
 79 
     | 
    
         
            +
                #
         
     | 
| 
      
 80 
     | 
    
         
            +
                def items
         
     | 
| 
      
 81 
     | 
    
         
            +
                  @set.map(&:item)
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
       61 
84 
     | 
    
         
             
                # The array of ordered error messages
         
     | 
| 
       62 
85 
     | 
    
         
             
                #
         
     | 
| 
       63 
86 
     | 
    
         
             
                # @return [Array<String>]
         
     | 
| 
         @@ -66,12 +89,16 @@ class Tram::Policy 
     | 
|
| 
       66 
89 
     | 
    
         
             
                  @set.map(&:message).sort
         
     | 
| 
       67 
90 
     | 
    
         
             
                end
         
     | 
| 
       68 
91 
     | 
    
         | 
| 
       69 
     | 
    
         
            -
                #  
     | 
| 
      
 92 
     | 
    
         
            +
                # @deprecated
         
     | 
| 
      
 93 
     | 
    
         
            +
                # List of error descriptions
         
     | 
| 
       70 
94 
     | 
    
         
             
                #
         
     | 
| 
       71 
95 
     | 
    
         
             
                # @return [Array<String>]
         
     | 
| 
       72 
96 
     | 
    
         
             
                #
         
     | 
| 
       73 
97 
     | 
    
         
             
                def full_messages
         
     | 
| 
       74 
     | 
    
         
            -
                   
     | 
| 
      
 98 
     | 
    
         
            +
                  warn "[DEPRECATED] The method Tram::Policy::Errors#full_messages" \
         
     | 
| 
      
 99 
     | 
    
         
            +
                       " will be removed in the v1.0.0."
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                  map(&:full_message)
         
     | 
| 
       75 
102 
     | 
    
         
             
                end
         
     | 
| 
       76 
103 
     | 
    
         | 
| 
       77 
104 
     | 
    
         
             
                # @!method merge(other, options)
         
     | 
| 
         @@ -90,8 +117,9 @@ class Tram::Policy 
     | 
|
| 
       90 
117 
     | 
    
         
             
                  return self unless other.is_a?(self.class)
         
     | 
| 
       91 
118 
     | 
    
         | 
| 
       92 
119 
     | 
    
         
             
                  other.each do |err|
         
     | 
| 
       93 
     | 
    
         
            -
                     
     | 
| 
       94 
     | 
    
         
            -
                     
     | 
| 
      
 120 
     | 
    
         
            +
                    key, opts = err.item
         
     | 
| 
      
 121 
     | 
    
         
            +
                    opts = yield(opts) if block_given?
         
     | 
| 
      
 122 
     | 
    
         
            +
                    add key, opts.merge(options)
         
     | 
| 
       95 
123 
     | 
    
         
             
                  end
         
     | 
| 
       96 
124 
     | 
    
         | 
| 
       97 
125 
     | 
    
         
             
                  self
         
     | 
| 
         @@ -99,9 +127,9 @@ class Tram::Policy 
     | 
|
| 
       99 
127 
     | 
    
         | 
| 
       100 
128 
     | 
    
         
             
                private
         
     | 
| 
       101 
129 
     | 
    
         | 
| 
       102 
     | 
    
         
            -
                def initialize(policy, errors =  
     | 
| 
      
 130 
     | 
    
         
            +
                def initialize(policy, errors = [])
         
     | 
| 
       103 
131 
     | 
    
         
             
                  @policy = policy
         
     | 
| 
       104 
     | 
    
         
            -
                  @set    = errors
         
     | 
| 
      
 132 
     | 
    
         
            +
                  @set    = Set.new(errors)
         
     | 
| 
       105 
133 
     | 
    
         
             
                end
         
     | 
| 
       106 
134 
     | 
    
         
             
              end
         
     | 
| 
       107 
135 
     | 
    
         
             
            end
         
     | 
    
        data/lib/tram/policy/rspec.rb
    CHANGED
    
    | 
         @@ -34,7 +34,9 @@ RSpec::Matchers.define :be_invalid_at do |**tags| 
     | 
|
| 
       34 
34 
     | 
    
         
             
                I18n.locale    = locale
         
     | 
| 
       35 
35 
     | 
    
         
             
                local_policy   = policy_block.call
         
     | 
| 
       36 
36 
     | 
    
         
             
                self.policy    = local_policy.inspect
         
     | 
| 
       37 
     | 
    
         
            -
                errors[locale] = local_policy&.errors&. 
     | 
| 
      
 37 
     | 
    
         
            +
                errors[locale] = local_policy&.errors&.filter(tags)&.map do |error|
         
     | 
| 
      
 38 
     | 
    
         
            +
                  { message: error.message, tags: error.options } # translate immediately
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
       38 
40 
     | 
    
         
             
              end
         
     | 
| 
       39 
41 
     | 
    
         | 
| 
       40 
42 
     | 
    
         
             
              def prepare_results(policy_block, tags)
         
     | 
| 
         @@ -62,7 +64,7 @@ RSpec::Matchers.define :be_invalid_at do |**tags| 
     | 
|
| 
       62 
64 
     | 
    
         | 
| 
       63 
65 
     | 
    
         
             
              # Checks if all collected errors are translated
         
     | 
| 
       64 
66 
     | 
    
         
             
              def translated?
         
     | 
| 
       65 
     | 
    
         
            -
                texts = errors.values.flatten.map 
     | 
| 
      
 67 
     | 
    
         
            +
                texts = errors.values.flatten.map { |err| err[:message] }
         
     | 
| 
       66 
68 
     | 
    
         
             
                texts.select { |text| text.start_with?("translation missing") }.empty?
         
     | 
| 
       67 
69 
     | 
    
         
             
              end
         
     | 
| 
       68 
70 
     | 
    
         | 
| 
         @@ -70,7 +72,7 @@ RSpec::Matchers.define :be_invalid_at do |**tags| 
     | 
|
| 
       70 
72 
     | 
    
         
             
                text = "Actual errors:\n"
         
     | 
| 
       71 
73 
     | 
    
         
             
                errors.each do |locale, local_errors|
         
     | 
| 
       72 
74 
     | 
    
         
             
                  text << "  #{locale}:\n"
         
     | 
| 
       73 
     | 
    
         
            -
                  local_errors&.each { | 
     | 
| 
      
 75 
     | 
    
         
            +
                  local_errors&.each { |err| text << "  - #{err.values.join(" ")}\n" }
         
     | 
| 
       74 
76 
     | 
    
         
             
                end
         
     | 
| 
       75 
77 
     | 
    
         
             
                text
         
     | 
| 
       76 
78 
     | 
    
         
             
              end
         
     | 
| 
         @@ -11,7 +11,7 @@ class Tram::Policy 
     | 
|
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
                def initialize(policy, filter)
         
     | 
| 
       13 
13 
     | 
    
         
             
                  @policy  = policy
         
     | 
| 
       14 
     | 
    
         
            -
                  messages = policy.errors.reject(&filter).map(&: 
     | 
| 
      
 14 
     | 
    
         
            +
                  messages = policy.errors.to_a.reject(&filter).map(&:message)
         
     | 
| 
       15 
15 
     | 
    
         
             
                  super (["Validation failed with errors:"] + messages).join("\n- ")
         
     | 
| 
       16 
16 
     | 
    
         
             
                end
         
     | 
| 
       17 
17 
     | 
    
         
             
              end
         
     | 
    
        data/spec/fixtures/en.yml
    CHANGED
    
    
    
        data/spec/spec_helper.rb
    CHANGED
    
    | 
         @@ -15,7 +15,11 @@ RSpec.configure do |config| 
     | 
|
| 
       15 
15 
     | 
    
         
             
              config.filter_run focus: true
         
     | 
| 
       16 
16 
     | 
    
         
             
              config.run_all_when_everything_filtered = true
         
     | 
| 
       17 
17 
     | 
    
         | 
| 
       18 
     | 
    
         
            -
               
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
      
 18 
     | 
    
         
            +
              config.before(:each) do
         
     | 
| 
      
 19 
     | 
    
         
            +
                Test = Class.new(Module)
         
     | 
| 
      
 20 
     | 
    
         
            +
                I18n.available_locales = %w[en]
         
     | 
| 
      
 21 
     | 
    
         
            +
                I18n.backend.store_translations :en, yaml_fixture_file("en.yml")["en"]
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              config.after(:each) { Object.send :remove_const, :Test }
         
     | 
| 
       21 
25 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,45 +1,32 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            RSpec.describe Tram::Policy::Error do
         
     | 
| 
       2 
     | 
    
         
            -
              subject(:error) { described_class.new  
     | 
| 
      
 2 
     | 
    
         
            +
              subject(:error) { described_class.new :bad, options }
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
     | 
    
         
            -
              let(: 
     | 
| 
      
 4 
     | 
    
         
            +
              let(:options) { { level: "warning", scope: %w[tram-policy] } }
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
     | 
    
         
            -
              describe "# 
     | 
| 
       7 
     | 
    
         
            -
                subject { error. 
     | 
| 
       8 
     | 
    
         
            -
                it { is_expected.to eq  
     | 
| 
       9 
     | 
    
         
            -
              end
         
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
              describe "#full_message" do
         
     | 
| 
       12 
     | 
    
         
            -
                subject { error.full_message }
         
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
                context "with tags:" do
         
     | 
| 
       15 
     | 
    
         
            -
                  it { is_expected.to eq "Something bad happened {:level=>\"warning\"}" }
         
     | 
| 
       16 
     | 
    
         
            -
                end
         
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
                context "without tags:" do
         
     | 
| 
       19 
     | 
    
         
            -
                  let(:tags) { {} }
         
     | 
| 
       20 
     | 
    
         
            -
                  it { is_expected.to eq "Something bad happened" }
         
     | 
| 
       21 
     | 
    
         
            -
                end
         
     | 
| 
      
 6 
     | 
    
         
            +
              describe "#item" do
         
     | 
| 
      
 7 
     | 
    
         
            +
                subject { error.item }
         
     | 
| 
      
 8 
     | 
    
         
            +
                it { is_expected.to eq [:bad, level: "warning", scope: %w[tram-policy]] }
         
     | 
| 
       22 
9 
     | 
    
         
             
              end
         
     | 
| 
       23 
10 
     | 
    
         | 
| 
       24 
     | 
    
         
            -
              describe "# 
     | 
| 
       25 
     | 
    
         
            -
                subject { error. 
     | 
| 
       26 
     | 
    
         
            -
                it { is_expected.to eq  
     | 
| 
      
 11 
     | 
    
         
            +
              describe "#message" do
         
     | 
| 
      
 12 
     | 
    
         
            +
                subject { error.message }
         
     | 
| 
      
 13 
     | 
    
         
            +
                it { is_expected.to eq "Something bad has happened" }
         
     | 
| 
       27 
14 
     | 
    
         
             
              end
         
     | 
| 
       28 
15 
     | 
    
         | 
| 
       29 
16 
     | 
    
         
             
              describe "#==" do
         
     | 
| 
       30 
17 
     | 
    
         
             
                subject { error == other }
         
     | 
| 
       31 
18 
     | 
    
         | 
| 
       32 
     | 
    
         
            -
                context "when other object has the same # 
     | 
| 
       33 
     | 
    
         
            -
                  let(:other) { double  
     | 
| 
      
 19 
     | 
    
         
            +
                context "when other object has the same #item:" do
         
     | 
| 
      
 20 
     | 
    
         
            +
                  let(:other) { double to_a: error.item }
         
     | 
| 
       34 
21 
     | 
    
         
             
                  it { is_expected.to eq true }
         
     | 
| 
       35 
22 
     | 
    
         
             
                end
         
     | 
| 
       36 
23 
     | 
    
         | 
| 
       37 
     | 
    
         
            -
                context "when other object has different # 
     | 
| 
       38 
     | 
    
         
            -
                  let(:other) { double  
     | 
| 
      
 24 
     | 
    
         
            +
                context "when other object has different #item:" do
         
     | 
| 
      
 25 
     | 
    
         
            +
                  let(:other) { double to_a: [:foo] }
         
     | 
| 
       39 
26 
     | 
    
         
             
                  it { is_expected.to eq false }
         
     | 
| 
       40 
27 
     | 
    
         
             
                end
         
     | 
| 
       41 
28 
     | 
    
         | 
| 
       42 
     | 
    
         
            -
                context "when other object not respond to # 
     | 
| 
      
 29 
     | 
    
         
            +
                context "when other object not respond to #item:" do
         
     | 
| 
       43 
30 
     | 
    
         
             
                  let(:other) { double }
         
     | 
| 
       44 
31 
     | 
    
         
             
                  it { is_expected.to eq false }
         
     | 
| 
       45 
32 
     | 
    
         
             
                end
         
     | 
| 
         @@ -1,5 +1,5 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            RSpec.describe Tram::Policy::Errors do
         
     | 
| 
       2 
     | 
    
         
            -
              let(:policy) { double :policy,  
     | 
| 
      
 2 
     | 
    
         
            +
              let(:policy) { double :policy, scope: %w[tram-policy] }
         
     | 
| 
       3 
3 
     | 
    
         
             
              let(:errors) { described_class.new(policy) }
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
              describe ".new" do
         
     | 
| 
         @@ -12,14 +12,16 @@ RSpec.describe Tram::Policy::Errors do 
     | 
|
| 
       12 
12 
     | 
    
         
             
              end
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
14 
     | 
    
         
             
              describe "#add" do
         
     | 
| 
       15 
     | 
    
         
            -
                subject 
     | 
| 
      
 15 
     | 
    
         
            +
                subject { errors.add :omg, level: "info", field: "name" }
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
       16 
17 
     | 
    
         
             
                let(:error) { errors.to_a.last }
         
     | 
| 
       17 
18 
     | 
    
         | 
| 
       18 
19 
     | 
    
         
             
                it "adds an error to the collection:" do
         
     | 
| 
       19 
20 
     | 
    
         
             
                  expect { 2.times { subject } }.to change { errors.count }.by 1
         
     | 
| 
       20 
21 
     | 
    
         | 
| 
       21 
22 
     | 
    
         
             
                  expect(error).to be_kind_of Tram::Policy::Error
         
     | 
| 
       22 
     | 
    
         
            -
                  expect(error) 
     | 
| 
      
 23 
     | 
    
         
            +
                  expect(error)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    .to eq [:omg, level: "info", field: "name", scope: %w[tram-policy]]
         
     | 
| 
       23 
25 
     | 
    
         
             
                end
         
     | 
| 
       24 
26 
     | 
    
         
             
              end
         
     | 
| 
       25 
27 
     | 
    
         | 
| 
         @@ -35,6 +37,13 @@ RSpec.describe Tram::Policy::Errors do 
     | 
|
| 
       35 
37 
     | 
    
         
             
                end
         
     | 
| 
       36 
38 
     | 
    
         
             
              end
         
     | 
| 
       37 
39 
     | 
    
         | 
| 
      
 40 
     | 
    
         
            +
              describe "#items" do
         
     | 
| 
      
 41 
     | 
    
         
            +
                subject { errors.items }
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                before { errors.add "OMG!", level: "info", field: "name" }
         
     | 
| 
      
 44 
     | 
    
         
            +
                it { is_expected.to eq errors.map(&:item) }
         
     | 
| 
      
 45 
     | 
    
         
            +
              end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
       38 
47 
     | 
    
         
             
              describe "#merge" do
         
     | 
| 
       39 
48 
     | 
    
         
             
                let(:other) { described_class.new(policy) }
         
     | 
| 
       40 
49 
     | 
    
         | 
| 
         @@ -48,9 +57,9 @@ RSpec.describe Tram::Policy::Errors do 
     | 
|
| 
       48 
57 
     | 
    
         | 
| 
       49 
58 
     | 
    
         
             
                  it "merges other collection as is" do
         
     | 
| 
       50 
59 
     | 
    
         
             
                    expect(subject).to be_a Tram::Policy::Errors
         
     | 
| 
       51 
     | 
    
         
            -
                    expect(subject. 
     | 
| 
       52 
     | 
    
         
            -
                       
     | 
| 
       53 
     | 
    
         
            -
                       
     | 
| 
      
 60 
     | 
    
         
            +
                    expect(subject.items).to match_array [
         
     | 
| 
      
 61 
     | 
    
         
            +
                      ["D'OH!", level: "disaster", scope: %w[tram-policy]],
         
     | 
| 
      
 62 
     | 
    
         
            +
                      ["OUCH!", level: "error",    scope: %w[tram-policy]]
         
     | 
| 
       54 
63 
     | 
    
         
             
                    ]
         
     | 
| 
       55 
64 
     | 
    
         
             
                  end
         
     | 
| 
       56 
65 
     | 
    
         
             
                end
         
     | 
| 
         @@ -60,9 +69,9 @@ RSpec.describe Tram::Policy::Errors do 
     | 
|
| 
       60 
69 
     | 
    
         | 
| 
       61 
70 
     | 
    
         
             
                  it "merges filtered collection as is" do
         
     | 
| 
       62 
71 
     | 
    
         
             
                    expect(subject).to be_a Tram::Policy::Errors
         
     | 
| 
       63 
     | 
    
         
            -
                    expect(subject. 
     | 
| 
       64 
     | 
    
         
            -
                       
     | 
| 
       65 
     | 
    
         
            -
                       
     | 
| 
      
 72 
     | 
    
         
            +
                    expect(subject.items).to match_array [
         
     | 
| 
      
 73 
     | 
    
         
            +
                      ["D'OH!", level: "disaster", scope: %w[tram-policy]],
         
     | 
| 
      
 74 
     | 
    
         
            +
                      ["OUCH!", level: "error",    scope: %w[tram-policy], source: "Homer"]
         
     | 
| 
       66 
75 
     | 
    
         
             
                    ]
         
     | 
| 
       67 
76 
     | 
    
         
             
                  end
         
     | 
| 
       68 
77 
     | 
    
         
             
                end
         
     | 
| 
         @@ -72,9 +81,9 @@ RSpec.describe Tram::Policy::Errors do 
     | 
|
| 
       72 
81 
     | 
    
         | 
| 
       73 
82 
     | 
    
         
             
                  it "merges other collection with given options" do
         
     | 
| 
       74 
83 
     | 
    
         
             
                    expect(subject).to be_a Tram::Policy::Errors
         
     | 
| 
       75 
     | 
    
         
            -
                    expect(subject. 
     | 
| 
       76 
     | 
    
         
            -
                       
     | 
| 
       77 
     | 
    
         
            -
                       
     | 
| 
      
 84 
     | 
    
         
            +
                    expect(subject.items).to match_array [
         
     | 
| 
      
 85 
     | 
    
         
            +
                      ["D'OH!", level: "disaster", scope: %w[tram-policy]],
         
     | 
| 
      
 86 
     | 
    
         
            +
                      ["OUCH!", level: "error",    scope: %w[tram-policy], source: "Homer"]
         
     | 
| 
       78 
87 
     | 
    
         
             
                    ]
         
     | 
| 
       79 
88 
     | 
    
         
             
                  end
         
     | 
| 
       80 
89 
     | 
    
         
             
                end
         
     | 
| 
         @@ -84,14 +93,14 @@ RSpec.describe Tram::Policy::Errors do 
     | 
|
| 
       84 
93 
     | 
    
         | 
| 
       85 
94 
     | 
    
         
             
                  it "merges filtered collection with given options" do
         
     | 
| 
       86 
95 
     | 
    
         
             
                    expect(subject).to be_a Tram::Policy::Errors
         
     | 
| 
       87 
     | 
    
         
            -
                    expect(subject. 
     | 
| 
       88 
     | 
    
         
            -
                       
     | 
| 
       89 
     | 
    
         
            -
                       
     | 
| 
      
 96 
     | 
    
         
            +
                    expect(subject.items).to match_array [
         
     | 
| 
      
 97 
     | 
    
         
            +
                      ["D'OH!", level: "disaster", scope: %w[tram-policy]],
         
     | 
| 
      
 98 
     | 
    
         
            +
                      ["OUCH!", level: "error",    scope: %w[tram-policy], id: 5, age: 4]
         
     | 
| 
       90 
99 
     | 
    
         
             
                    ]
         
     | 
| 
       91 
100 
     | 
    
         
             
                  end
         
     | 
| 
       92 
101 
     | 
    
         
             
                end
         
     | 
| 
       93 
102 
     | 
    
         | 
| 
       94 
     | 
    
         
            -
                context " 
     | 
| 
      
 103 
     | 
    
         
            +
                context "with no errors:" do
         
     | 
| 
       95 
104 
     | 
    
         
             
                  subject { errors.merge 1 }
         
     | 
| 
       96 
105 
     | 
    
         
             
                  it { is_expected.to eql errors }
         
     | 
| 
       97 
106 
     | 
    
         
             
                end
         
     | 
| 
         @@ -100,26 +109,11 @@ RSpec.describe Tram::Policy::Errors do 
     | 
|
| 
       100 
109 
     | 
    
         
             
              describe "#messages" do
         
     | 
| 
       101 
110 
     | 
    
         
             
                subject { errors.messages }
         
     | 
| 
       102 
111 
     | 
    
         | 
| 
       103 
     | 
    
         
            -
                 
     | 
| 
       104 
     | 
    
         
            -
             
     | 
| 
       105 
     | 
    
         
            -
                context "with errors added:" do
         
     | 
| 
       106 
     | 
    
         
            -
                  before  { errors.add "OMG!", level: "info", field: "name" }
         
     | 
| 
       107 
     | 
    
         
            -
                  it { is_expected.to eq %w[OMG!] }
         
     | 
| 
       108 
     | 
    
         
            -
                end
         
     | 
| 
       109 
     | 
    
         
            -
              end
         
     | 
| 
       110 
     | 
    
         
            -
             
     | 
| 
       111 
     | 
    
         
            -
              describe "#full_messages" do
         
     | 
| 
       112 
     | 
    
         
            -
                subject { errors.full_messages }
         
     | 
| 
       113 
     | 
    
         
            -
             
     | 
| 
       114 
     | 
    
         
            -
                it { is_expected.to eq [] }
         
     | 
| 
       115 
     | 
    
         
            -
             
     | 
| 
       116 
     | 
    
         
            -
                context "with errors added:" do
         
     | 
| 
       117 
     | 
    
         
            -
                  before  { errors.add "OMG!", level: "info", field: "name" }
         
     | 
| 
       118 
     | 
    
         
            -
                  it { is_expected.to eq ["OMG! {:level=>\"info\", :field=>\"name\"}"] }
         
     | 
| 
       119 
     | 
    
         
            -
                end
         
     | 
| 
      
 112 
     | 
    
         
            +
                before { errors.add "OMG!", level: "info", field: "name" }
         
     | 
| 
      
 113 
     | 
    
         
            +
                it { is_expected.to eq errors.map(&:message) }
         
     | 
| 
       120 
114 
     | 
    
         
             
              end
         
     | 
| 
       121 
115 
     | 
    
         | 
| 
       122 
     | 
    
         
            -
              describe "# 
     | 
| 
      
 116 
     | 
    
         
            +
              describe "#filter" do
         
     | 
| 
       123 
117 
     | 
    
         
             
                before do
         
     | 
| 
       124 
118 
     | 
    
         
             
                  errors.add :foo, field: "name",  level: "error"
         
     | 
| 
       125 
119 
     | 
    
         
             
                  errors.add :foo, field: "email", level: "info"
         
     | 
| 
         @@ -127,21 +121,21 @@ RSpec.describe Tram::Policy::Errors do 
     | 
|
| 
       127 
121 
     | 
    
         
             
                end
         
     | 
| 
       128 
122 
     | 
    
         | 
| 
       129 
123 
     | 
    
         
             
                context "with filter" do
         
     | 
| 
       130 
     | 
    
         
            -
                  subject { errors. 
     | 
| 
      
 124 
     | 
    
         
            +
                  subject { errors.filter level: "error" }
         
     | 
| 
       131 
125 
     | 
    
         | 
| 
       132 
126 
     | 
    
         
             
                  it "returns selected errors only" do
         
     | 
| 
       133 
     | 
    
         
            -
                    expect(subject 
     | 
| 
       134 
     | 
    
         
            -
                       
     | 
| 
       135 
     | 
    
         
            -
                       
     | 
| 
      
 127 
     | 
    
         
            +
                    expect(subject).to match_array [
         
     | 
| 
      
 128 
     | 
    
         
            +
                      [:foo, field: "name",  level: "error", scope: %w[tram-policy]],
         
     | 
| 
      
 129 
     | 
    
         
            +
                      [:foo, field: "email", level: "error", scope: %w[tram-policy]]
         
     | 
| 
       136 
130 
     | 
    
         
             
                    ]
         
     | 
| 
       137 
131 
     | 
    
         
             
                  end
         
     | 
| 
       138 
132 
     | 
    
         
             
                end
         
     | 
| 
       139 
133 
     | 
    
         | 
| 
       140 
134 
     | 
    
         
             
                context "without a filter" do
         
     | 
| 
       141 
     | 
    
         
            -
                  subject { errors. 
     | 
| 
      
 135 
     | 
    
         
            +
                  subject { errors.filter }
         
     | 
| 
       142 
136 
     | 
    
         | 
| 
       143 
137 
     | 
    
         
             
                  it "returns selected all errors" do
         
     | 
| 
       144 
     | 
    
         
            -
                    expect(subject 
     | 
| 
      
 138 
     | 
    
         
            +
                    expect(subject).to match_array errors.to_a
         
     | 
| 
       145 
139 
     | 
    
         
             
                  end
         
     | 
| 
       146 
140 
     | 
    
         
             
                end
         
     | 
| 
       147 
141 
     | 
    
         
             
              end
         
     | 
| 
         @@ -57,9 +57,6 @@ RSpec.describe "RSpec support:" do 
     | 
|
| 
       57 
57 
     | 
    
         | 
| 
       58 
58 
     | 
    
         
             
              describe "shared examples" do
         
     | 
| 
       59 
59 
     | 
    
         
             
                it_behaves_like :invalid_policy
         
     | 
| 
       60 
     | 
    
         
            -
                it_behaves_like : 
     | 
| 
       61 
     | 
    
         
            -
                  before { I18n.available_locales = %i[en ru] }
         
     | 
| 
       62 
     | 
    
         
            -
                end
         
     | 
| 
       63 
     | 
    
         
            -
                it_behaves_like :valid_policy,   field: "email"
         
     | 
| 
      
 60 
     | 
    
         
            +
                it_behaves_like :valid_policy, field: "email"
         
     | 
| 
       64 
61 
     | 
    
         
             
              end
         
     | 
| 
       65 
62 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,8 +1,8 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            RSpec.describe Tram::Policy::ValidationError do
         
     | 
| 
       2 
2 
     | 
    
         
             
              subject(:error) { described_class.new policy, filter }
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
     | 
    
         
            -
              let(:one)    { double  
     | 
| 
       5 
     | 
    
         
            -
              let(:two)    { double  
     | 
| 
      
 4 
     | 
    
         
            +
              let(:one)    { double message: "OMG!",  level: "error" }
         
     | 
| 
      
 5 
     | 
    
         
            +
              let(:two)    { double message: "phew!", level: "warning" }
         
     | 
| 
       6 
6 
     | 
    
         
             
              let(:policy) { double :policy, errors: [one, two] }
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
8 
     | 
    
         
             
              shared_examples :exception_with_messages do |text|
         
     | 
    
        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.4.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.4.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: 2018- 
     | 
| 
      
 12 
     | 
    
         
            +
            date: 2018-02-17 00:00:00.000000000 Z
         
     | 
| 
       13 
13 
     | 
    
         
             
            dependencies:
         
     | 
| 
       14 
14 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       15 
15 
     | 
    
         
             
              name: dry-initializer
         
     |