snfoil-context 0.0.1 → 1.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 +167 -84
- data/lib/snfoil/context/structure.rb +39 -27
- data/lib/snfoil/context/version.rb +1 -1
- data/lib/snfoil/context.rb +47 -37
- data/{snfoil-policy.gemspec → snfoil-context.gemspec} +12 -10
- metadata +43 -19
- data/.github/workflows/main.yml +0 -46
- data/.gitignore +0 -14
- data/.rspec +0 -3
- data/.rubocop.yml +0 -39
- data/Rakefile +0 -12
- data/bin/console +0 -15
- data/bin/setup +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 865d3b34caed7991d357df2f115087b8c99b84c5e1a0050e691e9bf751149647
|
4
|
+
data.tar.gz: 28c589b8f0fd88f7828c91678de7c58051d1489107ff2c96ad3c94e4c1cfba02
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7115fd9f9bbb8de53f6008fccaa4018f41fc2ce2c13066a7a00e08192d1054589d3c0a471e3be837ac9d26d62cd32088ccc5e39700d7842d954d98d24fb9b9cc
|
7
|
+
data.tar.gz: a7d880bbff1e6cb9fa1fcb39a9f865ebb171d940ec3804ae0ae05a92c97fce9007b480876dcb1b05fab188635f88d28e3e65ea2f37f39d1b8819a3f4e144aacc
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
![build](https://github.com/limited-effort/snfoil-context/actions/workflows/main.yml/badge.svg) [![maintainability](https://api.codeclimate.com/v1/badges/6a7a2f643707c17cb879/maintainability)](https://codeclimate.com/github/limited-effort/snfoil-context/maintainability)
|
4
4
|
|
5
|
-
SnFoil Contexts are a simple way to
|
5
|
+
SnFoil Contexts are a simple way to ensure a workflow pipeline can be easily established end extended. It helps by creating a workflow, allowing additional steps at specific intervals, and reacting to success or failure, you should find your code being more maintainable and testable.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -15,7 +15,47 @@ gem 'snfoil-context'
|
|
15
15
|
## Usage
|
16
16
|
While contexts are powerful, they aren't a magic bullet. Each function should strive to only contain a single purpose. This also has the added benefit of outlining some basic tests - if it is in a function it should have a related test.
|
17
17
|
|
18
|
-
|
18
|
+
|
19
|
+
### Quickstart Example
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require 'snfoil/context'
|
23
|
+
|
24
|
+
class TokenContext
|
25
|
+
include SnFoil::Context
|
26
|
+
|
27
|
+
action(:create) { |options| options[:object].save }
|
28
|
+
action(:expire) { |options| options[:object].update(expired_at: Time.current) }
|
29
|
+
|
30
|
+
# inject created_by
|
31
|
+
setup_create { |options| options[:params][:created_by] = entity }
|
32
|
+
|
33
|
+
# initialize token
|
34
|
+
before_create do |options|
|
35
|
+
options[:object] = Token.create(options[:params])
|
36
|
+
options
|
37
|
+
end
|
38
|
+
|
39
|
+
# send token email
|
40
|
+
after_create_success { |options| TokenMailer.new(token: option[:object]) }
|
41
|
+
|
42
|
+
# log expiration error
|
43
|
+
after_expire_failure { |options| ErrorLogger.notify(error: options[:object].errors) }
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
|
48
|
+
### Initialize
|
49
|
+
|
50
|
+
When you `new` up a SnFoil Context you should provide the entity running the actions. This will usually be a user but you can pass in anything. This will be accessible from within the context as `entity`.
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
TokenContext.new(current_user)
|
54
|
+
```
|
55
|
+
|
56
|
+
### Actions
|
57
|
+
Actions are a group of hookable intervals that create a workflow around a single primary function.
|
58
|
+
|
19
59
|
To start you will need to define an action.
|
20
60
|
|
21
61
|
Arguments:
|
@@ -30,18 +70,32 @@ require 'snfoil/context'
|
|
30
70
|
class TokenContext
|
31
71
|
include SnFoil::Context
|
32
72
|
|
33
|
-
|
73
|
+
...
|
74
|
+
|
75
|
+
action(:expire) { |options| options[:object].update(expired_at: Time.current) }
|
34
76
|
end
|
35
77
|
```
|
36
78
|
|
37
|
-
This will generate the
|
79
|
+
This will generate the intervals of the pipeline. In this example the following get made:
|
38
80
|
* setup_expire
|
39
81
|
* before_expire
|
40
82
|
* after_expire_success
|
41
83
|
* after_expire_failure
|
42
84
|
* after_expire
|
43
85
|
|
44
|
-
|
86
|
+
Now you can trigger the workflow by calling the action as a method on an instance of the context.
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
class TokenContext
|
90
|
+
include SnFoil::Context
|
91
|
+
|
92
|
+
action(:expire) { |options| options[:object].update(expired_at: Time.current) }
|
93
|
+
end
|
94
|
+
|
95
|
+
TokenContext.new(current_user).expire(object: current_token)
|
96
|
+
```
|
97
|
+
|
98
|
+
If you want to reuse the primary action or just prefer methods, you can pass in the method name you would like to call, rather than providing a block. If a method name and a block are provided, the block is ignored.
|
45
99
|
|
46
100
|
|
47
101
|
```ruby
|
@@ -59,10 +113,10 @@ class TokenContext
|
|
59
113
|
end
|
60
114
|
```
|
61
115
|
|
62
|
-
#### Primary
|
63
|
-
The primary
|
116
|
+
#### Primary Function
|
117
|
+
The primary function is the function that determines whether or not the action is successful. To do this, the primary function must always return a truthy value if the action was successful, or a falsey one if it failed.
|
64
118
|
|
65
|
-
The primary
|
119
|
+
The primary function is passed one argument which is the return value of the closest preceding interval function.
|
66
120
|
|
67
121
|
```ruby
|
68
122
|
# lib/contexts/token_context
|
@@ -85,67 +139,67 @@ class TokenContext
|
|
85
139
|
end
|
86
140
|
```
|
87
141
|
|
88
|
-
#### Intervals
|
89
|
-
The following are the intervals SnFoil Contexts
|
142
|
+
#### Action Intervals
|
143
|
+
The following are the intervals SnFoil Contexts set up in the order they occur. The suggested uses are just very simple examples. You can chain contexts to setup very complex interactions in a very easy-to-manage workflow.
|
90
144
|
|
91
145
|
<table>
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
146
|
+
<thead>
|
147
|
+
<th>Name</th>
|
148
|
+
<th>Suggested Use</th>
|
149
|
+
</thead>
|
150
|
+
<tbody>
|
151
|
+
<tr>
|
152
|
+
<td>setup_<action></td>
|
153
|
+
<td>
|
154
|
+
<div>* find or create a model</div>
|
155
|
+
<div>* setup params needed later in the action</div>
|
156
|
+
<div>* set scoping </div>
|
157
|
+
</td>
|
158
|
+
</tr>
|
159
|
+
<tr>
|
160
|
+
<td>before_<action></td>
|
161
|
+
<td>
|
162
|
+
<div>* alter model or set attributes</div>
|
163
|
+
</td>
|
164
|
+
</tr>
|
165
|
+
<tr>
|
166
|
+
<td>primary action</td>
|
167
|
+
<td>
|
168
|
+
<div>* persist database changes</div>
|
169
|
+
<div>* make primary network call</div>
|
170
|
+
</td>
|
171
|
+
</tr>
|
172
|
+
<tr>
|
173
|
+
<td>after_<action>_success</td>
|
174
|
+
<td>
|
175
|
+
<div>* setup additional relationships</div>
|
176
|
+
<div>* success specific logging</div>
|
177
|
+
</td>
|
178
|
+
</tr>
|
179
|
+
<tr>
|
180
|
+
<td>after_<action>_failure</td>
|
181
|
+
<td>
|
182
|
+
<div>* cleanup failed remenants</div>
|
183
|
+
<div>* call bug tracker</div>
|
184
|
+
<div>* failure specific logging</div>
|
185
|
+
</td>
|
186
|
+
</tr>
|
187
|
+
<tr>
|
188
|
+
<td>after_<action></td>
|
189
|
+
<td>
|
190
|
+
<div>* perform necessary required cleanup</div>
|
191
|
+
<div>* log outcome</div>
|
192
|
+
</td>
|
193
|
+
</tr>
|
194
|
+
</tbody>
|
141
195
|
<table>
|
142
196
|
|
143
197
|
|
144
198
|
#### Hook and Method Design
|
145
199
|
|
146
|
-
SnFoil Contexts try hard to not store variables longer than necessary. To facilitate this we have
|
200
|
+
SnFoil Contexts try hard to not store variables longer than necessary. To facilitate this we have chosen to pass an object (we normally use a hash called options) to each hook and method, and the return from the hook or method is passed down the chain to the next hook or method.
|
147
201
|
|
148
|
-
The only method or block that does not get its value
|
202
|
+
The only method or block that does not get its value passed down the chain is the primary action - which must always return a truthy value of whether or not the action was successful.
|
149
203
|
|
150
204
|
#### Hooks
|
151
205
|
Hooks make it very easy to compose multiple actions that need to occur in a specific order. You can have as many repeated hooks as you would like. This makes defining single responsibility hooks very simple, and they will get called in the order they are defined.
|
@@ -156,29 +210,29 @@ Hooks make it very easy to compose multiple actions that need to occur in a spec
|
|
156
210
|
```ruby
|
157
211
|
# Call the webhooks for third party integrations
|
158
212
|
after_expire_success do |options|
|
159
|
-
|
160
|
-
|
213
|
+
call_webhook_for_model(options[:object])
|
214
|
+
options
|
161
215
|
end
|
162
216
|
|
163
217
|
# Commit business logic to internal process
|
164
218
|
after_expire_success do |options|
|
165
|
-
|
166
|
-
|
219
|
+
finalize_business_logic(options[:object])
|
220
|
+
options
|
167
221
|
end
|
168
222
|
|
169
223
|
# notify error tracker
|
170
224
|
after_expire_error do |options|
|
171
|
-
|
172
|
-
|
225
|
+
notify_errors(options[:object].errors)
|
226
|
+
options
|
173
227
|
end
|
174
228
|
```
|
175
229
|
|
176
230
|
#### Methods
|
177
|
-
Methods allow users to create inheritable actions that occur in a specific order. Methods will always run after their hook counterpart. Since these are inheritable, you can chain needed actions
|
231
|
+
Methods allow users to create inheritable actions that occur in a specific order. Methods will always run after their hook counterpart. Since these are inheritable, you can chain needed actions through the parent hierarchy by using the `super` keyword. These are very useful when you need to have something always happen at the end of an Interval.
|
178
232
|
|
179
233
|
<strong>Important Note</strong> Methods <u>always</u> need to return the options hash at the end.
|
180
234
|
|
181
|
-
<i>Author's opinion:</i> While
|
235
|
+
<i>Author's opinion:</i> While simpler than hooks, they do not allow for as clean of a composition as hooks.
|
182
236
|
|
183
237
|
##### Example
|
184
238
|
|
@@ -186,21 +240,21 @@ Methods allow users to create inheritable actions that occur in a specific order
|
|
186
240
|
# Call the webhooks for third party integrations
|
187
241
|
# Commit business logic to internal process
|
188
242
|
def after_expire_success(**options)
|
189
|
-
|
243
|
+
options = super
|
190
244
|
|
191
|
-
|
192
|
-
|
245
|
+
call_webhook_for_model(options[:object])
|
246
|
+
finalize_business_logic(options[:object])
|
193
247
|
|
194
|
-
|
248
|
+
options
|
195
249
|
end
|
196
250
|
|
197
251
|
# notify error tracker
|
198
252
|
def after_expire_error(**options)
|
199
|
-
|
253
|
+
options = super
|
200
254
|
|
201
|
-
|
255
|
+
notify_errors(options[:object].errors)
|
202
256
|
|
203
|
-
|
257
|
+
options
|
204
258
|
end
|
205
259
|
```
|
206
260
|
|
@@ -209,10 +263,10 @@ The original purpose of all of SnFoil was to ensure there was a good consistent
|
|
209
263
|
|
210
264
|
These authorization hooks are always called twice. Once after `setup_<action>` and once after `before_<action>`
|
211
265
|
|
212
|
-
The `authorize` method functions much like primary action except the first argument is usually the name of action you are authorizing.
|
266
|
+
The `authorize` method functions much like primary action except the first argument is usually the name of the action you are authorizing.
|
213
267
|
|
214
268
|
Arguments:
|
215
|
-
* `name` - The name of this action to be authorized. If
|
269
|
+
* `name` - The name of this action to be authorized. If omitted, all actions without a specific associated authorize will use this
|
216
270
|
* `with` - Keyword Param - The method name of the primary action. Either this or a block is required
|
217
271
|
* `block` - Block - The block of the primary action. Either this or with is required
|
218
272
|
|
@@ -225,13 +279,13 @@ class TokenContext
|
|
225
279
|
|
226
280
|
action :expire, with: :expire_token
|
227
281
|
|
228
|
-
authorize
|
282
|
+
authorize(:expire) { |_options| :entity.is_admin? }
|
229
283
|
|
230
284
|
...
|
231
285
|
end
|
232
286
|
```
|
233
287
|
|
234
|
-
You can also call authorize without an action name. This will have all action authorize with the provided method or block unless there is a more specific authorize action configured.
|
288
|
+
You can also call authorize without an action name. This will have all action authorize with the provided method or block unless there is a more specific authorize action configured. It's probably easier explained with an example
|
235
289
|
|
236
290
|
|
237
291
|
```ruby
|
@@ -245,15 +299,44 @@ class TokenContext
|
|
245
299
|
action :search, with: :query_tokens #=> will authorize by checking the entity is a user
|
246
300
|
action :show, with: :find_token #=> will authorize by checking the entity is a user
|
247
301
|
|
248
|
-
authorize
|
249
|
-
authorize { |
|
302
|
+
authorize(:expire) { |_options| entity.is_admin? }
|
303
|
+
authorize { |_options| entity.is_user? }
|
250
304
|
|
251
305
|
...
|
252
306
|
end
|
253
307
|
```
|
254
308
|
|
255
309
|
#### Why before and after?
|
256
|
-
Simply to make sure the entity
|
310
|
+
Simply to make sure the entity is allowed access to the primary target and is allowed to make the requested alterations/interactions.
|
311
|
+
|
312
|
+
### Intervals
|
313
|
+
There might be a situation where you don't need a before, after, success or failure, and just need a single name pipeline you can hook into. `interval` allows you to create a single action-like segment.
|
314
|
+
|
315
|
+
```ruby
|
316
|
+
class TokenContext
|
317
|
+
include SnFoil::Context
|
318
|
+
|
319
|
+
interval :demo
|
320
|
+
|
321
|
+
demo do |options|
|
322
|
+
... # Logic Here
|
323
|
+
|
324
|
+
options
|
325
|
+
end
|
326
|
+
|
327
|
+
demo do |options|
|
328
|
+
... # Additional Steps here
|
329
|
+
|
330
|
+
options
|
331
|
+
end
|
332
|
+
end
|
333
|
+
```
|
334
|
+
|
335
|
+
Just like for an action SnFoil allows you to define both hooks and a method. To run this interval you call it using the `run_interval` method.
|
336
|
+
|
337
|
+
```ruby
|
338
|
+
TokenContext.new(entity).run_interval(:demo, **options)
|
339
|
+
```
|
257
340
|
|
258
341
|
## Development
|
259
342
|
|
@@ -271,4 +354,4 @@ The gem is available as open source under the terms of the [Apache 2 License](ht
|
|
271
354
|
|
272
355
|
## Code of Conduct
|
273
356
|
|
274
|
-
Everyone interacting in the Snfoil::Context project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/limited-effort/snfoil-context/blob/main/CODE_OF_CONDUCT.md).
|
357
|
+
Everyone interacting in the Snfoil::Context project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/limited-effort/snfoil-context/blob/main/CODE_OF_CONDUCT.md).
|
@@ -28,51 +28,63 @@ module SnFoil
|
|
28
28
|
extend ActiveSupport::Concern
|
29
29
|
|
30
30
|
class_methods do
|
31
|
-
|
31
|
+
attr_accessor :snfoil_authorizations
|
32
32
|
|
33
33
|
def authorize(action_name = nil, with: nil, &block)
|
34
|
-
@
|
34
|
+
@snfoil_authorizations ||= {}
|
35
35
|
action_name = action_name&.to_sym
|
36
36
|
|
37
|
-
|
37
|
+
if @snfoil_authorizations[action_name]
|
38
|
+
raise SnFoil::Context::Error, "#{name} already has authorize defined for #{action_name || ':default'}"
|
39
|
+
end
|
38
40
|
|
39
|
-
@
|
41
|
+
@snfoil_authorizations[action_name] = { method: with, block: block }
|
40
42
|
end
|
41
|
-
end
|
42
43
|
|
43
|
-
|
44
|
+
def inherited(subclass)
|
45
|
+
super
|
44
46
|
|
45
|
-
|
46
|
-
|
47
|
+
instance_variables.grep(/@snfoil_.+/).each do |i|
|
48
|
+
subclass.instance_variable_set(i, instance_variable_get(i).dup)
|
49
|
+
end
|
50
|
+
end
|
47
51
|
end
|
48
52
|
|
49
|
-
|
50
|
-
|
51
|
-
configured_call ||= self.class.i_authorizations&.fetch(nil, nil)
|
53
|
+
included do
|
54
|
+
attr_reader :entity
|
52
55
|
|
53
|
-
|
54
|
-
|
55
|
-
else
|
56
|
-
SnFoil.logger.info "No configuration for #{name} in #{self.class.name}. Authorize not called" if SnFoil.respond_to?(:logger)
|
57
|
-
true
|
56
|
+
def initialize(**keywords)
|
57
|
+
@entity = keywords[:entity]
|
58
58
|
end
|
59
|
-
end
|
60
59
|
|
61
|
-
|
60
|
+
def authorize(name, **options)
|
61
|
+
configured_call = self.class.snfoil_authorizations&.fetch(name.to_sym, nil)
|
62
|
+
configured_call ||= self.class.snfoil_authorizations&.fetch(nil, nil)
|
62
63
|
|
63
|
-
|
64
|
-
|
64
|
+
if configured_call
|
65
|
+
run_hook(configured_call, **options)
|
66
|
+
else
|
67
|
+
SnFoil.logger.info "No configuration for #{name} in #{self.class.name}. Authorize not called" if SnFoil.respond_to?(:logger)
|
68
|
+
true
|
69
|
+
end
|
70
|
+
end
|
65
71
|
|
66
|
-
|
72
|
+
private
|
67
73
|
|
68
|
-
|
69
|
-
|
74
|
+
def run_hook(hook, **options)
|
75
|
+
return options unless hook && hook_valid?(hook, **options)
|
70
76
|
|
71
|
-
|
72
|
-
return false if !hook[:if].nil? && hook[:if].call(options) == false
|
73
|
-
return false if !hook[:unless].nil? && hook[:unless].call(options) == true
|
77
|
+
return send(hook[:method], **options) if hook[:method]
|
74
78
|
|
75
|
-
|
79
|
+
instance_exec(**options, &hook[:block])
|
80
|
+
end
|
81
|
+
|
82
|
+
def hook_valid?(hook, **options)
|
83
|
+
return false if !hook[:if].nil? && hook[:if].call(**options) == false
|
84
|
+
return false if !hook[:unless].nil? && hook[:unless].call(**options) == true
|
85
|
+
|
86
|
+
true
|
87
|
+
end
|
76
88
|
end
|
77
89
|
end
|
78
90
|
end
|
data/lib/snfoil/context.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright 2021 Matthew Howes
|
3
|
+
# Copyright 2021 Matthew Howes, Cliff Campbell
|
4
4
|
|
5
5
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
6
|
# you may not use this file except in compliance with the License.
|
@@ -35,12 +35,27 @@ module SnFoil
|
|
35
35
|
|
36
36
|
class_methods do
|
37
37
|
def action(name, with: nil, &block)
|
38
|
-
raise SnFoil::Context::Error, "action #{name} already defined for #{self.name}" if (@
|
38
|
+
raise SnFoil::Context::Error, "action #{name} already defined for #{self.name}" if (@snfoil_actions ||= []).include?(name)
|
39
39
|
|
40
|
-
@
|
41
|
-
|
40
|
+
@snfoil_actions << name
|
41
|
+
define_workflow(name)
|
42
42
|
define_action_primary(name, with, block)
|
43
43
|
end
|
44
|
+
|
45
|
+
def interval(name)
|
46
|
+
define_singleton_methods(name)
|
47
|
+
define_instance_methods(name)
|
48
|
+
end
|
49
|
+
|
50
|
+
def intervals(*names)
|
51
|
+
names.each { |name| interval(name) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def run_interval(interval, **options)
|
56
|
+
hooks = self.class.instance_variable_get("@snfoil_#{interval}_hooks") || []
|
57
|
+
options = hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
|
58
|
+
send(interval, **options)
|
44
59
|
end
|
45
60
|
|
46
61
|
private
|
@@ -48,63 +63,58 @@ module SnFoil
|
|
48
63
|
# rubocop:disable reason: These are builder/mapping methods that are just too complex to simplify without
|
49
64
|
# making them more complex. If anyone has a better way please let me know
|
50
65
|
class_methods do # rubocop:disable Metrics/BlockLength
|
66
|
+
def define_workflow(name)
|
67
|
+
interval "setup_#{name}"
|
68
|
+
interval "before_#{name}"
|
69
|
+
interval "after_#{name}_success"
|
70
|
+
interval "after_#{name}_failure"
|
71
|
+
interval "after_#{name}"
|
72
|
+
end
|
73
|
+
|
51
74
|
def define_action_primary(name, method, block) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
52
|
-
define_method(name) do
|
53
|
-
options[:action]
|
54
|
-
|
75
|
+
define_method(name) do |*_args, **options| # rubocop:disable Metrics/MethodLength
|
76
|
+
options[:action] ||= name.to_sym
|
77
|
+
|
78
|
+
options = run_interval(format('setup_%s', name), **options)
|
55
79
|
authorize(name, **options) if respond_to?(:authorize)
|
56
80
|
|
57
|
-
options =
|
81
|
+
options = run_interval(format('before_%s', name), **options)
|
58
82
|
authorize(name, **options) if respond_to?(:authorize)
|
59
83
|
|
60
84
|
options = if run_action_primary(method, block, **options)
|
61
|
-
|
85
|
+
run_interval(format('after_%s_success', name), **options)
|
62
86
|
else
|
63
|
-
|
87
|
+
run_interval(format('after_%s_failure', name), **options)
|
64
88
|
end
|
65
|
-
|
89
|
+
run_interval(format('after_%s', name), **options)
|
66
90
|
end
|
67
91
|
end
|
68
92
|
|
69
|
-
def
|
70
|
-
|
71
|
-
hook_builder('before_%s', name)
|
72
|
-
hook_builder('after_%s_success', name)
|
73
|
-
hook_builder('after_%s_failure', name)
|
74
|
-
hook_builder('after_%s', name)
|
75
|
-
end
|
76
|
-
|
77
|
-
def hook_builder(name_format, name)
|
78
|
-
assign_singleton_methods format(name_format, name),
|
79
|
-
format("#{name_format}_hooks", name)
|
80
|
-
end
|
81
|
-
|
82
|
-
def assign_singleton_methods(method_name, singleton_var)
|
93
|
+
def define_singleton_methods(method_name)
|
94
|
+
singleton_var = "snfoil_#{method_name}_hooks"
|
83
95
|
instance_variable_set("@#{singleton_var}", [])
|
84
96
|
define_singleton_method(singleton_var) { instance_variable_get("@#{singleton_var}") }
|
85
|
-
define_singleton_method(method_name) do |
|
86
|
-
raise SnFoil::Context::ArgumentError, "\##{method_name} requires either a method name or a block" if
|
97
|
+
define_singleton_method(method_name) do |with: nil, **options, &block|
|
98
|
+
raise SnFoil::Context::ArgumentError, "\##{method_name} requires either a method name or a block" if with.nil? && block.nil?
|
87
99
|
|
88
|
-
instance_variable_get("@#{singleton_var}") << { method:
|
100
|
+
instance_variable_get("@#{singleton_var}") << { method: with,
|
89
101
|
block: block,
|
90
102
|
if: options[:if],
|
91
103
|
unless: options[:unless] }
|
92
104
|
end
|
93
105
|
end
|
106
|
+
|
107
|
+
def define_instance_methods(method_name)
|
108
|
+
define_method(method_name) do |**options|
|
109
|
+
options
|
110
|
+
end
|
111
|
+
end
|
94
112
|
end
|
95
113
|
|
96
114
|
def run_action_primary(method, block, **options)
|
97
115
|
return send(method, **options) if method
|
98
116
|
|
99
|
-
instance_exec
|
100
|
-
end
|
101
|
-
|
102
|
-
def run_action_group(group_name, **options)
|
103
|
-
options = self.class.instance_variable_get("@#{group_name}_hooks")
|
104
|
-
.reduce(options) { |opts, hook| run_hook(hook, opts) }
|
105
|
-
options = send(group_name, **options) if respond_to?(group_name)
|
106
|
-
|
107
|
-
options
|
117
|
+
instance_exec(**options, &block)
|
108
118
|
end
|
109
119
|
end
|
110
120
|
end
|
@@ -5,24 +5,24 @@ require_relative 'lib/snfoil/context/version'
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = 'snfoil-context'
|
7
7
|
spec.version = SnFoil::Context::VERSION
|
8
|
-
spec.authors = ['Matthew Howes']
|
9
|
-
spec.email = ['howeszy@gmail.com']
|
8
|
+
spec.authors = ['Matthew Howes', 'Cliff Campbell']
|
9
|
+
spec.email = ['howeszy@gmail.com', 'cliffcampbell@hey.com']
|
10
10
|
|
11
|
-
spec.summary = ''
|
12
|
-
spec.description = ''
|
11
|
+
spec.summary = 'Setup Simple Pipelined Workflows'
|
12
|
+
spec.description = 'An easy way to make extensible workflows and actions'
|
13
13
|
spec.homepage = 'https://github.com/limited-effort/snfoil-context'
|
14
|
-
spec.license = '
|
15
|
-
spec.required_ruby_version = '>= 2.
|
14
|
+
spec.license = 'Apache-2.0'
|
15
|
+
spec.required_ruby_version = '>= 2.7'
|
16
16
|
|
17
17
|
spec.metadata['homepage_uri'] = spec.homepage
|
18
18
|
spec.metadata['source_code_uri'] = spec.homepage
|
19
19
|
spec.metadata['changelog_uri'] = 'https://github.com/limited-effort/snfoil-context/blob/main/CHANGELOG.md'
|
20
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
20
21
|
|
21
22
|
# Specify which files should be added to the gem when it is released.
|
22
23
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
-
|
24
|
-
|
25
|
-
end
|
24
|
+
ignore_list = %r{\A(?:test/|spec/|bin/|features/|Rakefile|\.\w)}
|
25
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) { `git ls-files -z`.split("\x0").reject { |f| f.match(ignore_list) } }
|
26
26
|
|
27
27
|
spec.bindir = 'exe'
|
28
28
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
@@ -30,10 +30,12 @@ Gem::Specification.new do |spec|
|
|
30
30
|
|
31
31
|
spec.add_dependency 'activesupport', '>= 5.2.6'
|
32
32
|
|
33
|
+
spec.add_development_dependency 'bundle-audit', '~> 0.1.0'
|
34
|
+
spec.add_development_dependency 'fasterer', '~> 0.10.0'
|
33
35
|
spec.add_development_dependency 'pry-byebug', '~> 3.9'
|
34
36
|
spec.add_development_dependency 'rake', '~> 13.0'
|
35
37
|
spec.add_development_dependency 'rspec', '~> 3.10'
|
36
|
-
spec.add_development_dependency 'rubocop', '~> 1.
|
38
|
+
spec.add_development_dependency 'rubocop', '~> 1.29'
|
37
39
|
spec.add_development_dependency 'rubocop-performance', '~> 1.11'
|
38
40
|
spec.add_development_dependency 'rubocop-rspec', '~> 2.5'
|
39
41
|
end
|
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: snfoil-context
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Howes
|
8
|
-
|
8
|
+
- Cliff Campbell
|
9
|
+
autorequire:
|
9
10
|
bindir: exe
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2022-05-11 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: activesupport
|
@@ -24,6 +25,34 @@ dependencies:
|
|
24
25
|
- - ">="
|
25
26
|
- !ruby/object:Gem::Version
|
26
27
|
version: 5.2.6
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: bundle-audit
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 0.1.0
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 0.1.0
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: fasterer
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 0.10.0
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 0.10.0
|
27
56
|
- !ruby/object:Gem::Dependency
|
28
57
|
name: pry-byebug
|
29
58
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,14 +101,14 @@ dependencies:
|
|
72
101
|
requirements:
|
73
102
|
- - "~>"
|
74
103
|
- !ruby/object:Gem::Version
|
75
|
-
version: '1.
|
104
|
+
version: '1.29'
|
76
105
|
type: :development
|
77
106
|
prerelease: false
|
78
107
|
version_requirements: !ruby/object:Gem::Requirement
|
79
108
|
requirements:
|
80
109
|
- - "~>"
|
81
110
|
- !ruby/object:Gem::Version
|
82
|
-
version: '1.
|
111
|
+
version: '1.29'
|
83
112
|
- !ruby/object:Gem::Dependency
|
84
113
|
name: rubocop-performance
|
85
114
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,39 +137,34 @@ dependencies:
|
|
108
137
|
- - "~>"
|
109
138
|
- !ruby/object:Gem::Version
|
110
139
|
version: '2.5'
|
111
|
-
description:
|
140
|
+
description: An easy way to make extensible workflows and actions
|
112
141
|
email:
|
113
142
|
- howeszy@gmail.com
|
143
|
+
- cliffcampbell@hey.com
|
114
144
|
executables: []
|
115
145
|
extensions: []
|
116
146
|
extra_rdoc_files: []
|
117
147
|
files:
|
118
|
-
- ".github/workflows/main.yml"
|
119
|
-
- ".gitignore"
|
120
|
-
- ".rspec"
|
121
|
-
- ".rubocop.yml"
|
122
148
|
- CHANGELOG.md
|
123
149
|
- CODE_OF_CONDUCT.md
|
124
150
|
- Gemfile
|
125
151
|
- LICENSE.txt
|
126
152
|
- README.md
|
127
|
-
- Rakefile
|
128
|
-
- bin/console
|
129
|
-
- bin/setup
|
130
153
|
- lib/snfoil/context.rb
|
131
154
|
- lib/snfoil/context/argument_error.rb
|
132
155
|
- lib/snfoil/context/error.rb
|
133
156
|
- lib/snfoil/context/structure.rb
|
134
157
|
- lib/snfoil/context/version.rb
|
135
|
-
- snfoil-
|
158
|
+
- snfoil-context.gemspec
|
136
159
|
homepage: https://github.com/limited-effort/snfoil-context
|
137
160
|
licenses:
|
138
|
-
-
|
161
|
+
- Apache-2.0
|
139
162
|
metadata:
|
140
163
|
homepage_uri: https://github.com/limited-effort/snfoil-context
|
141
164
|
source_code_uri: https://github.com/limited-effort/snfoil-context
|
142
165
|
changelog_uri: https://github.com/limited-effort/snfoil-context/blob/main/CHANGELOG.md
|
143
|
-
|
166
|
+
rubygems_mfa_required: 'true'
|
167
|
+
post_install_message:
|
144
168
|
rdoc_options: []
|
145
169
|
require_paths:
|
146
170
|
- lib
|
@@ -148,7 +172,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
148
172
|
requirements:
|
149
173
|
- - ">="
|
150
174
|
- !ruby/object:Gem::Version
|
151
|
-
version: 2.
|
175
|
+
version: '2.7'
|
152
176
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
177
|
requirements:
|
154
178
|
- - ">="
|
@@ -156,7 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
156
180
|
version: '0'
|
157
181
|
requirements: []
|
158
182
|
rubygems_version: 3.1.6
|
159
|
-
signing_key:
|
183
|
+
signing_key:
|
160
184
|
specification_version: 4
|
161
|
-
summary:
|
185
|
+
summary: Setup Simple Pipelined Workflows
|
162
186
|
test_files: []
|
data/.github/workflows/main.yml
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
name: build
|
2
|
-
on:
|
3
|
-
push:
|
4
|
-
branches: [ main ]
|
5
|
-
pull_request:
|
6
|
-
|
7
|
-
jobs:
|
8
|
-
test:
|
9
|
-
|
10
|
-
runs-on: ubuntu-latest
|
11
|
-
|
12
|
-
strategy:
|
13
|
-
matrix:
|
14
|
-
ruby-version: [2.7, 2.6, 2.5]
|
15
|
-
|
16
|
-
steps:
|
17
|
-
- uses: actions/checkout@v2
|
18
|
-
- name: Set up Ruby ${{ matrix.ruby-version }}
|
19
|
-
uses: ruby/setup-ruby@v1.81.0
|
20
|
-
with:
|
21
|
-
ruby-version: ${{ matrix.ruby-version }}
|
22
|
-
bundler-cache: true
|
23
|
-
- name: Install dependencies
|
24
|
-
run: bundle install
|
25
|
-
- name: Run tests
|
26
|
-
run: bundle exec rspec
|
27
|
-
lint:
|
28
|
-
|
29
|
-
runs-on: ubuntu-latest
|
30
|
-
|
31
|
-
strategy:
|
32
|
-
matrix:
|
33
|
-
ruby-version: [2.7, 2.6, 2.5, '3.0']
|
34
|
-
|
35
|
-
steps:
|
36
|
-
- uses: actions/checkout@v2
|
37
|
-
- name: Set up Ruby ${{ matrix.ruby-version }}
|
38
|
-
uses: ruby/setup-ruby@v1.81.0
|
39
|
-
with:
|
40
|
-
ruby-version: ${{ matrix.ruby-version }}
|
41
|
-
bundler-cache: true
|
42
|
-
- name: Install dependencies
|
43
|
-
run: bundle install
|
44
|
-
- name: Run rubocop
|
45
|
-
run: bundle exec rubocop
|
46
|
-
|
data/.gitignore
DELETED
data/.rspec
DELETED
data/.rubocop.yml
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
require:
|
2
|
-
- rubocop-performance
|
3
|
-
- rubocop-rspec
|
4
|
-
|
5
|
-
AllCops:
|
6
|
-
NewCops: enable
|
7
|
-
SuggestExtensions: false
|
8
|
-
TargetRubyVersion: 2.5.0
|
9
|
-
|
10
|
-
# ================ LAYOUT ==============
|
11
|
-
Layout/LineLength:
|
12
|
-
Max: 150
|
13
|
-
|
14
|
-
# ================ LINT ================
|
15
|
-
Lint/AmbiguousBlockAssociation:
|
16
|
-
Exclude:
|
17
|
-
- spec/**/*_spec.rb
|
18
|
-
|
19
|
-
Lint/EmptyClass:
|
20
|
-
Exclude:
|
21
|
-
- spec/**/*_spec.rb
|
22
|
-
|
23
|
-
# ================ Metics ================
|
24
|
-
Metrics/BlockLength:
|
25
|
-
Exclude:
|
26
|
-
- spec/**/*_spec.rb
|
27
|
-
|
28
|
-
# ================ RSPEC ================
|
29
|
-
RSpec/FilePath:
|
30
|
-
Enabled: false
|
31
|
-
|
32
|
-
RSpec/MultipleExpectations:
|
33
|
-
Max: 5
|
34
|
-
|
35
|
-
RSpec/MultipleMemoizedHelpers:
|
36
|
-
Enabled: false
|
37
|
-
|
38
|
-
RSpec/NestedGroups:
|
39
|
-
Max: 5
|
data/Rakefile
DELETED
data/bin/console
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'bundler/setup'
|
5
|
-
require 'snfoil/policy'
|
6
|
-
|
7
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
-
# with your gem easier. You can also use a different console, if you like.
|
9
|
-
|
10
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
-
# require "pry"
|
12
|
-
# Pry.start
|
13
|
-
|
14
|
-
require 'irb'
|
15
|
-
IRB.start(__FILE__)
|