active_interaction 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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