active_interaction 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -3
- data/README.md +8 -6
- data/lib/active_interaction.rb +5 -3
- data/lib/active_interaction/active_model.rb +29 -0
- data/lib/active_interaction/base.rb +82 -116
- data/lib/active_interaction/errors.rb +79 -5
- data/lib/active_interaction/filter.rb +195 -21
- data/lib/active_interaction/filters.rb +26 -0
- data/lib/active_interaction/filters/array_filter.rb +22 -25
- data/lib/active_interaction/filters/boolean_filter.rb +12 -12
- data/lib/active_interaction/filters/date_filter.rb +32 -5
- data/lib/active_interaction/filters/date_time_filter.rb +34 -7
- data/lib/active_interaction/filters/file_filter.rb +12 -9
- data/lib/active_interaction/filters/float_filter.rb +13 -11
- data/lib/active_interaction/filters/hash_filter.rb +36 -17
- data/lib/active_interaction/filters/integer_filter.rb +13 -11
- data/lib/active_interaction/filters/model_filter.rb +15 -15
- data/lib/active_interaction/filters/string_filter.rb +19 -8
- data/lib/active_interaction/filters/symbol_filter.rb +29 -0
- data/lib/active_interaction/filters/time_filter.rb +38 -16
- data/lib/active_interaction/method_missing.rb +18 -0
- data/lib/active_interaction/overload_hash.rb +1 -0
- data/lib/active_interaction/validation.rb +19 -0
- data/lib/active_interaction/version.rb +1 -1
- data/spec/active_interaction/active_model_spec.rb +33 -0
- data/spec/active_interaction/base_spec.rb +54 -48
- data/spec/active_interaction/errors_spec.rb +99 -0
- data/spec/active_interaction/filter_spec.rb +12 -20
- data/spec/active_interaction/filters/array_filter_spec.rb +50 -28
- data/spec/active_interaction/filters/boolean_filter_spec.rb +15 -15
- data/spec/active_interaction/filters/date_filter_spec.rb +30 -18
- data/spec/active_interaction/filters/date_time_filter_spec.rb +31 -19
- data/spec/active_interaction/filters/file_filter_spec.rb +7 -7
- data/spec/active_interaction/filters/float_filter_spec.rb +13 -11
- data/spec/active_interaction/filters/hash_filter_spec.rb +38 -29
- data/spec/active_interaction/filters/integer_filter_spec.rb +18 -8
- data/spec/active_interaction/filters/model_filter_spec.rb +24 -20
- data/spec/active_interaction/filters/string_filter_spec.rb +14 -8
- data/spec/active_interaction/filters/symbol_filter_spec.rb +24 -0
- data/spec/active_interaction/filters/time_filter_spec.rb +33 -69
- data/spec/active_interaction/filters_spec.rb +21 -0
- data/spec/active_interaction/i18n_spec.rb +0 -15
- data/spec/active_interaction/integration/array_interaction_spec.rb +2 -22
- data/spec/active_interaction/integration/hash_interaction_spec.rb +5 -25
- data/spec/active_interaction/integration/symbol_interaction_spec.rb +5 -0
- data/spec/active_interaction/method_missing_spec.rb +69 -0
- data/spec/active_interaction/validation_spec.rb +55 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/support/filters.rb +168 -14
- data/spec/support/interactions.rb +11 -13
- metadata +31 -13
- data/lib/active_interaction/filter_method.rb +0 -13
- data/lib/active_interaction/filter_methods.rb +0 -26
- data/lib/active_interaction/filters/abstract_date_time_filter.rb +0 -25
- data/spec/active_interaction/filter_method_spec.rb +0 -43
- data/spec/active_interaction/filter_methods_spec.rb +0 -30
| @@ -6,41 +6,63 @@ module ActiveInteraction | |
| 6 6 | 
             
                #   in which case they will be processed with `strptime`. If `Time.zone` is
         | 
| 7 7 | 
             
                #   available it will be used so that the values are time zone aware.
         | 
| 8 8 | 
             
                #
         | 
| 9 | 
            -
                # @macro  | 
| 10 | 
            -
                # @option options [String] :format  | 
| 9 | 
            +
                # @macro filter_method_params
         | 
| 10 | 
            +
                # @option options [String] :format parse strings using this format string
         | 
| 11 11 | 
             
                #
         | 
| 12 12 | 
             
                # @example
         | 
| 13 13 | 
             
                #   time :start_date
         | 
| 14 14 | 
             
                #
         | 
| 15 15 | 
             
                # @example
         | 
| 16 | 
            -
                #   date_time :start_date, format: '%Y-%m-%dT%H:%M:%S'
         | 
| 16 | 
            +
                #   date_time :start_date, format: '%Y-%m-%dT%H:%M:%S%z'
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                # @since 0.1.0
         | 
| 17 19 | 
             
                #
         | 
| 18 20 | 
             
                # @method self.time(*attributes, options = {})
         | 
| 19 21 | 
             
              end
         | 
| 20 22 |  | 
| 21 23 | 
             
              # @private
         | 
| 22 | 
            -
              class TimeFilter <  | 
| 23 | 
            -
                def  | 
| 24 | 
            +
              class TimeFilter < Filter
         | 
| 25 | 
            +
                def cast(value)
         | 
| 24 26 | 
             
                  case value
         | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 27 | 
            +
                  when klass
         | 
| 28 | 
            +
                    value
         | 
| 29 | 
            +
                  when Numeric
         | 
| 30 | 
            +
                    time.at(value)
         | 
| 31 | 
            +
                  when String
         | 
| 32 | 
            +
                    begin
         | 
| 33 | 
            +
                      if has_format?
         | 
| 34 | 
            +
                        klass.strptime(value, format)
         | 
| 35 | 
            +
                      else
         | 
| 36 | 
            +
                        klass.parse(value)
         | 
| 37 | 
            +
                      end
         | 
| 38 | 
            +
                    rescue ArgumentError
         | 
| 39 | 
            +
                      super
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
                  else
         | 
| 42 | 
            +
                    super
         | 
| 29 43 | 
             
                  end
         | 
| 30 44 | 
             
                end
         | 
| 31 45 |  | 
| 32 | 
            -
                 | 
| 46 | 
            +
                private
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def format
         | 
| 49 | 
            +
                  options.fetch(:format)
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                def has_format?
         | 
| 53 | 
            +
                  options.has_key?(:format)
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def klass
         | 
| 57 | 
            +
                  time.at(0).class
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                def time
         | 
| 33 61 | 
             
                  if Time.respond_to?(:zone) && !Time.zone.nil?
         | 
| 34 62 | 
             
                    Time.zone
         | 
| 35 63 | 
             
                  else
         | 
| 36 64 | 
             
                    Time
         | 
| 37 65 | 
             
                  end
         | 
| 38 66 | 
             
                end
         | 
| 39 | 
            -
                private_class_method :time
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                def self.time_class
         | 
| 42 | 
            -
                  time.at(0).class
         | 
| 43 | 
            -
                end
         | 
| 44 | 
            -
                private_class_method :time_class
         | 
| 45 67 | 
             
              end
         | 
| 46 68 | 
             
            end
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            module ActiveInteraction
         | 
| 2 | 
            +
              # @private
         | 
| 3 | 
            +
              module MethodMissing
         | 
| 4 | 
            +
                def method_missing(slug, *args, &block)
         | 
| 5 | 
            +
                  begin
         | 
| 6 | 
            +
                    klass = Filter.factory(slug)
         | 
| 7 | 
            +
                  rescue MissingFilterError
         | 
| 8 | 
            +
                    super
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  options = args.last.is_a?(Hash) ? args.pop : {}
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  yield(klass, args, options) if block_given?
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  self
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            module ActiveInteraction
         | 
| 2 | 
            +
              # @private
         | 
| 3 | 
            +
              module Validation
         | 
| 4 | 
            +
                def self.validate(filters, inputs)
         | 
| 5 | 
            +
                  filters.reduce([]) do |errors, filter|
         | 
| 6 | 
            +
                    begin
         | 
| 7 | 
            +
                      filter.cast(inputs[filter.name])
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                      errors
         | 
| 10 | 
            +
                    rescue InvalidValueError
         | 
| 11 | 
            +
                      type = I18n.translate("#{Base.i18n_scope}.types.#{filter.class.slug}")
         | 
| 12 | 
            +
                      errors << [filter.name, :invalid, nil, type: type]
         | 
| 13 | 
            +
                    rescue MissingValueError
         | 
| 14 | 
            +
                      errors << [filter.name, :missing]
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe ActiveInteraction::ActiveModel do
         | 
| 4 | 
            +
              subject(:model) do
         | 
| 5 | 
            +
                Class.new do
         | 
| 6 | 
            +
                  include ActiveInteraction::ActiveModel
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              describe '#new_record?' do
         | 
| 11 | 
            +
                it 'returns true' do
         | 
| 12 | 
            +
                  expect(model.new).to be_new_record
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              describe '#persisted?' do
         | 
| 17 | 
            +
                it 'returns false' do
         | 
| 18 | 
            +
                  expect(model.new).to_not be_persisted
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              describe '.i18n_scope' do
         | 
| 23 | 
            +
                it 'returns the scope' do
         | 
| 24 | 
            +
                  expect(model.i18n_scope).to eq :active_interaction
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              describe '#i18n_scope' do
         | 
| 29 | 
            +
                it 'returns the scope' do
         | 
| 30 | 
            +
                  expect(model.new.i18n_scope).to eq :active_interaction
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| @@ -1,7 +1,8 @@ | |
| 1 1 | 
             
            require 'spec_helper'
         | 
| 2 2 |  | 
| 3 3 | 
             
            describe ActiveInteraction::Base do
         | 
| 4 | 
            -
               | 
| 4 | 
            +
              include_context 'interactions'
         | 
| 5 | 
            +
             | 
| 5 6 | 
             
              subject(:interaction) { described_class.new(options) }
         | 
| 6 7 |  | 
| 7 8 | 
             
              class InteractionWithFilter < described_class
         | 
| @@ -13,27 +14,29 @@ describe ActiveInteraction::Base do | |
| 13 14 | 
             
              end
         | 
| 14 15 |  | 
| 15 16 | 
             
              describe '.new(options = {})' do
         | 
| 16 | 
            -
                it 'does not allow : | 
| 17 | 
            -
                   | 
| 18 | 
            -
                   | 
| 17 | 
            +
                it 'does not allow :_interaction_* as an option' do
         | 
| 18 | 
            +
                  key = :"_interaction_#{SecureRandom.hex}"
         | 
| 19 | 
            +
                  options.merge!(key => nil)
         | 
| 20 | 
            +
                  expect {
         | 
| 21 | 
            +
                    interaction
         | 
| 22 | 
            +
                  }.to raise_error ActiveInteraction::InvalidValueError
         | 
| 19 23 | 
             
                end
         | 
| 20 24 |  | 
| 21 | 
            -
                it 'does not allow " | 
| 22 | 
            -
                   | 
| 23 | 
            -
                   | 
| 25 | 
            +
                it 'does not allow "_interaction_*" as an option' do
         | 
| 26 | 
            +
                  key = "_interaction_#{SecureRandom.hex}"
         | 
| 27 | 
            +
                  options.merge!(key => nil)
         | 
| 28 | 
            +
                  expect {
         | 
| 29 | 
            +
                    interaction
         | 
| 30 | 
            +
                  }.to raise_error ActiveInteraction::InvalidValueError
         | 
| 24 31 | 
             
                end
         | 
| 25 32 |  | 
| 26 33 | 
             
                context 'with an attribute' do
         | 
| 27 34 | 
             
                  let(:described_class) do
         | 
| 28 | 
            -
                    Class.new( | 
| 35 | 
            +
                    Class.new(TestInteraction) do
         | 
| 29 36 | 
             
                      attr_reader :thing
         | 
| 30 37 |  | 
| 31 38 | 
             
                      validates :thing, presence: true
         | 
| 32 39 |  | 
| 33 | 
            -
                      def self.name
         | 
| 34 | 
            -
                        SecureRandom.hex
         | 
| 35 | 
            -
                      end
         | 
| 36 | 
            -
             | 
| 37 40 | 
             
                      def execute
         | 
| 38 41 | 
             
                        thing
         | 
| 39 42 | 
             
                      end
         | 
| @@ -104,6 +107,14 @@ describe ActiveInteraction::Base do | |
| 104 107 | 
             
                  }.to raise_error NoMethodError
         | 
| 105 108 | 
             
                end
         | 
| 106 109 |  | 
| 110 | 
            +
                it do
         | 
| 111 | 
            +
                  expect do
         | 
| 112 | 
            +
                    Class.new(described_class) do
         | 
| 113 | 
            +
                      float :_interaction_thing
         | 
| 114 | 
            +
                    end
         | 
| 115 | 
            +
                  end.to raise_error ActiveInteraction::InvalidFilterError
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
             | 
| 107 118 | 
             
                context 'with a filter' do
         | 
| 108 119 | 
             
                  let(:described_class) { InteractionWithFilter }
         | 
| 109 120 |  | 
| @@ -142,41 +153,35 @@ describe ActiveInteraction::Base do | |
| 142 153 | 
             
                let(:thing) { rand }
         | 
| 143 154 |  | 
| 144 155 | 
             
                describe '.run(options = {})' do
         | 
| 145 | 
            -
                  subject(:outcome) { described_class.run(options) }
         | 
| 146 | 
            -
             | 
| 147 156 | 
             
                  it "returns an instance of #{described_class}" do
         | 
| 148 157 | 
             
                    expect(outcome).to be_a described_class
         | 
| 149 158 | 
             
                  end
         | 
| 150 159 |  | 
| 151 160 | 
             
                  context 'setting the result' do
         | 
| 152 161 | 
             
                    let(:described_class) do
         | 
| 153 | 
            -
                      Class.new( | 
| 162 | 
            +
                      Class.new(TestInteraction) do
         | 
| 154 163 | 
             
                        boolean :attribute
         | 
| 155 164 |  | 
| 156 165 | 
             
                        validate do
         | 
| 157 166 | 
             
                          @_interaction_result = SecureRandom.hex
         | 
| 158 167 | 
             
                          errors.add(:attribute, SecureRandom.hex)
         | 
| 159 168 | 
             
                        end
         | 
| 160 | 
            -
             | 
| 161 | 
            -
                        def self.name
         | 
| 162 | 
            -
                          SecureRandom.hex
         | 
| 163 | 
            -
                        end
         | 
| 164 169 | 
             
                      end
         | 
| 165 170 | 
             
                    end
         | 
| 166 171 |  | 
| 167 172 | 
             
                    it 'sets the result to nil' do
         | 
| 168 173 | 
             
                      expect(outcome).to be_invalid
         | 
| 169 | 
            -
                      expect( | 
| 174 | 
            +
                      expect(result).to be_nil
         | 
| 170 175 | 
             
                    end
         | 
| 171 176 | 
             
                  end
         | 
| 172 177 |  | 
| 173 178 | 
             
                  context 'failing validations' do
         | 
| 174 179 | 
             
                    it 'returns an invalid outcome' do
         | 
| 175 | 
            -
                      expect(outcome). | 
| 180 | 
            +
                      expect(outcome).to_not be_valid
         | 
| 176 181 | 
             
                    end
         | 
| 177 182 |  | 
| 178 183 | 
             
                    it 'sets the result to nil' do
         | 
| 179 | 
            -
                      expect( | 
| 184 | 
            +
                      expect(result).to be_nil
         | 
| 180 185 | 
             
                    end
         | 
| 181 186 | 
             
                  end
         | 
| 182 187 |  | 
| @@ -187,12 +192,15 @@ describe ActiveInteraction::Base do | |
| 187 192 | 
             
                      before do
         | 
| 188 193 | 
             
                        @execute = described_class.instance_method(:execute)
         | 
| 189 194 | 
             
                        described_class.send(:define_method, :execute) do
         | 
| 190 | 
            -
                          errors.add(:thing,  | 
| 195 | 
            +
                          errors.add(:thing, 'error')
         | 
| 196 | 
            +
                          errors.add_sym(:thing, :error, 'error')
         | 
| 191 197 | 
             
                        end
         | 
| 192 198 | 
             
                      end
         | 
| 193 199 |  | 
| 194 200 | 
             
                      after do
         | 
| 195 | 
            -
                         | 
| 201 | 
            +
                        silence_warnings do
         | 
| 202 | 
            +
                          described_class.send(:define_method, :execute, @execute)
         | 
| 203 | 
            +
                        end
         | 
| 196 204 | 
             
                      end
         | 
| 197 205 |  | 
| 198 206 | 
             
                      it 'returns an invalid outcome' do
         | 
| @@ -200,7 +208,15 @@ describe ActiveInteraction::Base do | |
| 200 208 | 
             
                      end
         | 
| 201 209 |  | 
| 202 210 | 
             
                      it 'sets the result to nil' do
         | 
| 203 | 
            -
                        expect( | 
| 211 | 
            +
                        expect(result).to be_nil
         | 
| 212 | 
            +
                      end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                      it 'has errors' do
         | 
| 215 | 
            +
                        expect(outcome.errors.messages[:thing]).to eq %w(error error)
         | 
| 216 | 
            +
                      end
         | 
| 217 | 
            +
             | 
| 218 | 
            +
                      it 'has symbolic errors' do
         | 
| 219 | 
            +
                        expect(outcome.errors.symbolic[:thing]).to eq [:error]
         | 
| 204 220 | 
             
                      end
         | 
| 205 221 | 
             
                    end
         | 
| 206 222 |  | 
| @@ -209,7 +225,7 @@ describe ActiveInteraction::Base do | |
| 209 225 | 
             
                    end
         | 
| 210 226 |  | 
| 211 227 | 
             
                    it 'sets the result' do
         | 
| 212 | 
            -
                      expect( | 
| 228 | 
            +
                      expect(result).to eq thing
         | 
| 213 229 | 
             
                    end
         | 
| 214 230 |  | 
| 215 231 | 
             
                    it 'calls transaction' do
         | 
| @@ -246,7 +262,7 @@ describe ActiveInteraction::Base do | |
| 246 262 | 
             
                    it 'raises an error' do
         | 
| 247 263 | 
             
                      expect {
         | 
| 248 264 | 
             
                        result
         | 
| 249 | 
            -
                      }.to raise_error ActiveInteraction:: | 
| 265 | 
            +
                      }.to raise_error ActiveInteraction::InteractionInvalidError
         | 
| 250 266 | 
             
                    end
         | 
| 251 267 | 
             
                  end
         | 
| 252 268 |  | 
| @@ -260,33 +276,23 @@ describe ActiveInteraction::Base do | |
| 260 276 | 
             
                end
         | 
| 261 277 | 
             
              end
         | 
| 262 278 |  | 
| 263 | 
            -
              describe '# | 
| 264 | 
            -
                 | 
| 265 | 
            -
             | 
| 266 | 
            -
                 | 
| 267 | 
            -
              end
         | 
| 268 | 
            -
             | 
| 269 | 
            -
              describe '#new_record?' do
         | 
| 270 | 
            -
                it 'returns true' do
         | 
| 271 | 
            -
                  expect(interaction).to be_new_record
         | 
| 272 | 
            -
                end
         | 
| 273 | 
            -
              end
         | 
| 279 | 
            +
              describe '#inputs' do
         | 
| 280 | 
            +
                let(:described_class) { InteractionWithFilter }
         | 
| 281 | 
            +
                let(:other_val) { SecureRandom.hex }
         | 
| 282 | 
            +
                let(:options) { {thing: 1, other: other_val} }
         | 
| 274 283 |  | 
| 275 | 
            -
             | 
| 276 | 
            -
             | 
| 277 | 
            -
                  expect(interaction).to_not be_persisted
         | 
| 284 | 
            +
                it 'casts filtered inputs' do
         | 
| 285 | 
            +
                  expect(interaction.inputs[:thing]).to eql 1.0
         | 
| 278 286 | 
             
                end
         | 
| 279 | 
            -
              end
         | 
| 280 287 |  | 
| 281 | 
            -
             | 
| 282 | 
            -
             | 
| 283 | 
            -
                  expect(described_class.i18n_scope).to eq :active_interaction
         | 
| 288 | 
            +
                it 'strips non-filtered inputs' do
         | 
| 289 | 
            +
                  expect(interaction.inputs).to_not have_key(:other)
         | 
| 284 290 | 
             
                end
         | 
| 285 291 | 
             
              end
         | 
| 286 292 |  | 
| 287 | 
            -
              describe '# | 
| 288 | 
            -
                it ' | 
| 289 | 
            -
                  expect | 
| 293 | 
            +
              describe '#execute' do
         | 
| 294 | 
            +
                it 'raises an error' do
         | 
| 295 | 
            +
                  expect { interaction.execute }.to raise_error NotImplementedError
         | 
| 290 296 | 
             
                end
         | 
| 291 297 | 
             
              end
         | 
| 292 298 | 
             
            end
         | 
| @@ -0,0 +1,99 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe ActiveInteraction::Errors do
         | 
| 4 | 
            +
              let(:klass) do
         | 
| 5 | 
            +
                Class.new do
         | 
| 6 | 
            +
                  include ActiveModel::Model
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  attr_reader :attribute
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def self.name
         | 
| 11 | 
            +
                    SecureRandom.hex
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              subject(:errors) { described_class.new(klass.new) }
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              describe '#add_sym' do
         | 
| 19 | 
            +
                it 'defaults to :invalid' do
         | 
| 20 | 
            +
                  errors.add_sym(:attribute)
         | 
| 21 | 
            +
                  expect(errors.symbolic).to eq({ attribute: [:invalid] })
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                it 'adds a symbol' do
         | 
| 25 | 
            +
                  errors.add_sym(:attribute, :symbol)
         | 
| 26 | 
            +
                  expect(errors.symbolic).to eq({ attribute: [:symbol] })
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                it 'accepts a message' do
         | 
| 30 | 
            +
                  errors.add_sym(:attribute, :symbol, 'message')
         | 
| 31 | 
            +
                  expect(errors.symbolic).to eq({ attribute: [:symbol] })
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                it 'accepts a message and options' do
         | 
| 35 | 
            +
                  errors.add_sym(:attribute, :symbol, 'message', { key: :value })
         | 
| 36 | 
            +
                  expect(errors.symbolic).to eq({ attribute: [:symbol] })
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                context 'calling #add' do
         | 
| 40 | 
            +
                  before do
         | 
| 41 | 
            +
                    allow(errors).to receive(:add)
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  it 'with the default' do
         | 
| 45 | 
            +
                    errors.add_sym(:attribute)
         | 
| 46 | 
            +
                    expect(errors).to have_received(:add).once.
         | 
| 47 | 
            +
                      with(:attribute, :invalid, {})
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  it 'with a symbol' do
         | 
| 51 | 
            +
                    errors.add_sym(:attribute, :symbol)
         | 
| 52 | 
            +
                    expect(errors).to have_received(:add).once.
         | 
| 53 | 
            +
                      with(:attribute, :symbol, {})
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  it 'with a symbol and message' do
         | 
| 57 | 
            +
                    errors.add_sym(:attribute, :symbol, 'message')
         | 
| 58 | 
            +
                    expect(errors).to have_received(:add).once.
         | 
| 59 | 
            +
                      with(:attribute, 'message', {})
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  it 'with a symbol, message and options' do
         | 
| 63 | 
            +
                    errors.add_sym(:attribute, :symbol, 'message', { key: :value })
         | 
| 64 | 
            +
                    expect(errors).to have_received(:add).once.
         | 
| 65 | 
            +
                      with(:attribute, 'message', { key: :value })
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
              describe '#initialize' do
         | 
| 71 | 
            +
                it 'sets symbolic to an empty hash' do
         | 
| 72 | 
            +
                  expect(errors.symbolic).to eq({})
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              describe '#initialize_dup' do
         | 
| 77 | 
            +
                let(:errors_dup) { errors.dup }
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                before do
         | 
| 80 | 
            +
                  errors.add_sym(:attribute)
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                it 'dups symbolic' do
         | 
| 84 | 
            +
                  expect(errors_dup.symbolic).to eq errors.symbolic
         | 
| 85 | 
            +
                  expect(errors_dup.symbolic).to_not equal errors.symbolic
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
              end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
              describe '#clear' do
         | 
| 90 | 
            +
                before do
         | 
| 91 | 
            +
                  errors.add_sym(:attribute)
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                it 'clears symbolic' do
         | 
| 95 | 
            +
                  errors.clear
         | 
| 96 | 
            +
                  expect(errors.symbolic).to be_empty
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
              end
         | 
| 99 | 
            +
            end
         |