active_interaction 1.0.5 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2bba7422c892bf66b8115b8945c6aea7b556ba96
4
- data.tar.gz: 3e5978c3cf8468a93b4355a207f50162fd819a3d
3
+ metadata.gz: 940655c81d5b5db1e207b9d317eee3b971ed9fbe
4
+ data.tar.gz: 25b6b1d2f3b217bd4e2da202765cbc3058e90da2
5
5
  SHA512:
6
- metadata.gz: cf7010af04046f64237456948ec72b93daf4d18e112b4390cbf2117e25babe24513d8c9802f795a1c892a43d62e2f3b7ba1bb28f22c21afa30699f899df1112f
7
- data.tar.gz: 5fe9bc9e56874a63aca92dd7b5bf43bb24c94c8049ffd51984c5bb3c53eb610f8381b95ccf126008dd0fe57eca14a4da47709494ed8237b608176fd37d0b7f20
6
+ metadata.gz: 7d326ebbed5ed325693ba080bf7b020e9d40d3b23ae832038880782363150af71fefb5b34ea2ac256de1a561062c3fba949be9f3c87728baff587b98ce49d899
7
+ data.tar.gz: 2f17941a1de3e1c4ad74c132e93c16dea5a9239bc3cfd03906953f499721c87001acf91a21cf3d89a92fb497039134667859851270781e1225a7f7b2a009d729
@@ -1,5 +1,16 @@
1
1
  # [Master][]
2
2
 
3
+ # [1.1.0][] (2014-02-28)
4
+
5
+ - Only set instance variables for attributes with readers defined.
6
+ - Support `:only` and `:except` options simultaneously with `import_filters`.
7
+ Previously this raised an `ArgumentError`.
8
+ - Support passing a single symbol to `:only` and `:except`. Previously an Array
9
+ was required.
10
+ - Speed up many filters by caching class constants.
11
+ - Add support for callbacks around `execute`.
12
+ - Allow callable defaults.
13
+
3
14
  # [1.0.5][] (2014-02-25)
4
15
 
5
16
  - Rollback database changes when `compose` fails.
@@ -143,7 +154,8 @@
143
154
 
144
155
  - Initial release.
145
156
 
146
- [master]: https://github.com/orgsync/active_interaction/compare/v1.0.5...master
157
+ [master]: https://github.com/orgsync/active_interaction/compare/v1.1.0...master
158
+ [1.1.0]: https://github.com/orgsync/active_interaction/compare/v1.0.5...v1.1.0
147
159
  [1.0.5]: https://github.com/orgsync/active_interaction/compare/v1.0.4...v1.0.5
148
160
  [1.0.4]: https://github.com/orgsync/active_interaction/compare/v1.0.3...v1.0.4
149
161
  [1.0.3]: https://github.com/orgsync/active_interaction/compare/v1.0.2...v1.0.3
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.0'
28
+ gem 'active_interaction', '~> 1.1'
29
29
  ```
30
30
 
31
31
  And then execute:
@@ -168,8 +168,8 @@ end
168
168
  boolean :smoking
169
169
  boolean :view
170
170
  end
171
- date :arrives_on, default: Date.today
172
- date :departs_on, default: Date.tomorrow
171
+ date :arrives_on, default: -> { Date.today }
172
+ date :departs_on, default: -> { Date.tomorrow }
173
173
  ```
174
174
 
175
175
  3. Use any additional validations you need:
@@ -284,8 +284,8 @@ work done in [Mutations][17].
284
284
  [2]: https://badge.fury.io/rb/active_interaction "Gem Version"
285
285
  [3]: https://travis-ci.org/orgsync/active_interaction.png?branch=master
286
286
  [4]: https://travis-ci.org/orgsync/active_interaction "Build Status"
287
- [5]: https://coveralls.io/repos/orgsync/active_interaction/badge.png
288
- [6]: https://coveralls.io/r/orgsync/active_interaction "Coverage Status"
287
+ [5]: https://coveralls.io/repos/orgsync/active_interaction/badge.png?branch=master
288
+ [6]: https://coveralls.io/r/orgsync/active_interaction?branch=master "Coverage Status"
289
289
  [7]: https://codeclimate.com/github/orgsync/active_interaction.png
290
290
  [8]: https://codeclimate.com/github/orgsync/active_interaction "Code Climate"
291
291
  [9]: https://gemnasium.com/orgsync/active_interaction.png
@@ -41,5 +41,5 @@ I18n.load_path << File.expand_path(
41
41
  #
42
42
  # @since 1.0.0
43
43
  #
44
- # @version 1.0.5
44
+ # @version 1.1.0
45
45
  module ActiveInteraction end
@@ -120,20 +120,14 @@ module ActiveInteraction
120
120
  #
121
121
  # @return (see .filters)
122
122
  #
123
- # @raise [ArgumentError] If both `:only` and `:except` are given.
124
- #
125
123
  # @!visibility public
126
124
  def import_filters(klass, options = {})
127
- if options.key?(:only) && options.key?(:except)
128
- fail ArgumentError, 'given both :only and :except'
129
- end
130
-
131
125
  only = options[:only]
132
126
  except = options[:except]
133
127
 
134
128
  other_filters = klass.filters.dup
135
- other_filters.select! { |k, _| only.include?(k) } if only
136
- other_filters.reject! { |k, _| except.include?(k) } if except
129
+ other_filters.select! { |k, _| [*only].include?(k) } if only
130
+ other_filters.reject! { |k, _| [*except].include?(k) } if except
137
131
 
138
132
  other_filters.values.each { |filter| initialize_filter(filter) }
139
133
  end
@@ -206,7 +200,7 @@ module ActiveInteraction
206
200
  inputs.each do |key, value|
207
201
  fail InvalidValueError, key.inspect if self.class.send(:reserved?, key)
208
202
 
209
- instance_variable_set("@#{key}", value)
203
+ instance_variable_set("@#{key}", value) if respond_to?(key)
210
204
  end
211
205
 
212
206
  self.class.filters.each do |name, filter|
@@ -31,6 +31,10 @@ module ActiveInteraction
31
31
  extend ActiveSupport::Concern
32
32
  include ActiveModel::Validations
33
33
 
34
+ included do
35
+ define_callbacks :execute
36
+ end
37
+
34
38
  # @return [Errors]
35
39
  def errors
36
40
  @_interaction_errors
@@ -96,7 +100,7 @@ module ActiveInteraction
96
100
 
97
101
  self.result = ActiveRecord::Base.transaction do
98
102
  begin
99
- execute
103
+ run_callbacks(:execute) { execute }
100
104
  rescue Interrupt => interrupt
101
105
  merge_errors_onto_base(interrupt.outcome.errors)
102
106
 
@@ -49,9 +49,7 @@ module ActiveInteraction
49
49
  #
50
50
  # @see .slug
51
51
  def factory(slug)
52
- CLASSES.fetch(slug)
53
- rescue KeyError
54
- raise MissingFilterError, slug.inspect
52
+ CLASSES.fetch(slug) { fail MissingFilterError, slug.inspect }
55
53
  end
56
54
 
57
55
  # Convert the class name into a short symbol.
@@ -139,16 +137,17 @@ module ActiveInteraction
139
137
  # ActiveInteraction::Filter.new(:example, default: 0).default
140
138
  # # => ActiveInteraction::InvalidDefaultError: example: 0
141
139
  #
142
- # @return [Object]
140
+ # @return (see #raw_default)
143
141
  #
144
142
  # @raise [NoDefaultError] If the default is missing.
145
143
  # @raise [InvalidDefaultError] If the default is invalid.
146
144
  def default
147
145
  fail NoDefaultError, name unless default?
148
146
 
149
- cast(options[:default])
147
+ value = raw_default
148
+ cast(value)
150
149
  rescue InvalidValueError, MissingValueError
151
- raise InvalidDefaultError, "#{name}: #{options[:default].inspect}"
150
+ raise InvalidDefaultError, "#{name}: #{value.inspect}"
152
151
  end
153
152
 
154
153
  # Get the description.
@@ -195,5 +194,18 @@ module ActiveInteraction
195
194
  fail InvalidValueError, "#{name}: #{value.inspect}"
196
195
  end
197
196
  end
197
+
198
+ private
199
+
200
+ # @return [Object]
201
+ def raw_default
202
+ value = options.fetch(:default)
203
+
204
+ if value.is_a?(Proc)
205
+ value.call
206
+ else
207
+ value
208
+ end
209
+ end
198
210
  end
199
211
  end
@@ -8,11 +8,14 @@ module ActiveInteraction
8
8
  #
9
9
  # @private
10
10
  class AbstractFilter < Filter
11
- private
12
-
13
11
  # @return [Class]
14
- def klass
15
- self.class.slug.to_s.camelize.constantize
12
+ attr_reader :klass
13
+ private :klass
14
+
15
+ def initialize(*)
16
+ super
17
+
18
+ @klass = self.class.slug.to_s.camelize.constantize
16
19
  end
17
20
  end
18
21
  end
@@ -35,14 +35,6 @@ module ActiveInteraction
35
35
  end
36
36
  end
37
37
 
38
- def default
39
- if options[:default].is_a?(Hash) && !options[:default].empty?
40
- fail InvalidDefaultError, "#{name}: #{options[:default].inspect}"
41
- end
42
-
43
- super
44
- end
45
-
46
38
  def method_missing(*args, &block)
47
39
  super(*args) do |klass, names, options|
48
40
  fail InvalidFilterError, 'missing attribute name' if names.empty?
@@ -55,6 +47,16 @@ module ActiveInteraction
55
47
 
56
48
  private
57
49
 
50
+ def raw_default
51
+ value = super
52
+
53
+ if value.is_a?(Hash) && !value.empty?
54
+ fail InvalidDefaultError, "#{name}: #{value.inspect}"
55
+ end
56
+
57
+ value
58
+ end
59
+
58
60
  # @return [Boolean]
59
61
  def strip?
60
62
  options.fetch(:strip, true)
@@ -18,12 +18,21 @@ module ActiveInteraction
18
18
 
19
19
  # @private
20
20
  class ModelFilter < Filter
21
- def cast(value)
21
+ def initialize(*)
22
+ super
23
+
24
+ @klass = klass
25
+ end
26
+
27
+ def cast(value, reconstantize = true)
22
28
  case value
23
- when klass
29
+ when @klass
24
30
  value
25
31
  else
26
- super
32
+ return super(value) unless reconstantize
33
+
34
+ @klass = klass
35
+ cast(value, false)
27
36
  end
28
37
  end
29
38
 
@@ -5,5 +5,5 @@ module ActiveInteraction
5
5
  # The version number.
6
6
  #
7
7
  # @return [Gem::Version]
8
- VERSION = Gem::Version.new('1.0.5')
8
+ VERSION = Gem::Version.new('1.1.0')
9
9
  end
@@ -54,7 +54,7 @@ describe ActiveInteraction::Base do
54
54
  end
55
55
  end
56
56
 
57
- context 'with an attribute' do
57
+ context 'with a reader' do
58
58
  let(:described_class) do
59
59
  Class.new(TestInteraction) do
60
60
  attr_reader :thing
@@ -64,6 +64,12 @@ describe ActiveInteraction::Base do
64
64
  end
65
65
  let(:thing) { SecureRandom.hex }
66
66
 
67
+ before { inputs.merge!(thing: thing) }
68
+
69
+ it 'sets the attribute' do
70
+ expect(interaction.thing).to eq thing
71
+ end
72
+
67
73
  context 'failing validations' do
68
74
  before { inputs.merge!(thing: nil) }
69
75
 
@@ -73,15 +79,9 @@ describe ActiveInteraction::Base do
73
79
  end
74
80
 
75
81
  context 'passing validations' do
76
- before { inputs.merge!(thing: thing) }
77
-
78
82
  it 'returns a valid outcome' do
79
83
  expect(interaction).to be_valid
80
84
  end
81
-
82
- it 'sets the attribute' do
83
- expect(interaction.thing).to eq thing
84
- end
85
85
  end
86
86
  end
87
87
 
@@ -391,8 +391,8 @@ describe ActiveInteraction::Base do
391
391
 
392
392
  it 'imports the filters' do
393
393
  expect(described_class.filters).to eq klass.filters
394
- .select { |k, _| only.nil? ? true : only.include?(k) }
395
- .reject { |k, _| except.nil? ? false : except.include?(k) }
394
+ .select { |k, _| only.nil? ? true : [*only].include?(k) }
395
+ .reject { |k, _| except.nil? ? false : [*except].include?(k) }
396
396
  end
397
397
 
398
398
  it 'does not modify the source' do
@@ -417,26 +417,38 @@ describe ActiveInteraction::Base do
417
417
  end
418
418
 
419
419
  context 'with :only' do
420
- include_examples 'import_filters examples'
420
+ context 'as an Array' do
421
+ include_examples 'import_filters examples'
421
422
 
422
- let(:only) { [:x] }
423
+ let(:only) { [:x] }
424
+ end
425
+
426
+ context 'as an Symbol' do
427
+ include_examples 'import_filters examples'
428
+
429
+ let(:only) { :x }
430
+ end
423
431
  end
424
432
 
425
433
  context 'with :except' do
426
- include_examples 'import_filters examples'
434
+ context 'as an Array' do
435
+ include_examples 'import_filters examples'
427
436
 
428
- let(:except) { [:x] }
437
+ let(:except) { [:x] }
438
+ end
439
+
440
+ context 'as an Symbol' do
441
+ include_examples 'import_filters examples'
442
+
443
+ let(:except) { :x }
444
+ end
429
445
  end
430
446
 
431
447
  context 'with :only & :except' do
432
- include_context 'import_filters context'
433
-
434
- let(:only) { [] }
435
- let(:except) { [] }
448
+ include_examples 'import_filters examples'
436
449
 
437
- it 'raises an error' do
438
- expect { described_class }.to raise_error ArgumentError
439
- end
450
+ let(:only) { [:x] }
451
+ let(:except) { [:x] }
440
452
  end
441
453
  end
442
454
  end
@@ -61,6 +61,39 @@ describe ActiveInteraction::Runnable do
61
61
  end
62
62
  end
63
63
 
64
+ context 'callbacks' do
65
+ describe '.set_callback' do
66
+ include_context 'with #execute defined'
67
+
68
+ shared_examples 'set_callback examples' do |name|
69
+ context name do
70
+ it 'does not raise an error' do
71
+ expect do
72
+ klass.set_callback name, :before, -> _ {}
73
+ end.to_not raise_error
74
+ end
75
+
76
+ [:after, :around, :before].each do |type|
77
+ it type do
78
+ has_run = false
79
+
80
+ klass.set_callback name, type, lambda { |_, &block|
81
+ has_run = true
82
+ block.call unless block.nil?
83
+ }
84
+
85
+ klass.run
86
+ expect(has_run).to be_true
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ include_examples 'set_callback examples', :validate
93
+ include_examples 'set_callback examples', :execute
94
+ end
95
+ end
96
+
64
97
  describe '#errors' do
65
98
  it 'returns the errors' do
66
99
  expect(instance.errors).to be_an ActiveInteraction::Errors
@@ -29,6 +29,29 @@ describe ActiveInteraction::ModelFilter, :filter do
29
29
 
30
30
  expect(filter.cast(value)).to eq value
31
31
  end
32
+
33
+ it 'handles reconstantizing subclasses' do
34
+ filter
35
+
36
+ Object.send(:remove_const, :Model)
37
+ Model = Class.new
38
+ Submodel = Class.new(Model)
39
+ value = Submodel.new
40
+
41
+ expect(filter.cast(value)).to eq value
42
+ end
43
+
44
+ it 'does not overflow the stack' do
45
+ klass = Class.new do
46
+ def self.name
47
+ Model.name
48
+ end
49
+ end
50
+
51
+ expect do
52
+ filter.cast(klass.new)
53
+ end.to raise_error ActiveInteraction::InvalidValueError
54
+ end
32
55
  end
33
56
 
34
57
  context 'with class as a superclass' do
@@ -3,5 +3,5 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe 'ModelInteraction' do
6
- it_behaves_like 'an interaction', :model, -> { proc {} }, class: Proc
6
+ it_behaves_like 'an interaction', :model, -> { Object }, class: Class
7
7
  end
@@ -153,6 +153,19 @@ shared_examples_for 'a filter' do
153
153
  end.to raise_error ActiveInteraction::InvalidDefaultError
154
154
  end
155
155
  end
156
+
157
+ context 'with a callable default' do
158
+ include_context 'optional'
159
+
160
+ before do
161
+ default = options[:default]
162
+ options[:default] = -> { default }
163
+ end
164
+
165
+ it 'returns the default' do
166
+ expect(filter.default).to eq options[:default].call
167
+ end
168
+ end
156
169
  end
157
170
 
158
171
  describe '#desc' do
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: 1.0.5
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Lasseigne
@@ -9,124 +9,124 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-02-25 00:00:00.000000000 Z
12
+ date: 2014-02-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activemodel
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - '>='
18
+ - - ">="
19
19
  - !ruby/object:Gem::Version
20
20
  version: '3'
21
- - - <
21
+ - - "<"
22
22
  - !ruby/object:Gem::Version
23
23
  version: '5'
24
24
  type: :runtime
25
25
  prerelease: false
26
26
  version_requirements: !ruby/object:Gem::Requirement
27
27
  requirements:
28
- - - '>='
28
+ - - ">="
29
29
  - !ruby/object:Gem::Version
30
30
  version: '3'
31
- - - <
31
+ - - "<"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '5'
34
34
  - !ruby/object:Gem::Dependency
35
35
  name: bundler
36
36
  requirement: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.5'
41
41
  type: :development
42
42
  prerelease: false
43
43
  version_requirements: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ~>
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: '1.5'
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: coveralls
50
50
  requirement: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ~>
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0.7'
55
55
  type: :development
56
56
  prerelease: false
57
57
  version_requirements: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ~>
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0.7'
62
62
  - !ruby/object:Gem::Dependency
63
63
  name: guard-rspec
64
64
  requirement: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ~>
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '4.2'
69
69
  type: :development
70
70
  prerelease: false
71
71
  version_requirements: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ~>
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
75
  version: '4.2'
76
76
  - !ruby/object:Gem::Dependency
77
77
  name: rake
78
78
  requirement: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ~>
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '10.1'
83
83
  type: :development
84
84
  prerelease: false
85
85
  version_requirements: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ~>
87
+ - - "~>"
88
88
  - !ruby/object:Gem::Version
89
89
  version: '10.1'
90
90
  - !ruby/object:Gem::Dependency
91
91
  name: rdoc
92
92
  requirement: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ~>
94
+ - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '4.1'
97
97
  type: :development
98
98
  prerelease: false
99
99
  version_requirements: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - ~>
101
+ - - "~>"
102
102
  - !ruby/object:Gem::Version
103
103
  version: '4.1'
104
104
  - !ruby/object:Gem::Dependency
105
105
  name: guard-rubocop
106
106
  requirement: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - ~>
108
+ - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '1.0'
111
111
  type: :development
112
112
  prerelease: false
113
113
  version_requirements: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - ~>
115
+ - - "~>"
116
116
  - !ruby/object:Gem::Version
117
117
  version: '1.0'
118
118
  - !ruby/object:Gem::Dependency
119
119
  name: yard
120
120
  requirement: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - ~>
122
+ - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0.8'
125
125
  type: :development
126
126
  prerelease: false
127
127
  version_requirements: !ruby/object:Gem::Requirement
128
128
  requirements:
129
- - - ~>
129
+ - - "~>"
130
130
  - !ruby/object:Gem::Version
131
131
  version: '0.8'
132
132
  description: Manage application specific business logic.
@@ -137,10 +137,6 @@ executables: []
137
137
  extensions: []
138
138
  extra_rdoc_files: []
139
139
  files:
140
- - CHANGELOG.md
141
- - LICENSE.txt
142
- - README.md
143
- - lib/active_interaction.rb
144
140
  - lib/active_interaction/base.rb
145
141
  - lib/active_interaction/concerns/active_modelable.rb
146
142
  - lib/active_interaction/concerns/hashable.rb
@@ -163,9 +159,9 @@ files:
163
159
  - lib/active_interaction/filters/string_filter.rb
164
160
  - lib/active_interaction/filters/symbol_filter.rb
165
161
  - lib/active_interaction/filters/time_filter.rb
166
- - lib/active_interaction/locale/en.yml
167
162
  - lib/active_interaction/modules/validation.rb
168
163
  - lib/active_interaction/version.rb
164
+ - lib/active_interaction.rb
169
165
  - spec/active_interaction/base_spec.rb
170
166
  - spec/active_interaction/concerns/active_modelable_spec.rb
171
167
  - spec/active_interaction/concerns/hashable_spec.rb
@@ -206,6 +202,10 @@ files:
206
202
  - spec/support/concerns.rb
207
203
  - spec/support/filters.rb
208
204
  - spec/support/interactions.rb
205
+ - CHANGELOG.md
206
+ - LICENSE.txt
207
+ - README.md
208
+ - lib/active_interaction/locale/en.yml
209
209
  homepage: http://orgsync.github.io/active_interaction/
210
210
  licenses:
211
211
  - MIT
@@ -216,17 +216,17 @@ require_paths:
216
216
  - lib
217
217
  required_ruby_version: !ruby/object:Gem::Requirement
218
218
  requirements:
219
- - - '>='
219
+ - - ">="
220
220
  - !ruby/object:Gem::Version
221
221
  version: 1.9.3
222
222
  required_rubygems_version: !ruby/object:Gem::Requirement
223
223
  requirements:
224
- - - '>='
224
+ - - ">="
225
225
  - !ruby/object:Gem::Version
226
226
  version: '0'
227
227
  requirements: []
228
228
  rubyforge_project:
229
- rubygems_version: 2.2.0
229
+ rubygems_version: 2.1.11
230
230
  signing_key:
231
231
  specification_version: 4
232
232
  summary: Manage application specific business logic.