active_interaction 1.1.7 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -1
- data/README.md +3 -2
- data/lib/active_interaction.rb +7 -1
- data/lib/active_interaction/base.rb +61 -13
- data/lib/active_interaction/concerns/runnable.rb +2 -18
- data/lib/active_interaction/concerns/transactable.rb +72 -0
- data/lib/active_interaction/errors.rb +7 -0
- data/lib/active_interaction/filter.rb +23 -2
- data/lib/active_interaction/filter_column.rb +59 -0
- data/lib/active_interaction/filters/abstract_date_time_filter.rb +14 -0
- data/lib/active_interaction/filters/abstract_filter.rb +4 -7
- data/lib/active_interaction/filters/abstract_numeric_filter.rb +4 -0
- data/lib/active_interaction/filters/boolean_filter.rb +4 -0
- data/lib/active_interaction/filters/date_time_filter.rb +3 -0
- data/lib/active_interaction/filters/decimal_filter.rb +54 -0
- data/lib/active_interaction/filters/file_filter.rb +4 -0
- data/lib/active_interaction/filters/time_filter.rb +4 -0
- data/lib/active_interaction/grouped_input.rb +24 -0
- data/lib/active_interaction/locale/en.yml +1 -0
- data/lib/active_interaction/modules/input_processor.rb +40 -0
- data/lib/active_interaction/version.rb +1 -1
- data/spec/active_interaction/base_spec.rb +90 -29
- data/spec/active_interaction/concerns/runnable_spec.rb +0 -26
- data/spec/active_interaction/concerns/transactable_spec.rb +114 -0
- data/spec/active_interaction/filter_column_spec.rb +96 -0
- data/spec/active_interaction/filter_spec.rb +15 -11
- data/spec/active_interaction/filters/array_filter_spec.rb +13 -5
- data/spec/active_interaction/filters/boolean_filter_spec.rb +6 -0
- data/spec/active_interaction/filters/date_filter_spec.rb +76 -5
- data/spec/active_interaction/filters/date_time_filter_spec.rb +87 -5
- data/spec/active_interaction/filters/decimal_filter_spec.rb +70 -0
- data/spec/active_interaction/filters/file_filter_spec.rb +11 -3
- data/spec/active_interaction/filters/float_filter_spec.rb +12 -4
- data/spec/active_interaction/filters/hash_filter_spec.rb +16 -8
- data/spec/active_interaction/filters/integer_filter_spec.rb +12 -4
- data/spec/active_interaction/filters/model_filter_spec.rb +12 -5
- data/spec/active_interaction/filters/string_filter_spec.rb +11 -3
- data/spec/active_interaction/filters/symbol_filter_spec.rb +10 -2
- data/spec/active_interaction/filters/time_filter_spec.rb +87 -5
- data/spec/active_interaction/grouped_input_spec.rb +19 -0
- data/spec/active_interaction/modules/input_processor_spec.rb +75 -0
- data/spec/support/filters.rb +6 -0
- metadata +16 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1c1d5b914798cc9b8b70729d645f8ef4a0d4031f
|
4
|
+
data.tar.gz: ae3a8b0db8afdd311a44d9c73e1fcb2788d11086
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 95496b5a5f1b4d837b7d2e1231c38ae6522aac69fd0bcc5f5c70739f26428299e4aaf89d43791558b97c5f90b5e4e422b9ade2db76aec0371da27fcb078a9fa8
|
7
|
+
data.tar.gz: 7fcb37ddb4a3bd9beb005401448304314a6996fe99c0b4e6ddcb1f684c456cd861fcfdaddc75aa7a7ff24571f1a52f2783406c6ff8af519d5295d6c1287df61d
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# [Master][]
|
2
2
|
|
3
|
+
# [1.2.0][] (2014-04-30)
|
4
|
+
|
5
|
+
- Add a decimal filter.
|
6
|
+
- Add support for disabling and modifying transactions through the
|
7
|
+
`transaction` helper method.
|
8
|
+
- Add support for `column_for_attribute` which provides better interoperability with gems like Formtastic and Simple Form.
|
9
|
+
|
3
10
|
# [1.1.7][] (2014-04-30)
|
4
11
|
|
5
12
|
- Fix a bug that leaked validators among all child classes.
|
@@ -186,7 +193,8 @@
|
|
186
193
|
|
187
194
|
- Initial release.
|
188
195
|
|
189
|
-
[master]: https://github.com/orgsync/active_interaction/compare/v1.
|
196
|
+
[master]: https://github.com/orgsync/active_interaction/compare/v1.2.0...master
|
197
|
+
[1.2.0]: https://github.com/orgsync/active_interaction/compare/v1.1.7...v1.2.0
|
190
198
|
[1.1.7]: https://github.com/orgsync/active_interaction/compare/v1.1.6...v1.1.7
|
191
199
|
[1.1.6]: https://github.com/orgsync/active_interaction/compare/v1.1.5...v1.1.6
|
192
200
|
[1.1.5]: https://github.com/orgsync/active_interaction/compare/v1.1.4...v1.1.5
|
data/README.md
CHANGED
@@ -25,7 +25,7 @@ This project uses [semantic versioning][13].
|
|
25
25
|
Add it to your Gemfile:
|
26
26
|
|
27
27
|
```ruby
|
28
|
-
gem 'active_interaction', '~> 1.
|
28
|
+
gem 'active_interaction', '~> 1.2'
|
29
29
|
```
|
30
30
|
|
31
31
|
And then execute:
|
@@ -94,7 +94,7 @@ end
|
|
94
94
|
You may have noticed that ActiveInteraction::Base quacks like
|
95
95
|
ActiveRecord::Base. It can use validations from your Rails application
|
96
96
|
and check option validity with `valid?`. Any errors are added to
|
97
|
-
`errors` which works exactly like an ActiveRecord model.
|
97
|
+
`errors` which works exactly like an ActiveRecord model. By default,
|
98
98
|
everything within the `execute` method is run in a transaction if
|
99
99
|
ActiveRecord is available.
|
100
100
|
|
@@ -247,6 +247,7 @@ hsilgne:
|
|
247
247
|
boolean: naeloob
|
248
248
|
date: etad
|
249
249
|
date_time: emit etad
|
250
|
+
decimal: lamiced
|
250
251
|
file: elif
|
251
252
|
float: taolf
|
252
253
|
hash: hsah
|
data/lib/active_interaction.rb
CHANGED
@@ -8,10 +8,15 @@ require 'active_interaction/errors'
|
|
8
8
|
require 'active_interaction/concerns/active_modelable'
|
9
9
|
require 'active_interaction/concerns/hashable'
|
10
10
|
require 'active_interaction/concerns/missable'
|
11
|
+
require 'active_interaction/concerns/transactable'
|
11
12
|
require 'active_interaction/concerns/runnable'
|
12
13
|
|
14
|
+
require 'active_interaction/grouped_input'
|
15
|
+
|
16
|
+
require 'active_interaction/modules/input_processor'
|
13
17
|
require 'active_interaction/modules/validation'
|
14
18
|
|
19
|
+
require 'active_interaction/filter_column'
|
15
20
|
require 'active_interaction/filter'
|
16
21
|
require 'active_interaction/filters/abstract_filter'
|
17
22
|
require 'active_interaction/filters/abstract_date_time_filter'
|
@@ -20,6 +25,7 @@ require 'active_interaction/filters/array_filter'
|
|
20
25
|
require 'active_interaction/filters/boolean_filter'
|
21
26
|
require 'active_interaction/filters/date_filter'
|
22
27
|
require 'active_interaction/filters/date_time_filter'
|
28
|
+
require 'active_interaction/filters/decimal_filter'
|
23
29
|
require 'active_interaction/filters/file_filter'
|
24
30
|
require 'active_interaction/filters/float_filter'
|
25
31
|
require 'active_interaction/filters/hash_filter'
|
@@ -41,5 +47,5 @@ I18n.load_path << File.expand_path(
|
|
41
47
|
#
|
42
48
|
# @since 1.0.0
|
43
49
|
#
|
44
|
-
# @version 1.
|
50
|
+
# @version 1.2.0
|
45
51
|
module ActiveInteraction end
|
@@ -58,6 +58,28 @@ module ActiveInteraction
|
|
58
58
|
#
|
59
59
|
# @raise (see ActiveInteraction::Runnable::ClassMethods#run!)
|
60
60
|
|
61
|
+
# @!method transaction(enable, options = {})
|
62
|
+
# Configure transactions by enabling or disabling them and setting
|
63
|
+
# their options.
|
64
|
+
#
|
65
|
+
# @example Disable transactions
|
66
|
+
# Class.new(ActiveInteraction::Base) do
|
67
|
+
# transaction false
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# @example Use different transaction options
|
71
|
+
# Class.new(ActiveInteraction::Base) do
|
72
|
+
# transaction true, isolation: :serializable
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# @param enable [Boolean] Should transactions be enabled?
|
76
|
+
# @param options [Hash] Options to pass to
|
77
|
+
# `ActiveRecord::Base.transaction`.
|
78
|
+
#
|
79
|
+
# @return [nil]
|
80
|
+
#
|
81
|
+
# @since 1.2.0
|
82
|
+
|
61
83
|
# Get or set the description.
|
62
84
|
#
|
63
85
|
# @example
|
@@ -104,7 +126,7 @@ module ActiveInteraction
|
|
104
126
|
# @param name [Symbol]
|
105
127
|
# @param options [Hash]
|
106
128
|
def add_filter(klass, name, options, &block)
|
107
|
-
fail InvalidFilterError, name.inspect if reserved?(name)
|
129
|
+
fail InvalidFilterError, name.inspect if InputProcessor.reserved?(name)
|
108
130
|
|
109
131
|
initialize_filter(klass.new(name, options, &block))
|
110
132
|
end
|
@@ -148,13 +170,6 @@ module ActiveInteraction
|
|
148
170
|
|
149
171
|
filter.default if filter.default?
|
150
172
|
end
|
151
|
-
|
152
|
-
# @param symbol [Symbol]
|
153
|
-
#
|
154
|
-
# @return [Boolean]
|
155
|
-
def reserved?(symbol)
|
156
|
-
symbol.to_s.start_with?('_interaction_')
|
157
|
-
end
|
158
173
|
end
|
159
174
|
|
160
175
|
# @param inputs [Hash{Symbol => Object}] Attribute values to set.
|
@@ -166,6 +181,31 @@ module ActiveInteraction
|
|
166
181
|
process_inputs(inputs.symbolize_keys)
|
167
182
|
end
|
168
183
|
|
184
|
+
# Returns the column object for the named filter.
|
185
|
+
#
|
186
|
+
# @param name [Symbol] The name of a filter.
|
187
|
+
#
|
188
|
+
# @example
|
189
|
+
# class Interaction < ActiveInteraction::Base
|
190
|
+
# string :email, default: nil
|
191
|
+
#
|
192
|
+
# def execute; end
|
193
|
+
# end
|
194
|
+
#
|
195
|
+
# Interaction.new.column_for_attribute(:email)
|
196
|
+
# # => #<ActiveInteraction::FilterColumn:0x007faebeb2a6c8 @type=:string>
|
197
|
+
#
|
198
|
+
# Interaction.new.column_for_attribute(:not_a_filter)
|
199
|
+
# # => nil
|
200
|
+
#
|
201
|
+
# @return [FilterColumn, nil]
|
202
|
+
#
|
203
|
+
# @since 1.2.0
|
204
|
+
def column_for_attribute(name)
|
205
|
+
filter = self.class.filters[name]
|
206
|
+
FilterColumn.intern(filter.database_column_type) if filter
|
207
|
+
end
|
208
|
+
|
169
209
|
# @!method compose(other, inputs = {})
|
170
210
|
# Run another interaction and return its result. If the other interaction
|
171
211
|
# fails, halt execution.
|
@@ -179,9 +219,9 @@ module ActiveInteraction
|
|
179
219
|
# @abstract
|
180
220
|
#
|
181
221
|
# Runs the business logic associated with the interaction. This method is
|
182
|
-
#
|
183
|
-
#
|
184
|
-
#
|
222
|
+
# only run when there are no validation errors. The return value is
|
223
|
+
# placed into {#result}. By default, this method is run in a transaction
|
224
|
+
# if ActiveRecord is available (see {.transaction}).
|
185
225
|
#
|
186
226
|
# @raise (see ActiveInteraction::Runnable#execute)
|
187
227
|
|
@@ -200,11 +240,19 @@ module ActiveInteraction
|
|
200
240
|
# @param inputs [Hash{Symbol => Object}]
|
201
241
|
def process_inputs(inputs)
|
202
242
|
inputs.each do |key, value|
|
203
|
-
fail InvalidValueError, key.inspect if
|
243
|
+
fail InvalidValueError, key.inspect if InputProcessor.reserved?(key)
|
204
244
|
|
205
|
-
|
245
|
+
populate_reader(key, value)
|
206
246
|
end
|
207
247
|
|
248
|
+
populate_filters(InputProcessor.process(inputs))
|
249
|
+
end
|
250
|
+
|
251
|
+
def populate_reader(key, value)
|
252
|
+
instance_variable_set("@#{key}", value) if respond_to?(key)
|
253
|
+
end
|
254
|
+
|
255
|
+
def populate_filters(inputs)
|
208
256
|
self.class.filters.each do |name, filter|
|
209
257
|
begin
|
210
258
|
public_send("#{name}=", filter.clean(inputs[name]))
|
@@ -1,22 +1,5 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
begin
|
4
|
-
require 'active_record'
|
5
|
-
rescue LoadError
|
6
|
-
# rubocop:disable Documentation
|
7
|
-
module ActiveRecord
|
8
|
-
Rollback = Class.new(ActiveInteraction::Error)
|
9
|
-
|
10
|
-
class Base
|
11
|
-
def self.transaction(*)
|
12
|
-
yield
|
13
|
-
rescue Rollback
|
14
|
-
# rollbacks are silently swallowed
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
3
|
module ActiveInteraction
|
21
4
|
# @abstract Include and override {#execute} to implement a custom Runnable
|
22
5
|
# class.
|
@@ -30,6 +13,7 @@ module ActiveInteraction
|
|
30
13
|
module Runnable
|
31
14
|
extend ActiveSupport::Concern
|
32
15
|
include ActiveModel::Validations
|
16
|
+
include ActiveInteraction::Transactable
|
33
17
|
|
34
18
|
included do
|
35
19
|
define_callbacks :execute
|
@@ -98,7 +82,7 @@ module ActiveInteraction
|
|
98
82
|
def run
|
99
83
|
return unless valid?
|
100
84
|
|
101
|
-
self.result =
|
85
|
+
self.result = transaction do
|
102
86
|
begin
|
103
87
|
run_callbacks(:execute) { execute }
|
104
88
|
rescue Interrupt => interrupt
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'active_record'
|
5
|
+
rescue LoadError
|
6
|
+
module ActiveRecord # rubocop:disable Documentation
|
7
|
+
Rollback = Class.new(ActiveInteraction::Error)
|
8
|
+
|
9
|
+
class Base # rubocop:disable Documentation
|
10
|
+
def self.transaction(*)
|
11
|
+
yield
|
12
|
+
rescue Rollback
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ActiveInteraction
|
19
|
+
# @private
|
20
|
+
#
|
21
|
+
# Execute code in a transaction. If ActiveRecord isn't available, don't do
|
22
|
+
# anything special.
|
23
|
+
#
|
24
|
+
# @since 1.2.0
|
25
|
+
module Transactable
|
26
|
+
extend ActiveSupport::Concern
|
27
|
+
|
28
|
+
# @yield []
|
29
|
+
def transaction
|
30
|
+
return unless block_given?
|
31
|
+
|
32
|
+
if self.class.transaction?
|
33
|
+
ActiveRecord::Base.transaction(self.class.transaction_options) do
|
34
|
+
yield
|
35
|
+
end
|
36
|
+
else
|
37
|
+
yield
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module ClassMethods # rubocop:disable Documentation
|
42
|
+
# @param enable [Boolean]
|
43
|
+
# @param options [Hash]
|
44
|
+
#
|
45
|
+
# @return [nil]
|
46
|
+
def transaction(enable, options = {})
|
47
|
+
@_interaction_transaction_enabled = enable
|
48
|
+
@_interaction_transaction_options = options
|
49
|
+
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Boolean]
|
54
|
+
def transaction?
|
55
|
+
unless defined?(@_interaction_transaction_enabled)
|
56
|
+
@_interaction_transaction_enabled = true
|
57
|
+
end
|
58
|
+
|
59
|
+
@_interaction_transaction_enabled
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [Hash]
|
63
|
+
def transaction_options
|
64
|
+
unless defined?(@_interaction_transaction_options)
|
65
|
+
@_interaction_transaction_options = {}
|
66
|
+
end
|
67
|
+
|
68
|
+
@_interaction_transaction_options
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -47,6 +47,13 @@ module ActiveInteraction
|
|
47
47
|
# @return [Class]
|
48
48
|
NoDefaultError = Class.new(Error)
|
49
49
|
|
50
|
+
# Raised if a reserved name is used.
|
51
|
+
#
|
52
|
+
# @return [Class]
|
53
|
+
#
|
54
|
+
# @since 1.2.0
|
55
|
+
ReservedNameError = Class.new(Error)
|
56
|
+
|
50
57
|
# Raised if a user-supplied value to a nested hash input is invalid.
|
51
58
|
#
|
52
59
|
# @return [Class]
|
@@ -67,9 +67,11 @@ module ActiveInteraction
|
|
67
67
|
#
|
68
68
|
# @see .factory
|
69
69
|
def slug
|
70
|
-
|
70
|
+
return @slug if defined?(@slug)
|
71
|
+
|
72
|
+
match = name[CLASS_REGEXP, 1]
|
71
73
|
fail InvalidClassError, name unless match
|
72
|
-
match.
|
74
|
+
@slug = match.underscore.to_sym
|
73
75
|
end
|
74
76
|
|
75
77
|
# @param klass [Class]
|
@@ -145,6 +147,8 @@ module ActiveInteraction
|
|
145
147
|
fail NoDefaultError, name unless default?
|
146
148
|
|
147
149
|
value = raw_default
|
150
|
+
fail InvalidValueError if value.is_a?(GroupedInput)
|
151
|
+
|
148
152
|
cast(value)
|
149
153
|
rescue InvalidValueError, MissingValueError
|
150
154
|
raise InvalidDefaultError, "#{name}: #{value.inspect}"
|
@@ -195,6 +199,23 @@ module ActiveInteraction
|
|
195
199
|
end
|
196
200
|
end
|
197
201
|
|
202
|
+
# Gets the type of database column that would represent the filter data.
|
203
|
+
#
|
204
|
+
# @example
|
205
|
+
# ActiveInteraction::TimeFilter.new(Time.now).database_column_type
|
206
|
+
# # => :datetime
|
207
|
+
# @example
|
208
|
+
# ActiveInteraction::Filter.new(:example).database_column_type
|
209
|
+
# # => :string
|
210
|
+
#
|
211
|
+
# @return [Symbol] A database column type. If no sensible mapping exists,
|
212
|
+
# returns `:string`.
|
213
|
+
#
|
214
|
+
# @since 1.2.0
|
215
|
+
def database_column_type
|
216
|
+
:string
|
217
|
+
end
|
218
|
+
|
198
219
|
private
|
199
220
|
|
200
221
|
# @return [Object]
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module ActiveInteraction
|
4
|
+
# A minimal implementation of an `ActiveRecord::ConnectionAdapters::Column`.
|
5
|
+
#
|
6
|
+
# @since 1.2.0
|
7
|
+
class FilterColumn
|
8
|
+
# @return [nil]
|
9
|
+
attr_reader :limit
|
10
|
+
|
11
|
+
# @return [Symbol]
|
12
|
+
attr_reader :type
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# Find or create the `FilterColumn` for a specific type.
|
16
|
+
#
|
17
|
+
# @param type [Symbol] A database column type.
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# FilterColumn.intern(:string)
|
21
|
+
# # => #<ActiveInteraction::FilterColumn:0x007feeaa649c @type=:string>
|
22
|
+
#
|
23
|
+
# FilterColumn.intern(:string)
|
24
|
+
# # => #<ActiveInteraction::FilterColumn:0x007feeaa649c @type=:string>
|
25
|
+
#
|
26
|
+
# FilterColumn.intern(:boolean)
|
27
|
+
# # => #<ActiveInteraction::FilterColumn:0x007feeab8a08 @type=:boolean>
|
28
|
+
#
|
29
|
+
# @return [FilterColumn]
|
30
|
+
def intern(type)
|
31
|
+
@columns ||= {}
|
32
|
+
@columns[type] ||= new(type)
|
33
|
+
end
|
34
|
+
|
35
|
+
private :new
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param type [type] The database column type.
|
39
|
+
#
|
40
|
+
# @private
|
41
|
+
def initialize(type)
|
42
|
+
@type = type
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns `true` if the column is either of type :integer or :float.
|
46
|
+
#
|
47
|
+
# @return [Boolean]
|
48
|
+
def number?
|
49
|
+
[:integer, :float].include?(type)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns `true` if the column is of type :string.
|
53
|
+
#
|
54
|
+
# @return [Boolean]
|
55
|
+
def text?
|
56
|
+
type == :string
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -17,11 +17,17 @@ module ActiveInteraction
|
|
17
17
|
value
|
18
18
|
when String
|
19
19
|
convert(value)
|
20
|
+
when GroupedInput
|
21
|
+
convert(stringify(value))
|
20
22
|
else
|
21
23
|
super
|
22
24
|
end
|
23
25
|
end
|
24
26
|
|
27
|
+
def database_column_type
|
28
|
+
self.class.slug
|
29
|
+
end
|
30
|
+
|
25
31
|
private
|
26
32
|
|
27
33
|
def convert(value)
|
@@ -49,5 +55,13 @@ module ActiveInteraction
|
|
49
55
|
def klasses
|
50
56
|
[klass]
|
51
57
|
end
|
58
|
+
|
59
|
+
# @return [String]
|
60
|
+
def stringify(value)
|
61
|
+
date = %w[1 2 3].map { |key| value[key] }.join('-')
|
62
|
+
time = %w[4 5 6].map { |key| value[key] }.join(':')
|
63
|
+
|
64
|
+
"#{date} #{time}"
|
65
|
+
end
|
52
66
|
end
|
53
67
|
end
|