active_interaction 0.10.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -1
  3. data/README.md +32 -35
  4. data/lib/active_interaction.rb +14 -6
  5. data/lib/active_interaction/base.rb +155 -135
  6. data/lib/active_interaction/concerns/active_modelable.rb +46 -0
  7. data/lib/active_interaction/{modules/overload_hash.rb → concerns/hashable.rb} +5 -1
  8. data/lib/active_interaction/concerns/missable.rb +47 -0
  9. data/lib/active_interaction/concerns/runnable.rb +156 -0
  10. data/lib/active_interaction/errors.rb +50 -14
  11. data/lib/active_interaction/filter.rb +33 -45
  12. data/lib/active_interaction/filters/abstract_date_time_filter.rb +24 -15
  13. data/lib/active_interaction/filters/abstract_filter.rb +18 -0
  14. data/lib/active_interaction/filters/abstract_numeric_filter.rb +13 -8
  15. data/lib/active_interaction/filters/array_filter.rb +42 -35
  16. data/lib/active_interaction/filters/boolean_filter.rb +8 -11
  17. data/lib/active_interaction/filters/date_filter.rb +11 -15
  18. data/lib/active_interaction/filters/date_time_filter.rb +11 -15
  19. data/lib/active_interaction/filters/file_filter.rb +11 -11
  20. data/lib/active_interaction/filters/float_filter.rb +7 -15
  21. data/lib/active_interaction/filters/hash_filter.rb +18 -24
  22. data/lib/active_interaction/filters/integer_filter.rb +7 -14
  23. data/lib/active_interaction/filters/model_filter.rb +13 -14
  24. data/lib/active_interaction/filters/string_filter.rb +11 -14
  25. data/lib/active_interaction/filters/symbol_filter.rb +6 -9
  26. data/lib/active_interaction/filters/time_filter.rb +13 -16
  27. data/lib/active_interaction/modules/validation.rb +9 -4
  28. data/lib/active_interaction/version.rb +5 -2
  29. data/spec/active_interaction/base_spec.rb +109 -4
  30. data/spec/active_interaction/{modules/active_model_spec.rb → concerns/active_modelable_spec.rb} +15 -3
  31. data/spec/active_interaction/{modules/overload_hash_spec.rb → concerns/hashable_spec.rb} +2 -3
  32. data/spec/active_interaction/{modules/method_missing_spec.rb → concerns/missable_spec.rb} +38 -3
  33. data/spec/active_interaction/concerns/runnable_spec.rb +192 -0
  34. data/spec/active_interaction/filters/abstract_filter_spec.rb +8 -0
  35. data/spec/active_interaction/filters/abstract_numeric_filter_spec.rb +1 -1
  36. data/spec/active_interaction/modules/validation_spec.rb +2 -2
  37. data/spec/support/concerns.rb +15 -0
  38. data/spec/support/filters.rb +5 -5
  39. data/spec/support/interactions.rb +6 -5
  40. metadata +47 -73
  41. data/lib/active_interaction/filters.rb +0 -28
  42. data/lib/active_interaction/modules/active_model.rb +0 -32
  43. data/lib/active_interaction/modules/core.rb +0 -70
  44. data/lib/active_interaction/modules/method_missing.rb +0 -20
  45. data/spec/active_interaction/filters_spec.rb +0 -23
  46. data/spec/active_interaction/modules/core_spec.rb +0 -114
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 88026615847767352e357dadf32abd0a6a114889
4
- data.tar.gz: 5dd283e9aee1bab91a73ea20d52a7c8c24d5bed9
3
+ metadata.gz: 02f4ec454ba99aa3327ab6d539a8dd2f6e9b8622
4
+ data.tar.gz: e20fe1fb15d09040a7c00379962f53ac41d26cf1
5
5
  SHA512:
6
- metadata.gz: 0d6082582172127078839165df075f8de2f222a3ed6822f52ad69270c6240e9d3f3eacfbfc95e3bd4989e21e43d371182b3ca1a09a9639ac14546d7c76b604d6
7
- data.tar.gz: 6e81489a89de567be04f3f8713f88a0af5959de1fd2954b3731b1ce6ff7335615d423a59a428bd2081aa64a6a6dfede40e422005fbbb60cf3737f2a9e791ec31
6
+ metadata.gz: ab410e55639bb75899387091d50213f0b53b4d58de8b4cc0c3cc36d9c0f2acdc247bfe88662d276ed635a3fc695631a291c6c1f6234a7a46c38b4615127b3c7f
7
+ data.tar.gz: f48693acfcc32d503fdc03af08cad9b2ed1a4c7fb32b7dac467f30e325ab97f3647c7b79d865686d26a1cd93fb04ca9b449e52a8d247086a3bd1cb27ab85aad2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # [Master][]
2
2
 
3
+ # [1.0.0][]
4
+
5
+ - **Replace `Filters` with a hash.** To iterate over `Filter` objects, use
6
+ `Interaction.filters.values`.
7
+ - Rename `Filter#has_default?` to `Filter#default?`.
8
+ - Add `respond_to_missing?` to complement `method_missing` calls.
9
+ - Add predicate methods for checking if an input was passed.
10
+ - When adding a filter that shares a name with an existing filter, it will now
11
+ replace the existing one instead of adding a duplicate.
12
+ - Allow fetching filters by name.
13
+ - Allow import filters from another interaction with `import_filters`.
14
+
3
15
  # [0.10.2][] (2014-01-02)
4
16
 
5
17
  - Fix a bug that marked Time instances as invalid if Time.zone was set.
@@ -106,7 +118,8 @@
106
118
 
107
119
  - Initial release.
108
120
 
109
- [master]: https://github.com/orgsync/active_interaction/compare/v0.10.2...master
121
+ [master]: https://github.com/orgsync/active_interaction/compare/v1.0.0...master
122
+ [1.0.0]: https://github.com/orgsync/active_interaction/compare/v0.10.2...v1.0.0
110
123
  [0.10.2]: https://github.com/orgsync/active_interaction/compare/v0.10.1...v0.10.2
111
124
  [0.10.1]: https://github.com/orgsync/active_interaction/compare/v0.10.0...v0.10.1
112
125
  [0.10.0]: https://github.com/orgsync/active_interaction/compare/v0.9.1...v0.10.0
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
- # ActiveInteraction
1
+ # [ActiveInteraction][0]
2
2
 
3
- [![Gem Version][]][1]
4
- [![Build Status][]][2]
5
- [![Coverage Status][]][3]
6
- [![Code Climate][]][4]
7
- [![Dependency Status][]][5]
3
+ [![Gem Version][1]][2]
4
+ [![Build Status][3]][4]
5
+ [![Coverage Status][5]][6]
6
+ [![Code Climate][7]][8]
7
+ [![Dependency Status][9]][10]
8
8
 
9
9
  At first it seemed alright. A little business logic in a controller
10
10
  or model wasn't going to hurt anything. Then one day you wake up
@@ -15,17 +15,17 @@ to this.
15
15
  Take back control. Slim down models and wrangle monstrous controller
16
16
  methods with ActiveInteraction.
17
17
 
18
- Read more on the [project page][] or check out the full [documentation][]
18
+ Read more on the [project page][11] or check out the full [documentation][12]
19
19
  on RubyDoc.info.
20
20
 
21
21
  ## Installation
22
22
 
23
- This project uses [semantic versioning][].
23
+ This project uses [semantic versioning][13].
24
24
 
25
25
  Add it to your Gemfile:
26
26
 
27
27
  ```ruby
28
- gem 'active_interaction', '~> 0.10.2'
28
+ gem 'active_interaction', '~> 1.0'
29
29
  ```
30
30
 
31
31
  And then execute:
@@ -198,12 +198,10 @@ end
198
198
  end
199
199
  ```
200
200
 
201
- Check out the [documentation][] for a full list of methods.
201
+ Check out the [documentation][12] for a full list of methods.
202
202
 
203
203
  ## How do I compose interactions?
204
204
 
205
- (Note: this feature is experimental. See [#41][] & [#79][].)
206
-
207
205
  You can run interactions from within other interactions by calling `compose`.
208
206
  If the interaction is successful, it'll return the result (just like if you had
209
207
  called it with `run!`). If something went wrong, execution will halt
@@ -278,26 +276,25 @@ p Interaction.run.errors.messages
278
276
 
279
277
  ## Credits
280
278
 
281
- ActiveInteraction is brought to you by [@AaronLasseigne][] and
282
- [@tfausak][] from [@orgsync][]. We were inspired by the fantastic
283
- work done in [Mutations][].
284
-
285
- [#41]: https://github.com/orgsync/active_interaction/issues/41
286
- [#79]: https://github.com/orgsync/active_interaction/issues/79
287
- [1]: https://badge.fury.io/rb/active_interaction "Gem Version"
288
- [2]: https://travis-ci.org/orgsync/active_interaction "Build Status"
289
- [3]: https://coveralls.io/r/orgsync/active_interaction "Coverage Status"
290
- [4]: https://codeclimate.com/github/orgsync/active_interaction "Code Climate"
291
- [5]: https://gemnasium.com/orgsync/active_interaction "Dependency Status"
292
- [@AaronLasseigne]: https://github.com/AaronLasseigne
293
- [@orgsync]: https://github.com/orgsync
294
- [@tfausak]: https://github.com/tfausak
295
- [build status]: https://travis-ci.org/orgsync/active_interaction.png
296
- [code climate]: https://codeclimate.com/github/orgsync/active_interaction.png
297
- [coverage status]: https://coveralls.io/repos/orgsync/active_interaction/badge.png
298
- [dependency status]: https://gemnasium.com/orgsync/active_interaction.png
299
- [documentation]: http://rubydoc.info/github/orgsync/active_interaction
300
- [gem version]: https://badge.fury.io/rb/active_interaction.png
301
- [mutations]: https://github.com/cypriss/mutations
302
- [project page]: http://orgsync.github.io/active_interaction/
303
- [semantic versioning]: http://semver.org/spec/v2.0.0.html
279
+ ActiveInteraction is brought to you by [@AaronLasseigne][14] and
280
+ [@tfausak][15] from [@orgsync][16]. We were inspired by the fantastic
281
+ work done in [Mutations][17].
282
+
283
+ [0]: https://github.com/orgsync/active_interaction
284
+ [1]: https://badge.fury.io/rb/active_interaction.png
285
+ [2]: https://badge.fury.io/rb/active_interaction "Gem Version"
286
+ [3]: https://travis-ci.org/orgsync/active_interaction.png
287
+ [4]: https://travis-ci.org/orgsync/active_interaction "Build Status"
288
+ [5]: https://coveralls.io/repos/orgsync/active_interaction/badge.png
289
+ [6]: https://coveralls.io/r/orgsync/active_interaction "Coverage Status"
290
+ [7]: https://codeclimate.com/github/orgsync/active_interaction.png
291
+ [8]: https://codeclimate.com/github/orgsync/active_interaction "Code Climate"
292
+ [9]: https://gemnasium.com/orgsync/active_interaction.png
293
+ [10]: https://gemnasium.com/orgsync/active_interaction "Dependency Status"
294
+ [11]: http://orgsync.github.io/active_interaction/
295
+ [12]: http://rubydoc.info/github/orgsync/active_interaction
296
+ [13]: http://semver.org/spec/v2.0.0.html
297
+ [14]: https://github.com/AaronLasseigne
298
+ [15]: https://github.com/tfausak
299
+ [16]: https://github.com/orgsync
300
+ [17]: https://github.com/cypriss/mutations
@@ -5,14 +5,15 @@ require 'active_model'
5
5
  require 'active_interaction/version'
6
6
  require 'active_interaction/errors'
7
7
 
8
- require 'active_interaction/modules/active_model'
9
- require 'active_interaction/modules/core'
10
- require 'active_interaction/modules/method_missing'
11
- require 'active_interaction/modules/overload_hash'
8
+ require 'active_interaction/concerns/active_modelable'
9
+ require 'active_interaction/concerns/hashable'
10
+ require 'active_interaction/concerns/missable'
11
+ require 'active_interaction/concerns/runnable'
12
+
12
13
  require 'active_interaction/modules/validation'
13
14
 
14
15
  require 'active_interaction/filter'
15
- require 'active_interaction/filters'
16
+ require 'active_interaction/filters/abstract_filter'
16
17
  require 'active_interaction/filters/abstract_date_time_filter'
17
18
  require 'active_interaction/filters/abstract_numeric_filter'
18
19
  require 'active_interaction/filters/array_filter'
@@ -34,5 +35,12 @@ I18n.backend.load_translations(
34
35
  Dir[File.join(%w(lib active_interaction locale *.yml))]
35
36
  )
36
37
 
37
- # @since 0.1.0
38
+ # Manage application specific business logic.
39
+ #
40
+ # @author Aaron Lasseigne <aaron.lasseigne@gmail.com>
41
+ # @author Taylor Fausak <taylor@fausak.me>
42
+ #
43
+ # @since 1.0.0
44
+ #
45
+ # @version 1.0.0
38
46
  module ActiveInteraction end
@@ -4,199 +4,219 @@ require 'active_support/core_ext/hash/indifferent_access'
4
4
 
5
5
  module ActiveInteraction
6
6
  # @abstract Subclass and override {#execute} to implement a custom
7
- # ActiveInteraction class.
7
+ # ActiveInteraction::Base class.
8
+ #
9
+ # Provides interaction functionality. Subclass this to create an interaction.
8
10
  #
9
11
  # @example
10
12
  # class ExampleInteraction < ActiveInteraction::Base
11
13
  # # Required
12
- # integer :a, :b
14
+ # boolean :a
13
15
  #
14
16
  # # Optional
15
- # integer :c, default: nil
17
+ # boolean :b, default: false
16
18
  #
17
19
  # def execute
18
- # sum = a + b
19
- # c.nil? ? sum : sum + c
20
+ # a && b
20
21
  # end
21
22
  # end
22
23
  #
23
- # outcome = ExampleInteraction.run(a: 1, b: 2, c: 3)
24
+ # outcome = ExampleInteraction.run(a: true)
24
25
  # if outcome.valid?
25
- # p outcome.result
26
+ # outcome.result
26
27
  # else
27
- # p outcome.errors
28
+ # outcome.errors
28
29
  # end
29
30
  class Base
30
- include ActiveModel
31
+ include ActiveModelable
32
+ include Runnable
33
+
34
+ validate :input_errors
35
+
36
+ class << self
37
+ include Hashable
38
+ include Missable
39
+
40
+ # Get or set the description.
41
+ #
42
+ # @example
43
+ # core.desc
44
+ # # => nil
45
+ # core.desc('Description!')
46
+ # core.desc
47
+ # # => "Description!"
48
+ #
49
+ # @param desc [String, nil] What to set the description to.
50
+ #
51
+ # @return [String, nil] The description.
52
+ def desc(desc = nil)
53
+ if desc.nil?
54
+ unless instance_variable_defined?(:@_interaction_desc)
55
+ @_interaction_desc = nil
56
+ end
57
+ else
58
+ @_interaction_desc = desc
59
+ end
31
60
 
32
- extend Core
33
- extend MethodMissing
34
- extend OverloadHash
61
+ @_interaction_desc
62
+ end
35
63
 
36
- validate :input_errors, :runtime_errors
64
+ # Get all the filters defined on this interaction.
65
+ #
66
+ # @return [Hash{Symbol => Filter}]
67
+ def filters
68
+ @_interaction_filters ||= {}
69
+ end
37
70
 
38
- # @param inputs [Hash{Symbol => Object}] Attribute values to set.
39
- #
40
- # @private
41
- def initialize(inputs = {})
42
- fail ArgumentError, 'inputs must be a hash' unless inputs.is_a?(Hash)
71
+ # @private
72
+ def method_missing(*args, &block)
73
+ super do |klass, names, options|
74
+ fail InvalidFilterError, 'missing attribute name' if names.empty?
43
75
 
44
- @_interaction_errors = Errors.new(self)
45
- @_interaction_result = nil
46
- @_interaction_runtime_errors = nil
76
+ names.each { |name| add_filter(klass, name, options, &block) }
77
+ end
78
+ end
47
79
 
48
- process_inputs(inputs.symbolize_keys)
49
- end
80
+ # @!method run(inputs = {})
81
+ # Runs validations and if there are no errors it will call {#execute}.
82
+ #
83
+ # @param (see ActiveInteraction::Base#initialize)
84
+ #
85
+ # @return [Base]
86
+ loop
87
+
88
+ # @!method run!(inputs = {})
89
+ # Like {.run} except that it returns the value of {#execute} or raises
90
+ # an exception if there were any validation errors.
91
+ #
92
+ # @param (see ActiveInteraction::Base.run)
93
+ #
94
+ # @return (see ActiveInteraction::Runnable::ClassMethods#run!)
95
+ #
96
+ # @raise (see ActiveInteraction::Runnable::ClassMethods#run!)
97
+ loop
98
+
99
+ private
100
+
101
+ # @param klass [Class]
102
+ # @param name [Symbol]
103
+ # @param options [Hash]
104
+ def add_filter(klass, name, options, &block)
105
+ fail InvalidFilterError, name.inspect if reserved?(name)
106
+
107
+ filter = klass.new(name, options, &block)
108
+ filters[name] = filter
109
+ attr_accessor name
110
+ define_method("#{name}?") { !public_send(name).nil? }
111
+
112
+ filter.default if filter.default?
113
+ end
50
114
 
51
- # Returns the inputs provided to {.run} or {.run!} after being cast based
52
- # on the filters in the class.
53
- #
54
- # @return [Hash{Symbol => Object}] All inputs passed to {.run} or {.run!}.
55
- #
56
- # @since 0.6.0
57
- def inputs
58
- self.class.filters.each_with_object({}) do |filter, h|
59
- h[filter.name] = send(filter.name)
115
+ # Import filters from another interaction.
116
+ #
117
+ # @param klass [Class] The other interaction.
118
+ # @param options [Hash]
119
+ #
120
+ # @option options [Array<Symbol>, nil] :only Import only these filters.
121
+ # @option options [Array<Symbol>, nil] :except Import all filters except
122
+ # for these.
123
+ #
124
+ # @return (see .filters)
125
+ #
126
+ # @!visibility public
127
+ def import_filters(klass, options = {})
128
+ if options.key?(:only) && options.key?(:except)
129
+ fail ArgumentError, 'given both :only and :except'
130
+ end
131
+
132
+ only = options[:only]
133
+ except = options[:except]
134
+
135
+ other_filters = klass.filters.dup
136
+ other_filters.select! { |k, _| only.include?(k) } if only
137
+ other_filters.reject! { |k, _| except.include?(k) } if except
138
+
139
+ filters.merge!(other_filters)
60
140
  end
61
- end
62
141
 
63
- # Runs the business logic associated with the interaction. The method is
64
- # only run when there are no validation errors. The return value is
65
- # placed into {#result}. This method must be overridden in the subclass.
66
- # This method is run in a transaction if ActiveRecord is available.
67
- #
68
- # @raise [NotImplementedError] if the method is not defined.
69
- #
70
- # @abstract
71
- def execute
72
- fail NotImplementedError
73
- end
142
+ # @param klass [Class]
143
+ def inherited(klass)
144
+ klass.instance_variable_set(:@_interaction_filters, filters.dup)
145
+ end
74
146
 
75
- # Returns the output from {#execute} if there are no validation errors or
76
- # `nil` otherwise.
77
- #
78
- # @return [Object, nil] the output or nil if there were validation errors
79
- def result
80
- @_interaction_result
147
+ # @param symbol [Symbol]
148
+ #
149
+ # @return [Boolean]
150
+ def reserved?(symbol)
151
+ symbol.to_s.start_with?('_interaction_')
152
+ end
81
153
  end
82
154
 
155
+ # @param inputs [Hash{Symbol => Object}] Attribute values to set.
156
+ #
83
157
  # @private
84
- def errors
85
- @_interaction_errors
86
- end
158
+ def initialize(inputs = {})
159
+ fail ArgumentError, 'inputs must be a hash' unless inputs.is_a?(Hash)
87
160
 
88
- # @private
89
- def valid?(*args)
90
- super(*args) || (@_interaction_result = nil)
161
+ process_inputs(inputs.symbolize_keys)
91
162
  end
92
163
 
93
- # Get all the filters defined on this interaction.
164
+ # @!method compose(other, inputs = {})
165
+ # Run another interaction and return its result. If the other interaction
166
+ # fails, halt execution.
94
167
  #
95
- # @return [Filters]
168
+ # @param other (see ActiveInteraction::Runnable#compose)
169
+ # @param inputs (see ActiveInteraction::Base#initialize)
96
170
  #
97
- # @since 0.6.0
98
- def self.filters
99
- @_interaction_filters ||= Filters.new
100
- end
171
+ # @return (see ActiveInteraction::Base.run!)
172
+ loop
101
173
 
102
- # Runs validations and if there are no errors it will call {#execute}.
174
+ # @!method execute
175
+ # @abstract
103
176
  #
104
- # @param (see #initialize)
177
+ # Runs the business logic associated with the interaction. This method is
178
+ # only run when there are no validation errors. The return value is
179
+ # placed into {#result}. This method is run in a transaction if
180
+ # ActiveRecord is available.
105
181
  #
106
- # @return [ActiveInteraction::Base] An instance of the class `run` is
107
- # called on.
108
- def self.run(*args)
109
- new(*args).tap do |interaction|
110
- if interaction.valid?
111
- result = transaction do
112
- begin
113
- interaction.execute
114
- rescue Interrupt
115
- # Inner interaction failed. #compose handles merging errors.
116
- end
117
- end
118
-
119
- if interaction.errors.empty?
120
- interaction.instance_variable_set(:@_interaction_result, result)
121
- else
122
- interaction.instance_variable_set(
123
- :@_interaction_runtime_errors, interaction.errors.dup)
124
- end
125
- end
126
- end
127
- end
182
+ # @raise (see ActiveInteraction::Runnable#execute)
183
+ loop
128
184
 
129
- # @private
130
- def self.method_missing(*args, &block)
131
- super do |klass, names, options|
132
- fail InvalidFilterError, 'missing attribute name' if names.empty?
133
-
134
- names.each do |attribute|
135
- if attribute.to_s.start_with?('_interaction_')
136
- fail InvalidFilterError, attribute.inspect
137
- end
138
-
139
- filter = klass.new(attribute, options, &block)
140
- filters.add(filter)
141
- attr_accessor filter.name
142
-
143
- # This isn't required, but it makes invalid defaults raise errors on
144
- # class definition instead of on execution.
145
- filter.default if filter.has_default?
146
- end
185
+ # Returns the inputs provided to {.run} or {.run!} after being cast based
186
+ # on the filters in the class.
187
+ #
188
+ # @return [Hash{Symbol => Object}] All inputs passed to {.run} or {.run!}.
189
+ def inputs
190
+ self.class.filters.keys.each_with_object({}) do |name, h|
191
+ h[name] = public_send(name)
147
192
  end
148
193
  end
149
194
 
150
195
  private
151
196
 
197
+ # @param inputs [Hash{Symbol => Object}]
152
198
  def process_inputs(inputs)
153
199
  inputs.each do |key, value|
154
- if key.to_s.start_with?('_interaction_')
155
- fail InvalidValueError, key.inspect
156
- end
200
+ fail InvalidValueError, key.inspect if self.class.send(:reserved?, key)
157
201
 
158
202
  instance_variable_set("@#{key}", value)
159
203
  end
160
204
 
161
- self.class.filters.each do |filter|
205
+ self.class.filters.each do |name, filter|
162
206
  begin
163
- send("#{filter.name}=", filter.clean(inputs[filter.name]))
207
+ public_send("#{name}=", filter.clean(inputs[name]))
164
208
  rescue InvalidValueError, MissingValueError
165
209
  # Validators (#input_errors) will add errors if appropriate.
166
210
  end
167
211
  end
168
212
  end
169
213
 
214
+ # @!group Validations
215
+
170
216
  def input_errors
171
217
  Validation.validate(self.class.filters, inputs).each do |error|
172
218
  errors.add_sym(*error)
173
219
  end
174
220
  end
175
-
176
- def runtime_errors
177
- if @_interaction_runtime_errors
178
- errors.merge!(@_interaction_runtime_errors)
179
- end
180
- end
181
-
182
- def compose(interaction, inputs = {})
183
- outcome = interaction.run(inputs)
184
- return outcome.result if outcome.valid?
185
-
186
- # This can't use Errors#merge! because the errors have to be added to
187
- # base.
188
- outcome.errors.full_messages.each do |message|
189
- errors.add(:base, message) unless errors.added?(:base, message)
190
- end
191
-
192
- fail Interrupt
193
- end
194
-
195
- def self.inherited(klass)
196
- new_filters = Filters.new
197
- filters.each { |f| new_filters.add(f) }
198
-
199
- klass.instance_variable_set(:@_interaction_filters, new_filters)
200
- end
201
221
  end
202
222
  end