active_interaction 1.6.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -5
  3. data/README.md +35 -32
  4. data/lib/active_interaction.rb +3 -4
  5. data/lib/active_interaction/backports.rb +47 -0
  6. data/lib/active_interaction/base.rb +8 -25
  7. data/lib/active_interaction/concerns/runnable.rb +4 -14
  8. data/lib/active_interaction/errors.rb +12 -82
  9. data/lib/active_interaction/filters/array_filter.rb +3 -9
  10. data/lib/active_interaction/filters/file_filter.rb +5 -24
  11. data/lib/active_interaction/filters/hash_filter.rb +6 -13
  12. data/lib/active_interaction/filters/interface_filter.rb +2 -2
  13. data/lib/active_interaction/filters/{model_filter.rb → object_filter.rb} +4 -5
  14. data/lib/active_interaction/locale/en.yml +0 -1
  15. data/lib/active_interaction/version.rb +1 -1
  16. data/spec/active_interaction/base_spec.rb +15 -14
  17. data/spec/active_interaction/concerns/runnable_spec.rb +2 -34
  18. data/spec/active_interaction/errors_spec.rb +5 -87
  19. data/spec/active_interaction/filters/array_filter_spec.rb +2 -2
  20. data/spec/active_interaction/filters/file_filter_spec.rb +4 -4
  21. data/spec/active_interaction/filters/hash_filter_spec.rb +1 -17
  22. data/spec/active_interaction/filters/{model_filter_spec.rb → object_filter_spec.rb} +17 -17
  23. data/spec/active_interaction/integration/array_interaction_spec.rb +10 -0
  24. data/spec/active_interaction/integration/hash_interaction_spec.rb +12 -2
  25. data/spec/active_interaction/integration/object_interaction_spec.rb +16 -0
  26. metadata +8 -11
  27. data/lib/active_interaction/concerns/transactable.rb +0 -81
  28. data/spec/active_interaction/concerns/transactable_spec.rb +0 -135
  29. data/spec/active_interaction/integration/model_interaction_spec.rb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4dfc633bc6a2816016866f2a1a61c2a20805c28a
4
- data.tar.gz: 53931759d4d2158be56ada22b2398a7ab539f119
3
+ metadata.gz: ec03f8a492e5db88366e50b9a918ff40a4e008b3
4
+ data.tar.gz: 356b91990bfac14981a6d36dffcaafb4e3dd2d33
5
5
  SHA512:
6
- metadata.gz: 8c1a71ae668feffea91020d19b0c48633efd39c15404e6a3bfdbcaf6edef9a5f278402c7504ca77141ce761864a32ec09f8c2f6aab8b1b5b8a7951be9a132f74
7
- data.tar.gz: bc56f77840ac9e9aace55893e73b4746bd0b95de9eb2e755648101f152fdd861ebfd6f4f786c6bb2e892a57604095a67c0ed0400a0ba1bf084cd40e5c67c798d
6
+ metadata.gz: a68094094d6dbf8a395a3f709ffd993944512c6edc921ce36f99b4383792de041954cc2d73271a0748102782f89c4e457bb63288ee56eb34e42ff6cb38fbcbd5
7
+ data.tar.gz: d682f33e94ea7bd25f4f9cf89e2fe3924bc501e608c6d32f469da42d92782f88aca01b43605c69adb5de62e36b2cafe3857307ff927a2306dbbb25ef334c4f93
data/CHANGELOG.md CHANGED
@@ -1,8 +1,20 @@
1
- # [1.6.1][] (2015-10-02)
1
+ # [2.0.0][] (2015-05-06)
2
2
 
3
- ## Fixed
3
+ ## Changed
4
+
5
+ - [#250][]: Replaced symbolic errors with Rails 5-style detailed errors.
6
+ - [#269][]: Prevented proc defaults from being eagerly evaluated.
7
+ - [#264][]: Renamed `model` filter to `object`.
8
+ - [#213][]: Remove transaction support. Database transactions will need to be
9
+ handled manually now.
10
+ - [#214][]: Results are returned from invalid outcomes.
11
+ - [#164][]: Changed the `hash` filter to use hashes with indifferent access.
12
+ - [#236][]: Changed the `file` filter to accept anything that responds to `eof?`.
13
+
14
+ ## Security
4
15
 
5
- - [#303][]: Allowed ActiveRecord associations as inputs to array filters.
16
+ - [#215][]: Rather than symbolizing keys all hashes now use indifferent access.
17
+ This takes care of potential but unlikely DoS attacks noted in [#163][].
6
18
 
7
19
  # [1.6.0][] (2015-05-06)
8
20
 
@@ -402,7 +414,7 @@
402
414
 
403
415
  - Initial release.
404
416
 
405
- [1.6.1]: https://github.com/orgsync/active_interaction/compare/v1.6.0...v1.6.1
417
+ [2.0.0]: https://github.com/orgsync/active_interaction/compare/v1.6.0...v2.0.0
406
418
  [1.6.0]: https://github.com/orgsync/active_interaction/compare/v1.5.1...v1.6.0
407
419
  [1.5.1]: https://github.com/orgsync/active_interaction/compare/v1.5.0...v1.5.1
408
420
  [1.5.0]: https://github.com/orgsync/active_interaction/compare/v1.4.1...v1.5.0
@@ -505,6 +517,7 @@
505
517
  [#155]: https://github.com/orgsync/active_interaction/issues/155
506
518
  [#156]: https://github.com/orgsync/active_interaction/issues/156
507
519
  [#163]: https://github.com/orgsync/active_interaction/issues/163
520
+ [#164]: https://github.com/orgsync/active_interaction/issues/164
508
521
  [#165]: https://github.com/orgsync/active_interaction/issues/165
509
522
  [#173]: https://github.com/orgsync/active_interaction/issues/173
510
523
  [#174]: https://github.com/orgsync/active_interaction/issues/174
@@ -517,11 +530,17 @@
517
530
  [#203]: https://github.com/orgsync/active_interaction/issues/203
518
531
  [#206]: https://github.com/orgsync/active_interaction/issues/206
519
532
  [#207]: https://github.com/orgsync/active_interaction/issues/207
533
+ [#213]: https://github.com/orgsync/active_interaction/issues/213
534
+ [#214]: https://github.com/orgsync/active_interaction/issues/214
535
+ [#215]: https://github.com/orgsync/active_interaction/issues/215
520
536
  [#224]: https://github.com/orgsync/active_interaction/issues/224
521
537
  [#235]: https://github.com/orgsync/active_interaction/issues/235
538
+ [#236]: https://github.com/orgsync/active_interaction/issues/236
522
539
  [#239]: https://github.com/orgsync/active_interaction/issues/239
523
540
  [#244]: https://github.com/orgsync/active_interaction/issues/244
524
541
  [#248]: https://github.com/orgsync/active_interaction/issues/248
542
+ [#250]: https://github.com/orgsync/active_interaction/issues/250
525
543
  [#256]: https://github.com/orgsync/active_interaction/issues/256
544
+ [#264]: https://github.com/orgsync/active_interaction/issues/264
526
545
  [#265]: https://github.com/orgsync/active_interaction/issues/265
527
- [#303]: https://github.com/orgsync/active_interaction/issues/303
546
+ [#269]: https://github.com/orgsync/active_interaction/issues/269
data/README.md CHANGED
@@ -39,7 +39,7 @@ Read more on [the project page][] or check out [the full documentation][].
39
39
  - [File](#file)
40
40
  - [Hash](#hash)
41
41
  - [Interface](#interface)
42
- - [Model](#model)
42
+ - [Object](#object)
43
43
  - [String](#string)
44
44
  - [Symbol](#symbol)
45
45
  - [Dates and times](#dates-and-times)
@@ -75,13 +75,13 @@ Read more on [the project page][] or check out [the full documentation][].
75
75
  Add it to your Gemfile:
76
76
 
77
77
  ``` rb
78
- gem 'active_interaction', '~> 1.6'
78
+ gem 'active_interaction', '~> 2.0'
79
79
  ```
80
80
 
81
81
  Or install it manually:
82
82
 
83
83
  ``` sh
84
- $ gem install active_interaction --version '~> 1.6'
84
+ $ gem install active_interaction --version '~> 2.0'
85
85
  ```
86
86
 
87
87
  This project uses [Semantic Versioning][]. Check out [the change log][] for a
@@ -124,8 +124,8 @@ end
124
124
  Call `.run` on your interaction to execute it. You must pass a single hash to
125
125
  `.run`. It will return an instance of your interaction. By convention, we call
126
126
  this an outcome. You can use the `#valid?` method to ask the outcome if it's
127
- valid. If it's invalid, take a look at its errors with `#errors`. If it's
128
- valid, `#result` will be the value returned from `#execute`.
127
+ valid. If it's invalid, take a look at its errors with `#errors`. In either
128
+ case, the value returned from `#execute` will be stored in `#result`.
129
129
 
130
130
  ``` rb
131
131
  outcome = Square.run(x: 'two point one')
@@ -379,11 +379,11 @@ InterfaceInteraction.run!(serializer: JSON)
379
379
  # => "{\"is_json\":true}"
380
380
  ```
381
381
 
382
- ### Model
382
+ ### Object
383
383
 
384
- Model filters allow you to require an instance of a particular class. It checks
385
- either `#is_a?` on the instance or `.===` on the class. Because of that, it
386
- also works with classes that have mixed modules in with `include`.
384
+ Object filters allow you to require an instance of a particular class. It
385
+ checks either `#is_a?` on the instance or `.===` on the class. Because of that,
386
+ it also works with classes that have mixed modules in with `include`.
387
387
 
388
388
  ``` rb
389
389
  class Cow
@@ -392,17 +392,17 @@ class Cow
392
392
  end
393
393
  end
394
394
 
395
- class ModelInteraction < ActiveInteraction::Base
396
- model :cow
395
+ class ObjectInteraction < ActiveInteraction::Base
396
+ object :cow
397
397
 
398
398
  def execute
399
399
  cow.moo
400
400
  end
401
401
  end
402
402
 
403
- ModelInteraction.run!(cow: Object.new)
404
- # ActiveInteraction::InvalidInteractionError: Cow is not a valid model
405
- ModelInteraction.run!(cow: Cow.new)
403
+ ObjectInteraction.run!(cow: Object.new)
404
+ # ActiveInteraction::InvalidInteractionError: Cow is not a valid object
405
+ ObjectInteraction.run!(cow: Cow.new)
406
406
  # => "Moo!"
407
407
  ```
408
408
 
@@ -411,11 +411,11 @@ name is different than your class name, use the `class` option. It can be
411
411
  either the class, a string, or a symbol.
412
412
 
413
413
  ``` rb
414
- model :dolly1,
414
+ object :dolly1,
415
415
  class: Sheep
416
- model :dolly2,
416
+ object :dolly2,
417
417
  class: 'Sheep'
418
- model :dolly3,
418
+ object :dolly3,
419
419
  class: :Sheep
420
420
  ```
421
421
 
@@ -788,7 +788,7 @@ spot.
788
788
 
789
789
  ``` rb
790
790
  class DestroyAccount < ActiveInteraction::Base
791
- model :account
791
+ object :account
792
792
 
793
793
  def execute
794
794
  account.destroy
@@ -822,7 +822,7 @@ Skip to [the predicates section](#predicates) for more information about them.
822
822
 
823
823
  ``` rb
824
824
  class UpdateAccount < ActiveInteraction::Base
825
- model :account
825
+ object :account
826
826
 
827
827
  string :first_name, :last_name,
828
828
  default: nil
@@ -1004,14 +1004,14 @@ end
1004
1004
 
1005
1005
  ### Errors
1006
1006
 
1007
- ActiveInteraction provides symbolic errors for easier introspection and testing
1008
- of errors. Symbolic errors improve on regular errors by adding a symbol that
1007
+ ActiveInteraction provides detailed errors for easier introspection and testing
1008
+ of errors. Detailed errors improve on regular errors by adding a symbol that
1009
1009
  represents the type of error that has occurred. Let's look at an example where
1010
1010
  an item is purchased using a credit card.
1011
1011
 
1012
1012
  ``` rb
1013
1013
  class BuyItem < ActiveInteraction::Base
1014
- model :credit_card, :item
1014
+ object :credit_card, :item
1015
1015
  hash :options do
1016
1016
  boolean :gift_wrapped
1017
1017
  end
@@ -1034,28 +1034,30 @@ errors.
1034
1034
  ``` rb
1035
1035
  outcome = BuyItem.run(item: 'Thing', options: { gift_wrapped: 'yes' })
1036
1036
  outcome.errors.messages
1037
- # => {:credit_card=>["is required"], :item=>["is not a valid model"], :options=>["has an invalid nested value (\"gift_wrapped\" => \"yes\")"]}
1037
+ # => {:credit_card=>["is required"], :item=>["is not a valid object"], :options=>["has an invalid nested value (\"gift_wrapped\" => \"yes\")"]}
1038
1038
  ```
1039
1039
 
1040
1040
  Determining the type of error based on the string is difficult if not
1041
- impossible. Calling `#symbolic` instead of `#messages` on `errors` gives you
1041
+ impossible. Calling `#details` instead of `#messages` on `errors` gives you
1042
1042
  the same list of errors with a testable label representing the error.
1043
1043
 
1044
1044
  ``` rb
1045
- outcome.errors.symbolic
1046
- # => {"credit_card"=>[:missing], "item"=>[:invalid_type], "options"=>[:invalid_nested]}
1045
+ outcome.errors.details
1046
+ # => {:credit_card=>[{:error=>:missing}], :item=>[{:type=>"object", :error=>:invalid_type}], :options=>[{:name=>"\"gift_wrapped\"", :value=>"\"yes\"", :error=>:invalid_nested}]}
1047
1047
  ```
1048
1048
 
1049
- Symbolic errors can also be manually added during the execute call by calling
1050
- `#add_sym` instead of `#add` on `errors`. It works the same way as `add` except
1051
- that the second argument is the error label.
1049
+ Detailed errors can also be manually added during the execute call by passing a
1050
+ symbol to `#add` instead of a string.
1052
1051
 
1053
1052
  ``` rb
1054
1053
  def execute
1055
- errors.add_sym(:monster, :no_passage, 'You shall not pass!')
1054
+ errors.add(:monster, :no_passage)
1056
1055
  end
1057
1056
  ```
1058
1057
 
1058
+ These types of errors will become standard with Rails 5. ActiveInteraction's
1059
+ implementation is based off of [active_model-errors_details][].
1060
+
1059
1061
  ActiveInteraction also supports merging errors. This is useful if you want to
1060
1062
  delegate validation to some other object. For example, if you have an
1061
1063
  interaction that updates a record, you might want that record to validate
@@ -1063,7 +1065,7 @@ itself. By using the `#merge!` helper on `errors`, you can do exactly that.
1063
1065
 
1064
1066
  ``` rb
1065
1067
  class UpdateThing < ActiveInteraction::Base
1066
- model :thing
1068
+ object :thing
1067
1069
 
1068
1070
  def execute
1069
1071
  unless thing.save
@@ -1180,7 +1182,7 @@ hsilgne:
1180
1182
  hash: hsah
1181
1183
  integer: regetni
1182
1184
  interface: ecafretni
1183
- model: ledom
1185
+ object: tcejbo
1184
1186
  string: gnirts
1185
1187
  symbol: lobmys
1186
1188
  time: emit
@@ -1225,6 +1227,7 @@ Logo design by [Tyler Lee][].
1225
1227
  [the full documentation]: http://rubydoc.info/github/orgsync/active_interaction
1226
1228
  [semantic versioning]: http://semver.org/spec/v2.0.0.html
1227
1229
  [the change log]: CHANGELOG.md
1230
+ [active_model-errors_details]: https://github.com/cowbell/active_model-errors_details
1228
1231
  [aaron lasseigne]: https://github.com/AaronLasseigne
1229
1232
  [taylor fausak]: https://github.com/tfausak
1230
1233
  [orgsync]: https://github.com/orgsync
@@ -9,7 +9,7 @@ require 'active_model'
9
9
  #
10
10
  # @since 1.0.0
11
11
  #
12
- # @version 1.6.1
12
+ # @version 2.0.0
13
13
  module ActiveInteraction
14
14
  DEPRECATOR =
15
15
  if ::ActiveSupport::Deprecation.respond_to?(:new)
@@ -31,7 +31,6 @@ require 'active_interaction/concerns/active_modelable'
31
31
  require 'active_interaction/concerns/active_recordable'
32
32
  require 'active_interaction/concerns/hashable'
33
33
  require 'active_interaction/concerns/missable'
34
- require 'active_interaction/concerns/transactable'
35
34
  require 'active_interaction/concerns/runnable'
36
35
 
37
36
  require 'active_interaction/grouped_input'
@@ -42,6 +41,7 @@ require 'active_interaction/modules/validation'
42
41
  require 'active_interaction/filter_column'
43
42
  require 'active_interaction/filter'
44
43
  require 'active_interaction/filters/abstract_filter'
44
+ require 'active_interaction/filters/interface_filter'
45
45
  require 'active_interaction/filters/abstract_date_time_filter'
46
46
  require 'active_interaction/filters/abstract_numeric_filter'
47
47
  require 'active_interaction/filters/array_filter'
@@ -53,8 +53,7 @@ require 'active_interaction/filters/file_filter'
53
53
  require 'active_interaction/filters/float_filter'
54
54
  require 'active_interaction/filters/hash_filter'
55
55
  require 'active_interaction/filters/integer_filter'
56
- require 'active_interaction/filters/interface_filter'
57
- require 'active_interaction/filters/model_filter'
56
+ require 'active_interaction/filters/object_filter'
58
57
  require 'active_interaction/filters/string_filter'
59
58
  require 'active_interaction/filters/symbol_filter'
60
59
  require 'active_interaction/filters/time_filter'
@@ -16,6 +16,53 @@ module ActiveInteraction
16
16
  class Errors # rubocop:disable Style/Documentation
17
17
  # Required for Rails < 3.2.13.
18
18
  protected :initialize_dup
19
+
20
+ # Required for Rails < 5.
21
+ #
22
+ # Extracted from active_model-errors_details 1.2.0. Modified to add support
23
+ # for ActiveModel 3.2.0.
24
+ module Details
25
+ extend ActiveSupport::Concern
26
+
27
+ CALLBACKS_OPTIONS = ::ActiveModel::Errors::CALLBACKS_OPTIONS
28
+ private_constant :CALLBACKS_OPTIONS
29
+
30
+ included do
31
+ attr_reader :details
32
+
33
+ %w[initialize initialize_dup add clear delete].each do |method|
34
+ alias_method_chain method, :details
35
+ end
36
+ end
37
+
38
+ def initialize_with_details(base)
39
+ @details = Hash.new { |details, attribute| details[attribute] = [] }
40
+ initialize_without_details(base)
41
+ end
42
+
43
+ def initialize_dup_with_details(other)
44
+ @details = other.details.deep_dup
45
+ initialize_dup_without_details(other)
46
+ end
47
+
48
+ def add_with_details(attribute, message = :invalid, options = {})
49
+ message = message.call if message.respond_to?(:call)
50
+ error = options.except(*CALLBACKS_OPTIONS).merge(error: message)
51
+ details[attribute].push(error)
52
+ add_without_details(attribute, message, options)
53
+ end
54
+
55
+ def clear_with_details
56
+ details.clear
57
+ clear_without_details
58
+ end
59
+
60
+ def delete_with_details(attribute)
61
+ details.delete(attribute)
62
+ delete_without_details(attribute)
63
+ end
64
+ end
65
+ include Details unless method_defined?(:details)
19
66
  end
20
67
 
21
68
  class HashFilter # rubocop:disable Style/Documentation
@@ -59,28 +59,6 @@ module ActiveInteraction
59
59
  #
60
60
  # @raise (see ActiveInteraction::Runnable::ClassMethods#run!)
61
61
 
62
- # @!method transaction(enable, options = {})
63
- # Configure transactions by enabling or disabling them and setting
64
- # their options.
65
- #
66
- # @example Disable transactions
67
- # Class.new(ActiveInteraction::Base) do
68
- # transaction false
69
- # end
70
- #
71
- # @example Use different transaction options
72
- # Class.new(ActiveInteraction::Base) do
73
- # transaction true, isolation: :serializable
74
- # end
75
- #
76
- # @param enable [Boolean] Should transactions be enabled?
77
- # @param options [Hash] Options to pass to
78
- # `ActiveRecord::Base.transaction`.
79
- #
80
- # @return [nil]
81
- #
82
- # @since 1.2.0
83
-
84
62
  # Get or set the description.
85
63
  #
86
64
  # @example
@@ -174,7 +152,13 @@ module ActiveInteraction
174
152
  attr_accessor filter.name
175
153
  define_method("#{filter.name}?") { !public_send(filter.name).nil? }
176
154
 
177
- filter.default if filter.default?
155
+ eagerly_evaluate_default(filter)
156
+ end
157
+
158
+ # @param filter [Filter]
159
+ def eagerly_evaluate_default(filter)
160
+ default = filter.options[:default]
161
+ filter.default if default && !default.is_a?(Proc)
178
162
  end
179
163
  end
180
164
 
@@ -201,8 +185,7 @@ module ActiveInteraction
201
185
  #
202
186
  # Runs the business logic associated with the interaction. This method is
203
187
  # only run when there are no validation errors. The return value is
204
- # placed into {#result}. By default, this method is run in a transaction
205
- # if ActiveRecord is available (see {.transaction}).
188
+ # placed into {#result}.
206
189
  #
207
190
  # @raise (see ActiveInteraction::Runnable#execute)
208
191
 
@@ -6,14 +6,12 @@ module ActiveInteraction
6
6
  #
7
7
  # @note Must be included after `ActiveModel::Validations`.
8
8
  #
9
- # Runs code in transactions and only provides the result if there are no
10
- # validation errors.
9
+ # Runs code and provides the result.
11
10
  #
12
11
  # @private
13
12
  module Runnable
14
13
  extend ActiveSupport::Concern
15
14
  include ActiveModel::Validations
16
- include ActiveInteraction::Transactable
17
15
 
18
16
  included do
19
17
  define_callbacks :execute
@@ -41,13 +39,8 @@ module ActiveInteraction
41
39
  #
42
40
  # @return (see #result)
43
41
  def result=(result)
44
- if errors.empty?
45
- @_interaction_result = result
46
- @_interaction_valid = true
47
- else
48
- @_interaction_result = nil
49
- @_interaction_valid = false
50
- end
42
+ @_interaction_result = result
43
+ @_interaction_valid = errors.empty?
51
44
  end
52
45
 
53
46
  # @return [Boolean]
@@ -82,15 +75,12 @@ module ActiveInteraction
82
75
  def run
83
76
  return unless valid?
84
77
 
85
- self.result = transaction do
78
+ self.result =
86
79
  begin
87
80
  run_callbacks(:execute) { execute }
88
81
  rescue Interrupt => interrupt
89
82
  merge_errors_onto_base(interrupt.outcome.errors)
90
-
91
- raise ActiveRecord::Rollback if self.class.transaction?
92
83
  end
93
- end
94
84
  end
95
85
 
96
86
  def merge_errors_onto_base(new_errors)