active_interaction 0.1.3 → 0.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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NWZlM2U4M2ZiMTYwNjRkOTRkNjQwYWM4OWU5OGM2ZWFhY2FjNDBhMA==
4
+ ODY0YTVjYmY2YTE4MGY3NWU1OGU5NTU1YjcwZWVmNjU1NDU1OGYwMA==
5
5
  data.tar.gz: !binary |-
6
- YjRjODdkZTczNWM0MWY1ZTQxMWY5NDUxMTcwNDRhYTI2YWNhYTY1MA==
6
+ MGYwNTY4Y2ZkMDRmNDMxZTIxNDYwMTc4YjA4YWQxN2I3ZmY2ZGIwMg==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- YWIxYmE0YzhjYzQwNGY1ZjMxY2M5ZTYyODEwYzA5N2UyYWM1ZjY5ZDIyN2Vk
10
- MTM3YzY5YzZkOGQyZjZmZGQzY2VlNDJkNjBhMjQ1YzQyNGRiMmY3NzRhMDc1
11
- MTQ0ZWM5Y2VhMGMxZTY1MjU4Nzg3YjQ5MzU2ZmNmMDhmZTdjNzY=
9
+ OWI3MDgwNGVlY2QxODU5ZGRhMjMyNDRkMzQ5YjI5YTRiM2VhMTU5N2JiYjAx
10
+ ZmEzNTMyODVlYjQyZGYzOWU4ZDViOGNhN2JmZWY1NDEwZTkxOGFlOTllZDAy
11
+ ZWZkZjQ0YmVjNmZhMzhjYTc1Mjk2ZmVlZTk2MjhmYTVmOTg0Mjc=
12
12
  data.tar.gz: !binary |-
13
- ZDQ3MDUwNDVmMDA3MmIwZDJiZmE4NDYxYTQ5ODIyMjY4ZmYwYTgwMDllYjhi
14
- YjZjNWNhODA2NWFhNjA0ZjY0ODliMTBhOWU3YTUzZDc5YTkyNWVmYjI2MDBl
15
- ZGI2Y2UxYjhhYjVjYTVmM2RhYjBlOTBiMWZlZDdmMjQwZmRjYWE=
13
+ MTQ5ZGZiMTU3ZTAzMzlkNTZmNjIxMzkwN2EwZWE3ZjAzNDZhZDUzYWU3MjUy
14
+ ZGFhM2YxNTIzYzA4ODI2NDQ2YzE0NmNjYjU3MjI5NGM2NWU3NWVmZGJmMDI2
15
+ M2IxZDRlNDVjZmZkNjAxYzEyMWY5YjliNjU4NzBhZTM0ZjhmNGM=
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Master
2
2
 
3
+ # 0.2.0
4
+
5
+ - Wrap interactions in ActiveRecord transactions if they're available.
6
+ - Add option to strip string values, which is enabled by default.
7
+ - Add support for strptime format strings on Date, DateTime, and Time filters.
8
+
3
9
  # 0.1.3
4
10
 
5
11
  - Fix bug that prevented `attr_accessor`s from working.
data/README.md CHANGED
@@ -22,7 +22,7 @@ This project uses [semantic versioning][].
22
22
  Add it to your Gemfile:
23
23
 
24
24
  ```ruby
25
- gem 'active_interaction', '~> 0.1.3'
25
+ gem 'active_interaction', '~> 0.2.0'
26
26
  ```
27
27
 
28
28
  And then execute:
@@ -91,7 +91,9 @@ end
91
91
  You may have noticed that ActiveInteraction::Base quacks like
92
92
  ActiveRecord::Base. It can use validations from your Rails application
93
93
  and check option validity with `valid?`. Any errors are added to
94
- `errors` which works exactly like an ActiveRecord model.
94
+ `errors` which works exactly like an ActiveRecord model. Additionally,
95
+ everything within the `execute` method is run in a transaction if
96
+ ActiveRecord is available.
95
97
 
96
98
  ## How do I call an interaction?
97
99
 
@@ -6,6 +6,7 @@ require 'active_interaction/overload_hash'
6
6
  require 'active_interaction/filter'
7
7
  require 'active_interaction/filter_method'
8
8
  require 'active_interaction/filter_methods'
9
+ require 'active_interaction/filters/abstract_date_time_filter'
9
10
  require 'active_interaction/filters/array_filter'
10
11
  require 'active_interaction/filters/boolean_filter'
11
12
  require 'active_interaction/filters/date_filter'
@@ -1,5 +1,10 @@
1
1
  require 'active_support/core_ext/hash/indifferent_access'
2
2
 
3
+ begin
4
+ require 'active_record'
5
+ rescue LoadError
6
+ end
7
+
3
8
  module ActiveInteraction
4
9
  # @abstract Subclass and override {#execute} to implement a custom
5
10
  # ActiveInteraction class.
@@ -69,12 +74,25 @@ module ActiveInteraction
69
74
  # Runs the business logic associated with the interaction. The method is
70
75
  # only run when there are no validation errors. The return value is
71
76
  # placed into {#result}. This method must be overridden in the subclass.
77
+ # This method is run in a transaction if ActiveRecord is available.
72
78
  #
73
79
  # @raise [NotImplementedError] if the method is not defined.
74
80
  def execute
75
81
  raise NotImplementedError
76
82
  end
77
83
 
84
+ # @private
85
+ def self.transaction
86
+ return unless block_given?
87
+
88
+ if defined?(ActiveRecord)
89
+ ::ActiveRecord::Base.transaction { yield }
90
+ else
91
+ yield
92
+ end
93
+ end
94
+ private_class_method :transaction
95
+
78
96
  # @!macro [new] run_attributes
79
97
  # @param options [Hash] Attribute values to set.
80
98
 
@@ -85,11 +103,12 @@ module ActiveInteraction
85
103
  # @return [ActiveInteraction::Base] An instance of the class `run` is
86
104
  # called on.
87
105
  def self.run(options = {})
88
- me = new(options)
89
-
90
- me.instance_variable_set(:@result, me.execute) if me.valid?
91
-
92
- me
106
+ new(options).tap do |interaction|
107
+ if interaction.valid?
108
+ result = transaction { interaction.execute }
109
+ interaction.instance_variable_set(:@result, result)
110
+ end
111
+ end
93
112
  end
94
113
 
95
114
  # Like {.run} except that it returns the value of {#execute} or raises an
@@ -18,24 +18,14 @@ module ActiveInteraction
18
18
  def self.prepare(key, value, options = {}, &block)
19
19
  case value
20
20
  when NilClass
21
- return_nil(options[:allow_nil])
21
+ if options[:allow_nil]
22
+ nil
23
+ else
24
+ raise MissingValue
25
+ end
22
26
  else
23
- bad_value
27
+ raise InvalidValue
24
28
  end
25
29
  end
26
-
27
- def self.return_nil(allow_nil)
28
- if allow_nil
29
- nil
30
- else
31
- raise MissingValue
32
- end
33
- end
34
- private_class_method :return_nil
35
-
36
- def self.bad_value
37
- raise InvalidValue
38
- end
39
- private_class_method :bad_value
40
30
  end
41
31
  end
@@ -0,0 +1,25 @@
1
+ module ActiveInteraction
2
+ # @private
3
+ class AbstractDateTimeFilter < Filter
4
+ def self.prepare(key, value, options = {}, &block)
5
+ klass = options.delete(:class)
6
+
7
+ case value
8
+ when klass
9
+ value
10
+ when String
11
+ begin
12
+ if options.has_key?(:format)
13
+ klass.strptime(value, options[:format])
14
+ else
15
+ klass.parse(value)
16
+ end
17
+ rescue ArgumentError
18
+ super
19
+ end
20
+ else
21
+ super
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,31 +1,26 @@
1
1
  module ActiveInteraction
2
2
  class Base
3
3
  # Creates accessors for the attributes and ensures that values passed to
4
- # the attributes are Dates. String values are processed using `parse`.
4
+ # the attributes are Dates. String values are processed using `parse`
5
+ # unless the format option is given, in which case they will be processed
6
+ # with `strptime`.
5
7
  #
6
8
  # @macro attribute_method_params
9
+ # @option options [String] :format Parse strings using this format string.
7
10
  #
8
11
  # @example
9
12
  # date :birthday
10
13
  #
14
+ # @example
15
+ # date :birthday, format: '%Y-%m-%d'
16
+ #
11
17
  # @method self.date(*attributes, options = {})
12
18
  end
13
19
 
14
20
  # @private
15
- class DateFilter < Filter
21
+ class DateFilter < AbstractDateTimeFilter
16
22
  def self.prepare(key, value, options = {}, &block)
17
- case value
18
- when Date
19
- value
20
- when String
21
- begin
22
- Date.parse(value)
23
- rescue ArgumentError
24
- bad_value
25
- end
26
- else
27
- super
28
- end
23
+ super(key, value, options.merge(class: Date), &block)
29
24
  end
30
25
  end
31
26
  end
@@ -1,32 +1,26 @@
1
1
  module ActiveInteraction
2
2
  class Base
3
3
  # Creates accessors for the attributes and ensures that values passed to
4
- # the attributes are DateTimes. String values are processed using
5
- # `parse`.
4
+ # the attributes are DateTimes. String values are processed using `parse`
5
+ # unless the format option is given, in which case they will be processed
6
+ # with `strptime`.
6
7
  #
7
8
  # @macro attribute_method_params
9
+ # @option options [String] :format Parse strings using this format string.
8
10
  #
9
11
  # @example
10
12
  # date_time :start_date
11
13
  #
14
+ # @example
15
+ # date_time :start_date, format: '%Y-%m-%dT%H:%M:%S'
16
+ #
12
17
  # @method self.date_time(*attributes, options = {})
13
18
  end
14
19
 
15
20
  # @private
16
- class DateTimeFilter < Filter
21
+ class DateTimeFilter < AbstractDateTimeFilter
17
22
  def self.prepare(key, value, options = {}, &block)
18
- case value
19
- when DateTime
20
- value
21
- when String
22
- begin
23
- DateTime.parse(value)
24
- rescue ArgumentError
25
- bad_value
26
- end
27
- else
28
- super
29
- end
23
+ super(key, value, options.merge(class: DateTime), &block)
30
24
  end
31
25
  end
32
26
  end
@@ -22,7 +22,7 @@ module ActiveInteraction
22
22
  begin
23
23
  Float(value)
24
24
  rescue ArgumentError
25
- bad_value
25
+ super
26
26
  end
27
27
  else
28
28
  super
@@ -21,7 +21,7 @@ module ActiveInteraction
21
21
  begin
22
22
  Integer(value)
23
23
  rescue ArgumentError
24
- bad_value
24
+ super
25
25
  end
26
26
  else
27
27
  super
@@ -4,6 +4,8 @@ module ActiveInteraction
4
4
  # the attributes are Strings.
5
5
  #
6
6
  # @macro attribute_method_params
7
+ # @option options [Boolean] :strip (true) Strip leading and trailing
8
+ # whitespace.
7
9
  #
8
10
  # @example
9
11
  # string :first_name
@@ -16,7 +18,7 @@ module ActiveInteraction
16
18
  def self.prepare(key, value, options = {}, &block)
17
19
  case value
18
20
  when String
19
- value
21
+ options.fetch(:strip, true) ? value.strip : value
20
22
  else
21
23
  super
22
24
  end
@@ -2,33 +2,30 @@ module ActiveInteraction
2
2
  class Base
3
3
  # Creates accessors for the attributes and ensures that values passed to
4
4
  # the attributes are Times. Numeric values are processed using `at`.
5
- # Strings are processed using `parse`. If `Time.zone` is available it
6
- # will be used so that the values are time zone aware.
5
+ # Strings are processed using `parse` unless the format option is given,
6
+ # in which case they will be processed with `strptime`. If `Time.zone` is
7
+ # available it will be used so that the values are time zone aware.
7
8
  #
8
9
  # @macro attribute_method_params
10
+ # @option options [String] :format Parse strings using this format string.
9
11
  #
10
12
  # @example
11
13
  # time :start_date
12
14
  #
15
+ # @example
16
+ # date_time :start_date, format: '%Y-%m-%dT%H:%M:%S'
17
+ #
13
18
  # @method self.time(*attributes, options = {})
14
19
  end
15
20
 
16
21
  # @private
17
- class TimeFilter < Filter
22
+ class TimeFilter < AbstractDateTimeFilter
18
23
  def self.prepare(key, value, options = {}, &block)
19
24
  case value
20
- when Time
21
- value
22
- when String
23
- begin
24
- time.parse(value)
25
- rescue ArgumentError
26
- bad_value
27
- end
28
25
  when Numeric
29
26
  time.at(value)
30
27
  else
31
- super
28
+ super(key, value, options.merge(class: time), &block)
32
29
  end
33
30
  end
34
31
 
@@ -1,3 +1,3 @@
1
1
  module ActiveInteraction
2
- VERSION = Gem::Version.new('0.1.3')
2
+ VERSION = Gem::Version.new('0.2.0')
3
3
  end
@@ -168,6 +168,31 @@ describe ActiveInteraction::Base do
168
168
  it 'sets the result' do
169
169
  expect(outcome.result).to eq thing
170
170
  end
171
+
172
+ it 'calls transaction' do
173
+ allow(described_class).to receive(:transaction)
174
+ outcome
175
+ expect(described_class).to have_received(:transaction).once.
176
+ with(no_args)
177
+ end
178
+
179
+ context 'with ActiveRecord' do
180
+ before do
181
+ ActiveRecord = Class.new
182
+ ActiveRecord::Base = double
183
+ allow(ActiveRecord::Base).to receive(:transaction)
184
+ end
185
+
186
+ after do
187
+ Object.send(:remove_const, :ActiveRecord)
188
+ end
189
+
190
+ it 'calls ActiveRecord::Base.transaction' do
191
+ outcome
192
+ expect(ActiveRecord::Base).to have_received(:transaction).once.
193
+ with(no_args)
194
+ end
195
+ end
171
196
  end
172
197
  end
173
198
 
@@ -19,6 +19,16 @@ describe ActiveInteraction::DateFilter do
19
19
  it 'parses the String' do
20
20
  expect(result).to eql Date.parse(value)
21
21
  end
22
+
23
+ context 'with options[:format]' do
24
+ let(:value) { '01012001' }
25
+
26
+ before { options.merge!(format: '%m%d%Y') }
27
+
28
+ it 'parses the String' do
29
+ expect(result).to eql Date.strptime(value, options[:format])
30
+ end
31
+ end
22
32
  end
23
33
 
24
34
  context 'with an invalid String' do
@@ -27,6 +37,14 @@ describe ActiveInteraction::DateFilter do
27
37
  it 'raises an error' do
28
38
  expect { result }.to raise_error ActiveInteraction::InvalidValue
29
39
  end
40
+
41
+ context 'with options[:format]' do
42
+ before { options.merge!(format: '%m%d%Y') }
43
+
44
+ it 'raises an error' do
45
+ expect { result }.to raise_error ActiveInteraction::InvalidValue
46
+ end
47
+ end
30
48
  end
31
49
  end
32
50
  end
@@ -19,6 +19,16 @@ describe ActiveInteraction::DateTimeFilter do
19
19
  it 'parses the String' do
20
20
  expect(result).to eql DateTime.parse(value)
21
21
  end
22
+
23
+ context 'with options[:format]' do
24
+ let(:value) { '01010101012001' }
25
+
26
+ before { options.merge!(format: '%S%M%H%m%d%Y') }
27
+
28
+ it 'parses the String' do
29
+ expect(result).to eql DateTime.strptime(value, options[:format])
30
+ end
31
+ end
22
32
  end
23
33
 
24
34
  context 'with an invalid String' do
@@ -27,6 +37,14 @@ describe ActiveInteraction::DateTimeFilter do
27
37
  it 'raises an error' do
28
38
  expect { result }.to raise_error ActiveInteraction::InvalidValue
29
39
  end
40
+
41
+ context 'with options[:format]' do
42
+ before { options.merge!(format: '%S%M%H%m%d%Y') }
43
+
44
+ it 'raises an error' do
45
+ expect { result }.to raise_error ActiveInteraction::InvalidValue
46
+ end
47
+ end
30
48
  end
31
49
  end
32
50
  end
@@ -12,5 +12,21 @@ describe ActiveInteraction::StringFilter do
12
12
  expect(result).to eql value
13
13
  end
14
14
  end
15
+
16
+ context 'with a strippable String' do
17
+ let(:value) { " #{SecureRandom.hex} " }
18
+
19
+ it 'returns the stripped String' do
20
+ expect(result).to eql value.strip
21
+ end
22
+
23
+ context 'with options[:strip] as false' do
24
+ before { options.merge!(strip: false) }
25
+
26
+ it 'returns the String' do
27
+ expect(result).to eql value
28
+ end
29
+ end
30
+ end
15
31
  end
16
32
  end
@@ -36,6 +36,16 @@ describe ActiveInteraction::TimeFilter do
36
36
  it 'parses the String' do
37
37
  expect(result).to eql Time.parse(value)
38
38
  end
39
+
40
+ context 'with options[:format]' do
41
+ let(:value) { '01010101012001' }
42
+
43
+ before { options.merge!(format: '%S%M%H%d%m%Y') }
44
+
45
+ it 'parses the String' do
46
+ expect(result).to eql Time.strptime(value, options[:format])
47
+ end
48
+ end
39
49
  end
40
50
 
41
51
  context 'with an invalid String' do
@@ -44,6 +54,14 @@ describe ActiveInteraction::TimeFilter do
44
54
  it 'raises an error' do
45
55
  expect { result }.to raise_error ActiveInteraction::InvalidValue
46
56
  end
57
+
58
+ context 'with options[:format]' do
59
+ before { options.merge!(format: '%S%M%H%d%m%Y') }
60
+
61
+ it 'raises an error' do
62
+ expect { result }.to raise_error ActiveInteraction::InvalidValue
63
+ end
64
+ end
47
65
  end
48
66
  end
49
67
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_interaction
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Lasseigne
@@ -150,6 +150,7 @@ files:
150
150
  - lib/active_interaction/filter.rb
151
151
  - lib/active_interaction/filter_method.rb
152
152
  - lib/active_interaction/filter_methods.rb
153
+ - lib/active_interaction/filters/abstract_date_time_filter.rb
153
154
  - lib/active_interaction/filters/array_filter.rb
154
155
  - lib/active_interaction/filters/boolean_filter.rb
155
156
  - lib/active_interaction/filters/date_filter.rb