active_interaction 0.5.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -3
  3. data/README.md +8 -6
  4. data/lib/active_interaction.rb +5 -3
  5. data/lib/active_interaction/active_model.rb +29 -0
  6. data/lib/active_interaction/base.rb +82 -116
  7. data/lib/active_interaction/errors.rb +79 -5
  8. data/lib/active_interaction/filter.rb +195 -21
  9. data/lib/active_interaction/filters.rb +26 -0
  10. data/lib/active_interaction/filters/array_filter.rb +22 -25
  11. data/lib/active_interaction/filters/boolean_filter.rb +12 -12
  12. data/lib/active_interaction/filters/date_filter.rb +32 -5
  13. data/lib/active_interaction/filters/date_time_filter.rb +34 -7
  14. data/lib/active_interaction/filters/file_filter.rb +12 -9
  15. data/lib/active_interaction/filters/float_filter.rb +13 -11
  16. data/lib/active_interaction/filters/hash_filter.rb +36 -17
  17. data/lib/active_interaction/filters/integer_filter.rb +13 -11
  18. data/lib/active_interaction/filters/model_filter.rb +15 -15
  19. data/lib/active_interaction/filters/string_filter.rb +19 -8
  20. data/lib/active_interaction/filters/symbol_filter.rb +29 -0
  21. data/lib/active_interaction/filters/time_filter.rb +38 -16
  22. data/lib/active_interaction/method_missing.rb +18 -0
  23. data/lib/active_interaction/overload_hash.rb +1 -0
  24. data/lib/active_interaction/validation.rb +19 -0
  25. data/lib/active_interaction/version.rb +1 -1
  26. data/spec/active_interaction/active_model_spec.rb +33 -0
  27. data/spec/active_interaction/base_spec.rb +54 -48
  28. data/spec/active_interaction/errors_spec.rb +99 -0
  29. data/spec/active_interaction/filter_spec.rb +12 -20
  30. data/spec/active_interaction/filters/array_filter_spec.rb +50 -28
  31. data/spec/active_interaction/filters/boolean_filter_spec.rb +15 -15
  32. data/spec/active_interaction/filters/date_filter_spec.rb +30 -18
  33. data/spec/active_interaction/filters/date_time_filter_spec.rb +31 -19
  34. data/spec/active_interaction/filters/file_filter_spec.rb +7 -7
  35. data/spec/active_interaction/filters/float_filter_spec.rb +13 -11
  36. data/spec/active_interaction/filters/hash_filter_spec.rb +38 -29
  37. data/spec/active_interaction/filters/integer_filter_spec.rb +18 -8
  38. data/spec/active_interaction/filters/model_filter_spec.rb +24 -20
  39. data/spec/active_interaction/filters/string_filter_spec.rb +14 -8
  40. data/spec/active_interaction/filters/symbol_filter_spec.rb +24 -0
  41. data/spec/active_interaction/filters/time_filter_spec.rb +33 -69
  42. data/spec/active_interaction/filters_spec.rb +21 -0
  43. data/spec/active_interaction/i18n_spec.rb +0 -15
  44. data/spec/active_interaction/integration/array_interaction_spec.rb +2 -22
  45. data/spec/active_interaction/integration/hash_interaction_spec.rb +5 -25
  46. data/spec/active_interaction/integration/symbol_interaction_spec.rb +5 -0
  47. data/spec/active_interaction/method_missing_spec.rb +69 -0
  48. data/spec/active_interaction/validation_spec.rb +55 -0
  49. data/spec/spec_helper.rb +6 -0
  50. data/spec/support/filters.rb +168 -14
  51. data/spec/support/interactions.rb +11 -13
  52. metadata +31 -13
  53. data/lib/active_interaction/filter_method.rb +0 -13
  54. data/lib/active_interaction/filter_methods.rb +0 -26
  55. data/lib/active_interaction/filters/abstract_date_time_filter.rb +0 -25
  56. data/spec/active_interaction/filter_method_spec.rb +0 -43
  57. data/spec/active_interaction/filter_methods_spec.rb +0 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 86843e7e3d2f88ef4972b87361eab66961892eeb
4
- data.tar.gz: dc9517aa3246d6310f114a3cb9ee3055f33a4e6c
3
+ metadata.gz: ddc5d64fad3052b09b16259d6aae6760411b384b
4
+ data.tar.gz: 1c3569cfc819b0153c2c875cf5674c29382fc956
5
5
  SHA512:
6
- metadata.gz: 38e2212cb6e9a0ae10687cc07533e28406ca15d09c918fe5b7bf29202a4c384cf8179701878b360deaa17aa6d64965f65eeb9122292027553a1e7a075c6be936
7
- data.tar.gz: 8049e3bab1696a64021bb89e37c5106351332b2eafb73d73c44bf62b6328fd3567ff0e0403c987a10e023386ba057be2c2906b9ad079701184ee76584570afc9
6
+ metadata.gz: 399f01cd79b55412083c29364afa80e53660e28ccbfc9d01788a138e30152ee61f4e6307e7a8f496a1b058557555ece132bf496b9e7d623b580e9414e203b030
7
+ data.tar.gz: c25734f3428395f3c063c7a6b1f867b65107dc806a7e5a391b38d8692fcd46a391cc8f3259aca4df2911bfb377fe45adb96393f7738eb2cfc7acde95cf401d86
@@ -1,11 +1,33 @@
1
1
  # [Master][]
2
2
 
3
- # [0.5.0][]
3
+ # [0.6.1][] (2013-11-14)
4
+
5
+ - Re-release. Forgot to merge into master.
6
+
7
+ # [0.6.0][] (2013-11-14)
8
+
9
+ - Error class now end with `Error`.
10
+ - By default, strip unlisted keys from hashes. To retain the old behavior, set
11
+ `strip: false` on a hash filter.
12
+ - Prevent specifying defaults (other than `nil` or `{}`) on hash filters. Set
13
+ defaults on the nested filters instead.
14
+ - Add ability to introspect interactions with `filters`.
15
+ - Fix bug that prevented listing multiple attributes in a hash filter.
16
+ - Allow getting all of the user-supplied inputs in an interaction with
17
+ `inputs`.
18
+ - Fix bug that prevented hash filters from being nested in array filters.
19
+ - Replace `allow_nil: true` with `default: nil`.
20
+ - Refactor internals.
21
+ - Add a symbol filter.
22
+ - Allow adding symbolic errors with `errors.add_sym` and retrieving them with
23
+ `errors.symbolic`.
24
+
25
+ # [0.5.0][] (2013-10-16)
4
26
 
5
27
  - Allow adding errors in `execute` method with `errors.add`.
6
28
  - Prevent manually setting the outcome's result.
7
29
 
8
- # [0.4.0][]
30
+ # [0.4.0][] (2013-08-15)
9
31
 
10
32
  - Support i18n translations.
11
33
 
@@ -48,7 +70,9 @@
48
70
 
49
71
  - Initial release.
50
72
 
51
- [master]: https://github.com/orgsync/active_interaction/compare/v0.5.0...master
73
+ [master]: https://github.com/orgsync/active_interaction/compare/v0.6.1...master
74
+ [0.6.1]: https://github.com/orgsync/active_interaction/compare/v0.6.0...v0.6.1
75
+ [0.6.0]: https://github.com/orgsync/active_interaction/compare/v0.5.0...v0.6.0
52
76
  [0.5.0]: https://github.com/orgsync/active_interaction/compare/v0.4.0...v0.5.0
53
77
  [0.4.0]: https://github.com/orgsync/active_interaction/compare/v0.3.0...v0.4.0
54
78
  [0.3.0]: https://github.com/orgsync/active_interaction/compare/v0.2.2...v0.3.0
data/README.md CHANGED
@@ -15,7 +15,8 @@ to this.
15
15
  Take back control. Slim down models and wrangle monstrous controller
16
16
  methods with ActiveInteraction.
17
17
 
18
- Check out the full [documentation][] on RubyDoc.info.
18
+ Read more on the [project page][] or check out the full [documentation][]
19
+ on RubyDoc.info.
19
20
 
20
21
  ## Installation
21
22
 
@@ -24,7 +25,7 @@ This project uses [semantic versioning][].
24
25
  Add it to your Gemfile:
25
26
 
26
27
  ```ruby
27
- gem 'active_interaction', '~> 0.5.0'
28
+ gem 'active_interaction', '~> 0.6.1'
28
29
  ```
29
30
 
30
31
  And then execute:
@@ -46,7 +47,7 @@ models ensure that certain options are provided and that those
46
47
  options are in the format you want them in. If the options are valid
47
48
  it will call `execute`, store the return value of that method in
48
49
  `result`, and return an instance of your ActiveInteraction::Base
49
- subclass. Let's looks at a simple example:
50
+ subclass. Let's look at a simple example:
50
51
 
51
52
  ```ruby
52
53
  # Define an interaction that signs up a user.
@@ -55,7 +56,7 @@ class UserSignup < ActiveInteraction::Base
55
56
  string :email, :name
56
57
 
57
58
  # optional
58
- boolean :newsletter_subscribe, allow_nil: true
59
+ boolean :newsletter_subscribe, default: nil
59
60
 
60
61
  # ActiveRecord validations
61
62
  validates :email, format: EMAIL_REGEX
@@ -160,10 +161,10 @@ end
160
161
  integer :age
161
162
  boolean :is_special
162
163
  model :account
163
- array :tags, allow_nil: true do
164
+ array :tags, default: nil do
164
165
  string
165
166
  end
166
- hash :prefs, allow_nil: true do
167
+ hash :prefs, default: nil do
167
168
  boolean :smoking
168
169
  boolean :view
169
170
  end
@@ -258,4 +259,5 @@ This project was inspired by the fantastic work done in [Mutations][].
258
259
  [documentation]: http://rubydoc.info/github/orgsync/active_interaction
259
260
  [gem version]: https://badge.fury.io/rb/active_interaction.png
260
261
  [mutations]: https://github.com/cypriss/mutations
262
+ [project page]: http://orgsync.github.io/active_interaction/
261
263
  [semantic versioning]: http://semver.org
@@ -2,11 +2,10 @@ require 'active_model'
2
2
 
3
3
  require 'active_interaction/version'
4
4
  require 'active_interaction/errors'
5
+ require 'active_interaction/active_model'
6
+ require 'active_interaction/method_missing'
5
7
  require 'active_interaction/overload_hash'
6
8
  require 'active_interaction/filter'
7
- require 'active_interaction/filter_method'
8
- require 'active_interaction/filter_methods'
9
- require 'active_interaction/filters/abstract_date_time_filter'
10
9
  require 'active_interaction/filters/array_filter'
11
10
  require 'active_interaction/filters/boolean_filter'
12
11
  require 'active_interaction/filters/date_filter'
@@ -17,7 +16,10 @@ require 'active_interaction/filters/hash_filter'
17
16
  require 'active_interaction/filters/integer_filter'
18
17
  require 'active_interaction/filters/model_filter'
19
18
  require 'active_interaction/filters/string_filter'
19
+ require 'active_interaction/filters/symbol_filter'
20
20
  require 'active_interaction/filters/time_filter'
21
+ require 'active_interaction/filters'
22
+ require 'active_interaction/validation'
21
23
  require 'active_interaction/base'
22
24
 
23
25
  I18n.backend.load_translations(
@@ -0,0 +1,29 @@
1
+ module ActiveInteraction
2
+ # @private
3
+ module ActiveModel
4
+ extend ::ActiveSupport::Concern
5
+
6
+ extend ::ActiveModel::Naming
7
+ include ::ActiveModel::Conversion
8
+ include ::ActiveModel::Validations
9
+
10
+ def new_record?
11
+ true
12
+ end
13
+
14
+ def persisted?
15
+ false
16
+ end
17
+
18
+ def i18n_scope
19
+ self.class.i18n_scope
20
+ end
21
+
22
+ # @private
23
+ module ClassMethods
24
+ def i18n_scope
25
+ :active_interaction
26
+ end
27
+ end
28
+ end
29
+ end
@@ -15,7 +15,7 @@ module ActiveInteraction
15
15
  # integer :a, :b
16
16
  #
17
17
  # # Optional
18
- # integer :c, allow_nil: true
18
+ # integer :c, default: nil
19
19
  #
20
20
  # def execute
21
21
  # sum = a + b
@@ -30,53 +30,59 @@ module ActiveInteraction
30
30
  # p outcome.errors
31
31
  # end
32
32
  class Base
33
- extend ::ActiveModel::Naming
34
- include ::ActiveModel::Conversion
35
- include ::ActiveModel::Validations
33
+ include ActiveModel
34
+ extend MethodMissing
35
+ extend OverloadHash
36
36
 
37
- # @private
38
- def new_record?
39
- true
37
+ validate do
38
+ Validation.validate(self.class.filters, inputs).each do |error|
39
+ errors.add_sym(*error)
40
+ end
40
41
  end
41
42
 
42
- # @private
43
- def persisted?
44
- false
45
- end
43
+ validate do
44
+ return unless instance_variable_defined?(:@_interaction_runtime_errors)
46
45
 
47
- # @private
48
- def self.i18n_scope
49
- :active_interaction
50
- end
46
+ @_interaction_runtime_errors.symbolic.each do |attribute, symbols|
47
+ symbols.each { |symbol| errors.add_sym(attribute, symbol) }
48
+ end
51
49
 
52
- # @private
53
- def i18n_scope
54
- self.class.i18n_scope
50
+ @_interaction_runtime_errors.messages.each do |attribute, messages|
51
+ messages.each { |message| errors.add(attribute, message) }
52
+ end
55
53
  end
56
54
 
57
- extend OverloadHash
58
-
59
- validate do
60
- return unless @_interaction_errors
61
- @_interaction_errors.each do |attribute, message|
62
- errors.add(attribute, message)
55
+ # Returns the inputs provided to {.run} or {.run!} after being cast based
56
+ # on the filters in the class.
57
+ #
58
+ # @return [Hash{Symbol => Object}] All inputs passed to {.run} or {.run!}.
59
+ #
60
+ # @since 0.6.0
61
+ def inputs
62
+ self.class.filters.reduce({}) do |h, filter|
63
+ h[filter.name] = send(filter.name)
64
+ h
63
65
  end
64
66
  end
65
67
 
68
+ # @param options [Hash{Symbol => Object}] Attribute values to set.
69
+ #
66
70
  # @private
67
71
  def initialize(options = {})
68
- options = options.with_indifferent_access
72
+ options = options.symbolize_keys
73
+
74
+ options.each do |key, value|
75
+ if key.to_s.start_with?('_interaction_')
76
+ raise InvalidValueError, key.inspect
77
+ end
69
78
 
70
- if options.has_key?(:result)
71
- raise ArgumentError, ':result is reserved and can not be used'
79
+ instance_variable_set("@#{key}", value)
72
80
  end
73
81
 
74
- options.each do |attribute, value|
75
- method = "_filter__#{attribute}="
76
- if respond_to?(method, true)
77
- send(method, value)
78
- else
79
- instance_variable_set("@#{attribute}", value)
82
+ self.class.filters.each do |filter|
83
+ begin
84
+ send("#{filter.name}=", filter.clean(options[filter.name]))
85
+ rescue InvalidValueError, MissingValueError
80
86
  end
81
87
  end
82
88
  end
@@ -87,6 +93,8 @@ module ActiveInteraction
87
93
  # This method is run in a transaction if ActiveRecord is available.
88
94
  #
89
95
  # @raise [NotImplementedError] if the method is not defined.
96
+ #
97
+ # @abstract
90
98
  def execute
91
99
  raise NotImplementedError
92
100
  end
@@ -97,12 +105,22 @@ module ActiveInteraction
97
105
  # @return [Nil] if there are validation errors.
98
106
  # @return [Object] if there are no validation errors.
99
107
  def result
100
- @_interaction_result
108
+ symbol = :'@_interaction_result'
109
+ if instance_variable_defined?(symbol)
110
+ instance_variable_get(symbol)
111
+ else
112
+ nil
113
+ end
114
+ end
115
+
116
+ # @private
117
+ def errors
118
+ @_interaction_errors ||= Errors.new(self)
101
119
  end
102
120
 
103
121
  # @private
104
122
  def valid?(*args)
105
- super || instance_variable_set(:@_interaction_result, nil)
123
+ super || @_interaction_result = nil
106
124
  end
107
125
 
108
126
  # @private
@@ -115,27 +133,32 @@ module ActiveInteraction
115
133
  yield
116
134
  end
117
135
  end
118
- private_class_method :transaction
119
136
 
120
- # @!macro [new] run_attributes
121
- # @param options [Hash] Attribute values to set.
137
+ # Get all the filters defined on this interaction.
138
+ #
139
+ # @return [Filters]
140
+ #
141
+ # @since 0.6.0
142
+ def self.filters
143
+ @_interaction_filters ||= Filters.new
144
+ end
122
145
 
123
146
  # Runs validations and if there are no errors it will call {#execute}.
124
147
  #
125
- # @macro run_attributes
148
+ # @param (see #initialize)
126
149
  #
127
150
  # @return [ActiveInteraction::Base] An instance of the class `run` is
128
151
  # called on.
129
- def self.run(options = {})
130
- new(options).tap do |interaction|
152
+ def self.run(*args)
153
+ new(*args).tap do |interaction|
131
154
  if interaction.valid?
132
155
  result = transaction { interaction.execute }
133
156
 
134
157
  if interaction.errors.empty?
135
158
  interaction.instance_variable_set(:@_interaction_result, result)
136
159
  else
137
- interaction.instance_variable_set(:@_interaction_errors,
138
- interaction.errors.dup)
160
+ interaction.instance_variable_set(
161
+ :@_interaction_runtime_errors, interaction.errors.dup)
139
162
  end
140
163
  end
141
164
  end
@@ -144,93 +167,36 @@ module ActiveInteraction
144
167
  # Like {.run} except that it returns the value of {#execute} or raises an
145
168
  # exception if there were any validation errors.
146
169
  #
147
- # @macro run_attributes
148
- #
149
- # @raise [InteractionInvalid] if there are any errors on the model.
170
+ # @param (see .run)
150
171
  #
151
172
  # @return The return value of {#execute}.
152
- def self.run!(options = {})
153
- outcome = run(options)
173
+ #
174
+ # @raise [InteractionInvalidError] if there are any errors on the model.
175
+ def self.run!(*args)
176
+ outcome = run(*args)
154
177
  if outcome.invalid?
155
- raise InteractionInvalid, outcome.errors.full_messages.join(', ')
178
+ raise InteractionInvalidError, outcome.errors.full_messages.join(', ')
156
179
  end
157
180
  outcome.result
158
181
  end
159
182
 
160
183
  # @private
161
- def self.method_missing(type, *args, &block)
162
- filter = Filter.factory(type)
163
- options = args.last.is_a?(Hash) ? args.pop : {}
164
- args.each do |attribute|
165
- set_up_reader(attribute, filter, options, &block)
166
- set_up_writer(attribute, filter, options, &block)
167
- set_up_validator(attribute, type, filter, options, &block)
168
- end
169
- end
170
- private_class_method :method_missing
171
-
172
- # @private
173
- def self.set_up_reader(attribute, filter, options, &block)
174
- default = nil
175
- if options.has_key?(:default)
176
- begin
177
- default = filter.
178
- prepare(attribute, options[:default], options, &block)
179
- rescue InvalidNestedValue, InvalidValue
180
- raise InvalidDefaultValue
181
- end
182
- end
184
+ def self.method_missing(*args, &block)
185
+ super do |klass, names, options|
186
+ raise InvalidFilterError, 'no name' if names.empty?
183
187
 
184
- define_method(attribute) do
185
- symbol = "@#{attribute}"
186
- if instance_variable_defined?(symbol)
187
- instance_variable_get(symbol)
188
- else
189
- default
190
- end
191
- end
192
- end
193
- private_class_method :set_up_reader
194
-
195
- # @private
196
- def self.set_up_writer(attribute, filter, options, &block)
197
- attr_writer attribute
198
-
199
- writer = "_filter__#{attribute}="
200
-
201
- define_method(writer) do |value|
202
- value =
203
- begin
204
- filter.prepare(attribute, value, options, &block)
205
- rescue InvalidNestedValue, InvalidValue, MissingValue
206
- value
188
+ names.each do |attribute|
189
+ if attribute.to_s.start_with?('_interaction_')
190
+ raise InvalidFilterError, attribute.inspect
207
191
  end
208
- instance_variable_set("@#{attribute}", value)
209
- end
210
- private writer
211
- end
212
- private_class_method :set_up_writer
213
192
 
214
- # @private
215
- def self.set_up_validator(attribute, type, filter, options, &block)
216
- validator = "_validate__#{attribute}__#{type}"
217
-
218
- validate validator
193
+ filter = klass.new(attribute, options, &block)
194
+ filters.add(filter)
195
+ attr_accessor filter.name
219
196
 
220
- define_method(validator) do
221
- begin
222
- filter.prepare(attribute, send(attribute), options, &block)
223
- rescue InvalidNestedValue
224
- errors.add(attribute, :invalid_nested)
225
- rescue InvalidValue
226
- errors.add(attribute, :invalid,
227
- type: I18n.translate("#{i18n_scope}.types.#{type.to_s}"))
228
- rescue MissingValue
229
- errors.add(attribute, :missing)
197
+ filter.default if filter.has_default?
230
198
  end
231
199
  end
232
- private validator
233
200
  end
234
- private_class_method :set_up_validator
235
201
  end
236
202
  end