action_operation 1.1.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +86 -88
- data/lib/action_operation/error/missing_error.rb +3 -4
- data/lib/action_operation/error/missing_schema.rb +13 -0
- data/lib/action_operation/error/missing_task.rb +3 -3
- data/lib/action_operation/error.rb +1 -1
- data/lib/action_operation/version.rb +1 -1
- data/lib/action_operation.rb +35 -52
- metadata +3 -3
- data/lib/action_operation/error/missing_schema_for_task.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f97e8fc036a03a7ff4b0fdab9e4316d2312ad17ae5cf1a1844dc1ceb7cf84ad1
|
4
|
+
data.tar.gz: e2e2b98a9c87a50b42acbe2b2d1cfa507a4972d2871f6e26ad5a938d5a28c464
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b0d55912ff981d7298d3c9ca0c24babeea983427af130714e808d8767913cfde985b9a5fd5cb611d2f87c9c0986f133daa6357dd5645d460d740ea4f29e44e42
|
7
|
+
data.tar.gz: 90dca03f785f51506abb76bdcda2a2448e1236de32957ab5e9592e2252c3f63f125eb83c7e4e17477619d534a06fcbeb07788e266182b1e2abadf32bd89a1eb1
|
data/README.md
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
- [![Version](http://img.shields.io/gem/v/action_operation.svg?style=flat-square)](https://rubygems.org/gems/action_operation)
|
6
6
|
|
7
7
|
|
8
|
-
A simple set of right-to-left operations, similar to many other gems out there.
|
8
|
+
A simple set of right-to-left operations, similar to many other gems out there like [trailblazer operations](http://trailblazer.to/gems/operation/2.0/index.html).
|
9
9
|
|
10
10
|
|
11
11
|
## Using
|
@@ -25,50 +25,55 @@ class AddToCartOperation
|
|
25
25
|
task :lock
|
26
26
|
task :persist
|
27
27
|
task :publish
|
28
|
-
|
29
|
-
|
28
|
+
catch :notify, exception: ProductMissingFromCartItemError
|
29
|
+
catch :reraise
|
30
30
|
|
31
|
-
|
31
|
+
schema :check_for_missing_product do
|
32
32
|
field :cart_item, type: Types.Instance(CartItem)
|
33
33
|
end
|
34
|
-
|
34
|
+
def check_for_missing_product(state:)
|
35
35
|
raise ProductMissingFromCartItemError if state.cart_item.product.nil?
|
36
36
|
end
|
37
37
|
|
38
|
-
|
38
|
+
schema :carbon_copy_cart_item do
|
39
39
|
field :cart_item, type: Types.Instance(CartItem)
|
40
40
|
end
|
41
|
-
|
41
|
+
def carbon_copy_cart_item(state:)
|
42
42
|
state.cart_item.carbon_copy
|
43
43
|
end
|
44
44
|
|
45
|
-
|
45
|
+
schema :lock do
|
46
46
|
field :cart_item, type: Types.Instance(CartItem)
|
47
47
|
end
|
48
|
-
|
49
|
-
GlobalLock.(
|
48
|
+
def lock(state:)
|
49
|
+
GlobalLock.(resource: state.cart_item, expires_in: 15.minutes)
|
50
50
|
end
|
51
51
|
|
52
|
-
|
52
|
+
schema :persist do
|
53
53
|
field :cart_item, type: Types.Instance(CartItem)
|
54
54
|
end
|
55
|
-
|
55
|
+
def persist(state:)
|
56
56
|
CartItem.transaction do
|
57
57
|
state.cart_item.save!
|
58
58
|
end
|
59
59
|
|
60
|
-
fresh(current_account: state.cart_item.owner, cart_item: state.cart_item)
|
60
|
+
fresh(state: {current_account: state.cart_item.owner, cart_item: state.cart_item})
|
61
61
|
end
|
62
62
|
|
63
|
-
|
63
|
+
schema :publish do
|
64
64
|
field :cart_item, type: Types.Instance(CartItem)
|
65
65
|
field :current_account, type: Types.Instance(Account)
|
66
66
|
end
|
67
|
-
|
68
|
-
CartItemPickedMessage.(
|
67
|
+
def publish(state:)
|
68
|
+
CartItemPickedMessage.(
|
69
|
+
to: state.current_account,
|
70
|
+
subject: state.cart_item,
|
71
|
+
via: :pubsub,
|
72
|
+
deliver: :later
|
73
|
+
)
|
69
74
|
end
|
70
75
|
|
71
|
-
|
76
|
+
def notify(exception:, **)
|
72
77
|
Bugsnag.notify(exception)
|
73
78
|
end
|
74
79
|
end
|
@@ -85,16 +90,16 @@ class AddToCartOperation
|
|
85
90
|
task :lock
|
86
91
|
task :persist
|
87
92
|
task :publish
|
88
|
-
|
89
|
-
|
93
|
+
catch :notify, exception: ProductMissingFromCartItemError
|
94
|
+
catch :reraise
|
90
95
|
|
91
96
|
# ...
|
92
97
|
end
|
93
98
|
```
|
94
99
|
|
95
|
-
These are the steps our process will take. Each `task` call is *in the order it is listed*, which means that `check_for_missing_product` will happen before `carbon_copy_cart_item`. Each `
|
100
|
+
These are the steps our process will take. Each `task` call is *in the order it is listed*, which means that `check_for_missing_product` will happen before `carbon_copy_cart_item`. Each `catch` is also *in the order it is listed*, but they only trigger when one of the `task` raises an exception. In this case, we only want to `notify` when there's something seriously wrong!
|
96
101
|
|
97
|
-
Finally, before we leave
|
102
|
+
Finally, before we leave notice the `reraise` error step. This is built in to the operation layer so that you can easily pass the buck to whomever owns the action currently.
|
98
103
|
|
99
104
|
Okay, so on to our first step:
|
100
105
|
|
@@ -102,10 +107,10 @@ Okay, so on to our first step:
|
|
102
107
|
class AddToCartOperation
|
103
108
|
# ...
|
104
109
|
|
105
|
-
|
110
|
+
schema :check_for_missing_product do
|
106
111
|
field :cart_item, type: Types.Instance(CartItem)
|
107
112
|
end
|
108
|
-
|
113
|
+
def check_for_missing_product(state:)
|
109
114
|
raise ProductMissingFromCartItemError if state.cart_item.product.nil?
|
110
115
|
end
|
111
116
|
|
@@ -113,66 +118,40 @@ class AddToCartOperation
|
|
113
118
|
end
|
114
119
|
```
|
115
120
|
|
116
|
-
There's two things we want to talk about there and the first is `
|
117
|
-
|
118
|
-
Second is the `step` definition itself which provides a `state` object that is based on the schema defined above. You have four choices on what you can do in a `step`. You can:
|
121
|
+
There's two things we want to talk about there and the first is `schema`. It defines the shape of the *immutable* state that the step will receive. We use [smart_params](https://github.com/krainboltgreene/smart_params.rb) which means each field is typed with [dry-types](https://github.com/dry-rb/dry-types). Read up on both of those libraries for more fine grained control over your data. Second is the step definition itself which provides a `state` object that is based on the schema by the same name. You have four choices on what you can do in a step. You can:
|
119
122
|
|
120
123
|
- Return any value, which will simply proceed to the next step.
|
121
|
-
- Raise an exception, which will move you into the left track (that uses `
|
124
|
+
- Raise an exception, which will move you into the left track (that uses `catch` steps)
|
122
125
|
- Return a fresh state, which will be described below
|
123
126
|
- Return a drift instruction, which will be described below
|
124
127
|
|
125
128
|
### Fresh State
|
126
129
|
|
127
|
-
Sometimes you want to
|
130
|
+
Sometimes you want to pass different state to all steps after. We provide the `fresh()` function for this very purpose:
|
128
131
|
|
129
132
|
``` ruby
|
130
133
|
class AddToCartOperation
|
131
134
|
# ...
|
132
135
|
|
133
|
-
|
136
|
+
schema :persist do
|
137
|
+
field :cart_item, type: Types.Instance(CartItem)
|
138
|
+
end
|
139
|
+
def persist(state:)
|
134
140
|
CartItem.transaction do
|
135
141
|
state.cart_item.save!
|
136
142
|
end
|
137
143
|
|
138
|
-
fresh(current_account: state.cart_item.owner, cart_item: state.cart_item)
|
144
|
+
fresh(state: {current_account: state.cart_item.owner, cart_item: state.cart_item})
|
139
145
|
end
|
140
146
|
|
141
147
|
# ...
|
142
148
|
end
|
143
149
|
```
|
144
150
|
|
145
|
-
This is the only way to "change" the shape of the state.
|
146
|
-
|
147
|
-
|
148
|
-
### Receivers
|
149
|
-
|
150
|
-
Sometimes you need to share functionality across multiple operations. You can do this via modules and inheritance like normal or you can use our specialized interface:
|
151
|
-
|
152
|
-
``` ruby
|
153
|
-
class DocumentUploadOperation
|
154
|
-
include ActionOperation
|
155
|
-
|
156
|
-
task :upload_to_s3, receiver: S3UploadOperation
|
157
|
-
end
|
158
|
-
```
|
159
|
-
|
160
|
-
This will give the `DocumentUploadOperation` a task that is on another operation! Sometimes that other task has a different name, so we also provide aliasing:
|
161
|
-
|
162
|
-
``` ruby
|
163
|
-
class DocumentUploadOperation
|
164
|
-
include ActionOperation
|
165
|
-
|
166
|
-
task :upload_to_s3, receiver: S3UploadOperation, :upload
|
167
|
-
end
|
168
|
-
```
|
169
|
-
|
170
|
-
So when `DocumentUploadOperation` finally gets to the `upload_to_s3` task it's actually calling the `S3UploadOperation` task called `upload`. More on why this is useful in the next section.
|
171
|
-
|
172
151
|
|
173
152
|
### Drifting
|
174
153
|
|
175
|
-
Alright, so lets say you have a business requirement to upload important documents to the cloud. You have multiple providers (S3, Azure, and DigitalOcean Spaces) and you want to make sure it gets pushed to at least one.
|
154
|
+
Alright, so lets say you have a business requirement to upload important documents to the cloud. You have multiple providers (S3, Azure, and DigitalOcean Spaces) and you want to make sure it gets pushed to at least one. First we define how to talk to S3:
|
176
155
|
|
177
156
|
``` ruby
|
178
157
|
class S3UploadOperation
|
@@ -180,67 +159,86 @@ class S3UploadOperation
|
|
180
159
|
|
181
160
|
task :upload
|
182
161
|
|
183
|
-
|
162
|
+
schema :upload do
|
184
163
|
field :document, type: Types.Instance(Document)
|
185
164
|
end
|
186
|
-
|
187
|
-
fresh(document: state.document, location: S3.push(state.document))
|
165
|
+
def upload(state:)
|
166
|
+
fresh(state: {document: state.document, location: S3.push(state.document)})
|
188
167
|
rescue StandardError => exception
|
189
168
|
raise FailedUploadError
|
190
169
|
end
|
191
170
|
end
|
192
|
-
|
193
171
|
```
|
194
172
|
|
195
|
-
|
196
|
-
class DocumentUploadOperation
|
197
|
-
include ActionOperation
|
173
|
+
Now we define the controlling operation:
|
198
174
|
|
199
|
-
|
200
|
-
|
201
|
-
task :
|
175
|
+
``` ruby
|
176
|
+
class DocumentUploadOperation < ApplicationOperation
|
177
|
+
task :upload_to_s3
|
178
|
+
task :upload_to_azure, required: false
|
179
|
+
task :upload_to_spaces, required: false
|
202
180
|
task :publish
|
203
|
-
|
204
|
-
|
181
|
+
catch :retry, exception: FailedUploadError
|
182
|
+
catch :reraise
|
205
183
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
184
|
+
schema :upload_to_s3 do
|
185
|
+
field :document, type: Types.Instance(Document)
|
186
|
+
end
|
187
|
+
def upload_to_s3(state:)
|
188
|
+
fresh(state: S3UploadOperation.(document: state.document))
|
211
189
|
end
|
212
190
|
|
213
|
-
|
191
|
+
schema :upload_to_azure do
|
192
|
+
field :document, type: Types.Instance(Document)
|
193
|
+
end
|
194
|
+
def upload_to_azure(state:)
|
195
|
+
fresh(state: AzureUploadOperation.(document: state.document))
|
196
|
+
end
|
197
|
+
|
198
|
+
schema :upload_to_spaces do
|
199
|
+
field :document, type: Types.Instance(Document)
|
200
|
+
end
|
201
|
+
def upload_to_spaces(state:)
|
202
|
+
fresh(state: SpacesUploadOperation.(document: state.document))
|
203
|
+
end
|
204
|
+
|
205
|
+
schema :publish do
|
214
206
|
field :document, type: Types.Instance(Document)
|
215
207
|
field :location, type: Types::Strict::String
|
216
208
|
end
|
217
|
-
|
218
|
-
DocumentSuccessfullyUploadedMessage.(
|
209
|
+
def publish(state:)
|
210
|
+
DocumentSuccessfullyUploadedMessage.(
|
211
|
+
to: state.document.owner,
|
212
|
+
subject: state.location,
|
213
|
+
via: :pubsub,
|
214
|
+
deliver: :later
|
215
|
+
)
|
216
|
+
end
|
217
|
+
|
218
|
+
def retry(exception:, step:, **)
|
219
|
+
case step.name
|
220
|
+
when :upload_to_s3 then drift(to: :upload_to_azure)
|
221
|
+
when :upload_to_azure then drift(to: :upload_to_spaces)
|
222
|
+
end
|
219
223
|
end
|
220
224
|
end
|
221
225
|
```
|
222
226
|
|
223
227
|
So here's how this works:
|
224
228
|
|
225
|
-
1. First we call `upload_to_s3
|
229
|
+
1. First we call `upload_to_s3` which talks to `S3UploadOperation`, but for some reason this fails which bubbles up a specific exception that we catch with `DocumentUploadOperation#retry`
|
226
230
|
2. `retry` looks at the last known step and then drifts to `upload_to_azure`, which functions just like above.
|
227
231
|
3. Then somehow we fail to upload to Azure, so we repeat and retry with DigitalOcean Spaces.
|
228
|
-
4. We fail to even upload that, which means the next
|
229
|
-
|
230
|
-
|
231
|
-
However, if it finishes successfully we get to push a notification to the document owner in `finish`.
|
232
|
-
|
233
|
-
|
234
|
-
### Understanding the design
|
232
|
+
4. We fail to even upload that, which means the next catch step gets called (`reraise()`) giving control back to the owner of the operation
|
235
233
|
|
236
|
-
|
234
|
+
However, if it finishes successfully we get to push a notification to the document owner in `publish()`.
|
237
235
|
|
238
236
|
|
239
237
|
## Installing
|
240
238
|
|
241
239
|
Add this line to your application's Gemfile:
|
242
240
|
|
243
|
-
gem "action_operation", "
|
241
|
+
gem "action_operation", "2.0.0"
|
244
242
|
|
245
243
|
And then execute:
|
246
244
|
|
@@ -1,13 +1,12 @@
|
|
1
1
|
module ActionOperation
|
2
2
|
class Error
|
3
3
|
class MissingError < Error
|
4
|
-
def initialize(
|
5
|
-
@
|
4
|
+
def initialize(step)
|
5
|
+
@step = step
|
6
6
|
end
|
7
7
|
|
8
8
|
def message
|
9
|
-
|
10
|
-
"expected to see #{@function.name} but the receiver (#{@function.receiver.name}) didn't support it"
|
9
|
+
"expected to see #{@step.name} but the receiver (#{@step.receiver.name}) didn't support it"
|
11
10
|
end
|
12
11
|
end
|
13
12
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ActionOperation
|
2
|
+
class Error
|
3
|
+
class MissingSchema < Error
|
4
|
+
def initialize(step)
|
5
|
+
@step = step
|
6
|
+
end
|
7
|
+
|
8
|
+
def message
|
9
|
+
"expected to see #{@step.name} have a schema but the receiver (#{@step.receiver.name}) didn't support it"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
module ActionOperation
|
2
2
|
class Error
|
3
3
|
class MissingTask < Error
|
4
|
-
def initialize(
|
5
|
-
@
|
4
|
+
def initialize(step)
|
5
|
+
@step = step
|
6
6
|
end
|
7
7
|
|
8
8
|
def message
|
9
|
-
"expected to see #{
|
9
|
+
"expected to see #{@step.name} but the receiver (#{@step.receiver.name}) didn't support it"
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
data/lib/action_operation.rb
CHANGED
@@ -6,62 +6,63 @@ module ActionOperation
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
8
|
require_relative "action_operation/version"
|
9
|
-
require_relative "action_operation/types"
|
10
9
|
require_relative "action_operation/error"
|
10
|
+
require_relative "action_operation/types"
|
11
11
|
|
12
12
|
State = Struct.new(:raw)
|
13
13
|
Drift = Struct.new(:to)
|
14
|
-
|
15
|
-
|
16
|
-
attr_reader :state
|
17
|
-
attr_reader :step
|
14
|
+
Task = Struct.new(:name, :required)
|
15
|
+
Catch = Struct.new(:name, :exception)
|
18
16
|
|
19
17
|
def initialize(raw:)
|
18
|
+
raise ArgumentError, "needs to be a Hash" unless raw.kind_of?(Hash)
|
19
|
+
|
20
20
|
@raw = raw
|
21
21
|
end
|
22
22
|
|
23
|
-
def call(
|
24
|
-
right.from(
|
25
|
-
next state unless
|
26
|
-
|
27
|
-
# NOTE: We store this so we can go drift back if an error tells us to
|
28
|
-
@state = state
|
23
|
+
def call(start: nil, raw: @raw)
|
24
|
+
right.from(start || 0).reduce(raw) do |state, step|
|
25
|
+
next state unless step.required || (start && right.at(start) == step)
|
29
26
|
|
30
|
-
|
31
|
-
|
27
|
+
raise Error::MissingTask, step unless respond_to?(step.name)
|
28
|
+
raise Error::MissingSchema, step unless self.class.schemas.key?(step.name)
|
32
29
|
|
33
|
-
|
34
|
-
|
30
|
+
# NOTE: We only care about this so we can refernece it in the rescue
|
31
|
+
@latest_step = step
|
35
32
|
|
36
|
-
value =
|
33
|
+
value = public_send(step.name, state: self.class.schemas.fetch(step.name).new(state))
|
37
34
|
|
38
35
|
case value
|
39
36
|
when State then value.raw
|
40
|
-
when Drift then break call(
|
37
|
+
when Drift then break call(start: right.find_index { |step| step.name == value.to }, raw: raw)
|
41
38
|
else state
|
42
39
|
end
|
43
40
|
end
|
44
|
-
rescue *left.select(&:
|
41
|
+
rescue *left.select(&:exception).map(&:exception).uniq => handled_exception
|
45
42
|
left.select do |failure|
|
46
|
-
failure.
|
47
|
-
end.reduce(handled_exception) do |exception,
|
48
|
-
raise Error::MissingError,
|
43
|
+
failure.exception === handled_exception
|
44
|
+
end.reduce(handled_exception) do |exception, step|
|
45
|
+
raise Error::MissingError, step unless respond_to?(step.name)
|
49
46
|
|
50
|
-
value =
|
47
|
+
value = public_send(step.name, exception: exception, state: self.class.schemas.fetch(@latest_step.name).new(raw), step: @latest_step)
|
51
48
|
|
52
49
|
if value.kind_of?(Drift)
|
53
|
-
break call(
|
50
|
+
break call(start: right.find_index { |step| step.name == value.to }, raw: raw)
|
54
51
|
else
|
55
52
|
exception
|
56
53
|
end
|
57
54
|
end
|
58
55
|
end
|
59
56
|
|
60
|
-
def fresh(
|
61
|
-
|
57
|
+
def fresh(state:)
|
58
|
+
raise ArgumentError, "needs to be a Hash" unless state.kind_of?(Hash)
|
59
|
+
|
60
|
+
State.new(state)
|
62
61
|
end
|
63
62
|
|
64
63
|
def drift(to:)
|
64
|
+
raise ArgumentError, "needs to be a Symbol or String" unless to.kind_of?(Symbol) || to.kind_of?(String)
|
65
|
+
|
65
66
|
Drift.new(to)
|
66
67
|
end
|
67
68
|
|
@@ -73,22 +74,16 @@ module ActionOperation
|
|
73
74
|
self.class.right
|
74
75
|
end
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
raise exception
|
79
|
-
end
|
77
|
+
def reraise(exception:, **)
|
78
|
+
raise exception
|
80
79
|
end
|
81
80
|
|
82
81
|
class_methods do
|
83
|
-
def
|
84
|
-
|
85
|
-
step :reraise do |exception|
|
86
|
-
raise exception
|
87
|
-
end
|
88
|
-
end
|
82
|
+
def call(raw = {})
|
83
|
+
new(raw: raw).call
|
89
84
|
end
|
90
85
|
|
91
|
-
def
|
86
|
+
def schema(name, &structure)
|
92
87
|
schemas[name] = Class.new do
|
93
88
|
include(SmartParams)
|
94
89
|
|
@@ -96,20 +91,12 @@ module ActionOperation
|
|
96
91
|
end
|
97
92
|
end
|
98
93
|
|
99
|
-
def task(name,
|
100
|
-
right
|
101
|
-
end
|
102
|
-
|
103
|
-
def error(name, receiver: self, catch: StandardError, as: name)
|
104
|
-
left.<<(OpenStruct.new({name: name, as: as, receiver: receiver || self, catch: catch || StandardError}))
|
94
|
+
def task(name, required: true)
|
95
|
+
right << Task.new(name, required)
|
105
96
|
end
|
106
97
|
|
107
|
-
def
|
108
|
-
|
109
|
-
end
|
110
|
-
|
111
|
-
def call(raw = {})
|
112
|
-
new(raw: raw).call
|
98
|
+
def catch(name, exception: StandardError)
|
99
|
+
left << Catch.new(name, exception)
|
113
100
|
end
|
114
101
|
|
115
102
|
def right
|
@@ -123,9 +110,5 @@ module ActionOperation
|
|
123
110
|
def schemas
|
124
111
|
@schemas ||= Hash.new
|
125
112
|
end
|
126
|
-
|
127
|
-
def steps
|
128
|
-
@steps ||= Hash.new
|
129
|
-
end
|
130
113
|
end
|
131
114
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: action_operation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kurtis Rainbolt-Greene
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-05-
|
11
|
+
date: 2018-05-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -138,7 +138,7 @@ files:
|
|
138
138
|
- lib/action_operation.rb
|
139
139
|
- lib/action_operation/error.rb
|
140
140
|
- lib/action_operation/error/missing_error.rb
|
141
|
-
- lib/action_operation/error/
|
141
|
+
- lib/action_operation/error/missing_schema.rb
|
142
142
|
- lib/action_operation/error/missing_task.rb
|
143
143
|
- lib/action_operation/types.rb
|
144
144
|
- lib/action_operation/version.rb
|
@@ -1,13 +0,0 @@
|
|
1
|
-
module ActionOperation
|
2
|
-
class Error
|
3
|
-
class MissingSchemaForTask < Error
|
4
|
-
def initialize(function)
|
5
|
-
@function = function
|
6
|
-
end
|
7
|
-
|
8
|
-
def message
|
9
|
-
"expected to see #{function.name} have a schema but the receiver (#{function.receiver.name}) didn't support it"
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|