pathway 0.11.1 → 0.12.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 +4 -4
- data/.github/workflows/tests.yml +28 -0
- data/CHANGELOG.md +13 -0
- data/README.md +94 -53
- data/lib/pathway/plugins/auto_deconstruct_state/ruby3.rb +21 -0
- data/lib/pathway/plugins/auto_deconstruct_state.rb +12 -0
- data/lib/pathway/plugins/dry_validation/v1_0.rb +6 -6
- data/lib/pathway/plugins/responder.rb +2 -2
- data/lib/pathway/plugins/sequel_models.rb +6 -5
- data/lib/pathway/version.rb +1 -1
- data/lib/pathway.rb +18 -15
- data/pathway.gemspec +6 -4
- metadata +43 -13
- data/.circleci/config.yml +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a2a1f9207d8b2db81dbf655234010e4cb8c622299cb69c1dfaa9486f824a7e86
|
4
|
+
data.tar.gz: 25e380e764a1b1585b86ae847b2cbc107a83fa23f1a1205e4e1e5e1a2c021c50
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 393014838fece86b27409c08ef0f5ccb9d7dacf5f5012d956bbf0e7ca3869175e4090c6dd23cf6c8f61bfcecd15839a738b87afcf911c226a06ea36e8cb91dc3
|
7
|
+
data.tar.gz: 1dd63c594d299837fb1b595f14fd2056ac39f81fdd508254c331fb9ef4b3a67262e0f47683bb5e0296708076d88c12aef0dd981c8864391d43f0bc6083ad9422
|
@@ -0,0 +1,28 @@
|
|
1
|
+
name: Tests
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ master ]
|
6
|
+
pull_request:
|
7
|
+
branches: [ master ]
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
test:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
strategy:
|
13
|
+
matrix:
|
14
|
+
ruby-version: [2.6, 2.7, 3.0, 3.1]
|
15
|
+
steps:
|
16
|
+
- uses: actions/checkout@v2
|
17
|
+
- name: Set up Ruby
|
18
|
+
uses: ruby/setup-ruby@v1
|
19
|
+
with:
|
20
|
+
ruby-version: ${{ matrix.ruby-version }}
|
21
|
+
bundler-cache: true
|
22
|
+
- name: Run tests
|
23
|
+
run: bundle exec rake
|
24
|
+
- name: Coveralls GitHub Action
|
25
|
+
if: matrix.ruby-version == '3.1'
|
26
|
+
uses: coverallsapp/github-action@1.1.3
|
27
|
+
with:
|
28
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
## [0.12.0] - 2022-05-31
|
2
|
+
### Changed
|
3
|
+
- Improve compatibility with Ruby 3.0
|
4
|
+
- Add plugin `:auto_deconstruct_state` to help migrating old apps to Ruby 3.0
|
5
|
+
|
6
|
+
## [0.11.3] - 2020-07-22
|
7
|
+
### Changed
|
8
|
+
- Use default error message on `:fetch_model` step, at `:sequel_models` plugin, if model type cannot be determined
|
9
|
+
|
10
|
+
## [0.11.2] - 2020-07-22
|
11
|
+
### Changed
|
12
|
+
- Improve `from:` option for `:fetch_model` step, at `:sequel_models` plugin, to also accept a Sequel Dataset
|
13
|
+
|
1
14
|
## [0.11.1] - 2020-01-09
|
2
15
|
### Changed
|
3
16
|
- Improve custom `rspec` matchers for testing field presence on schemas
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Pathway
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/pathway)
|
4
|
-
[](https://github.com/oabloh/pathway/actions?query=workflow%3ATests)
|
5
5
|
[](https://coveralls.io/github/pabloh/pathway?branch=master)
|
6
6
|
|
7
7
|
Pathway encapsulates your business logic into simple operation objects (AKA application services on the [DDD](https://en.wikipedia.org/wiki/Domain-driven_design) lingo).
|
@@ -91,36 +91,37 @@ end
|
|
91
91
|
#### Error objects
|
92
92
|
|
93
93
|
`Pathway::Error` is a helper class to represent the error description from an failed operation execution (and can be used also for pattern matching as we'll see later).
|
94
|
-
|
94
|
+
Its use is completely optional, but provides you with a basic schema to communicate what when wrong. You can instantiate it by calling `new` on the class itself or using the helper method `error` provided by the operation class:
|
95
95
|
|
96
96
|
```ruby
|
97
97
|
class CreateNugget < Pathway::Operation
|
98
98
|
def call(input)
|
99
|
-
validation =
|
99
|
+
validation = Validator.call(input)
|
100
100
|
|
101
101
|
if validation.ok?
|
102
102
|
success(Nugget.create(validation.values))
|
103
103
|
else
|
104
|
-
error(
|
104
|
+
error(:validation, message: 'Invalid input', details: validation.errors)
|
105
105
|
end
|
106
106
|
end
|
107
107
|
end
|
108
108
|
```
|
109
109
|
|
110
|
-
As you can see `error(...)` expects `type
|
110
|
+
As you can see `error(...)` expects the `type` as the first parameter (and only the mandatory) then `message:` and `details` keyword arguments; these 2 last ones can be omitted and have default values. The type parameter must be a `Symbol`, `message:` a `String` and `details:` can be a `Hash` or any other structure you see fit.
|
111
111
|
|
112
|
-
|
112
|
+
Finally, the `Error` object have three accessors available to get the values back:
|
113
113
|
|
114
114
|
```ruby
|
115
115
|
result = CreateNugget.new.call(foo: 'foobar')
|
116
116
|
if result.failure?
|
117
117
|
puts "#{result.error.type} error: #{result.error.message}"
|
118
|
+
puts "Error details: #{result.error.details}"
|
118
119
|
end
|
119
120
|
|
120
121
|
```
|
121
122
|
|
122
123
|
Mind you, `error(...)` creates an `Error` object wrapped into a `Pathway::Failure` so you don't have to do it yourself.
|
123
|
-
If you decide to use `Pathway::Error.new(...)` directly,
|
124
|
+
If you decide to use `Pathway::Error.new(...)` directly, you will have to pass all the arguments as keywords (including `type:`), and you will have to wrap the object before returning it.
|
124
125
|
|
125
126
|
#### Initialization context
|
126
127
|
|
@@ -135,7 +136,7 @@ class CreateNugget < Pathway::Operation
|
|
135
136
|
context :current_user, notify: false
|
136
137
|
|
137
138
|
def call(input)
|
138
|
-
validation =
|
139
|
+
validation = Validator.call(input)
|
139
140
|
|
140
141
|
if validation.valid?
|
141
142
|
nugget = Nugget.create(owner: current_user, **validation.values)
|
@@ -143,7 +144,7 @@ class CreateNugget < Pathway::Operation
|
|
143
144
|
Notifier.notify(:new_nugget, nugget) if @notify
|
144
145
|
success(nugget)
|
145
146
|
else
|
146
|
-
error(
|
147
|
+
error(:validation, message: 'Invalid input', details: validation.errors)
|
147
148
|
end
|
148
149
|
end
|
149
150
|
end
|
@@ -266,17 +267,17 @@ class CreateNugget < Pathway::Operation
|
|
266
267
|
if validation.ok?
|
267
268
|
state[:params] = validation.values
|
268
269
|
else
|
269
|
-
error(
|
270
|
+
error(:validation, details: validation.errors)
|
270
271
|
end
|
271
272
|
end
|
272
273
|
|
273
274
|
def create_nugget(:params, **)
|
274
275
|
Nugget.create(owner: current_user, **params)
|
275
|
-
|
276
|
+
end
|
276
277
|
|
277
278
|
def notify(:nugget, **)
|
278
279
|
Notifier.notify(:new_nugget, nugget)
|
279
|
-
|
280
|
+
end
|
280
281
|
end
|
281
282
|
```
|
282
283
|
|
@@ -313,8 +314,8 @@ class SomeOperation < BaseOperation
|
|
313
314
|
end
|
314
315
|
```
|
315
316
|
|
316
|
-
The plugin name must be specified as a `Symbol` (or also as the `Module` where is implemented, but more on that later), and can
|
317
|
-
When activated it will enrich your operations with new instance and class methods plus extra customs step for the process DSL.
|
317
|
+
The plugin name must be specified as a `Symbol` (or also as the `Module` where is implemented, but more on that later), and it can take parameters next to the plugin's name.
|
318
|
+
When activated it will enrich your operations with new instance and class methods plus extra customs step for the `process` DSL.
|
318
319
|
|
319
320
|
Mind you, if you wish to activate a plugin for a number of operations you can activate it for all of them directly on the `Pathway::Operation` class, or you can create your own base operation and all its descendants will inherit the base class' plugins.
|
320
321
|
|
@@ -322,21 +323,23 @@ Mind you, if you wish to activate a plugin for a number of operations you can ac
|
|
322
323
|
|
323
324
|
This plugin provides integration with the [dry-validation](http://dry-rb.org/gems/dry-validation/) gem. I won't explain in detail how to use this library since is already extensively documented on its official website, but instead I'll assume certain knowledge of it, nonetheless, as you'll see in a moment, its API pretty self-explanatory.
|
324
325
|
|
325
|
-
`dry-validation` provides a very simple way to define
|
326
|
+
`dry-validation` provides a very simple way to define contract objects (conceptually very similar to form objects) to process and validate input. The provided custom `:validate` step allows you to run your input though a contract to check if your data is valid before carrying on. When the input is invalid it will return an error object of type `:validation` and the reasons the validation failed will be available at the `details` attribute. Is usually the first step an operation runs.
|
326
327
|
|
327
|
-
When using this plugin we
|
328
|
+
When using this plugin we can provide an already defined contract to the step to use or we can also define it within the operation.
|
328
329
|
Let's see a few examples:
|
329
330
|
|
330
331
|
```ruby
|
331
|
-
|
332
|
-
|
333
|
-
|
332
|
+
class NuggetContract < Dry::Validation::Contract
|
333
|
+
params do
|
334
|
+
required(:owner).filled(:string)
|
335
|
+
required(:price).filled(:integer)
|
336
|
+
end
|
334
337
|
end
|
335
338
|
|
336
339
|
class CreateNugget < Pathway::Operation
|
337
340
|
plugin :dry_validation
|
338
341
|
|
339
|
-
|
342
|
+
contract NuggetContract
|
340
343
|
|
341
344
|
process do
|
342
345
|
step :validate
|
@@ -347,15 +350,17 @@ class CreateNugget < Pathway::Operation
|
|
347
350
|
end
|
348
351
|
```
|
349
352
|
|
350
|
-
As
|
353
|
+
As is is shown above, the contract is defined first, then is configured it will be used by the operation by calling `contract NuggetContract`, and validate the input at the process block by placing the step `step :validate` inside the `process` block.
|
351
354
|
|
352
355
|
```ruby
|
353
356
|
class CreateNugget < Pathway::Operation
|
354
357
|
plugin :dry_validation
|
355
358
|
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
+
contract do
|
360
|
+
params do
|
361
|
+
required(:owner).filled(:string)
|
362
|
+
required(:price).filled(:integer)
|
363
|
+
end
|
359
364
|
end
|
360
365
|
|
361
366
|
process do
|
@@ -367,16 +372,36 @@ class CreateNugget < Pathway::Operation
|
|
367
372
|
end
|
368
373
|
```
|
369
374
|
|
370
|
-
Now, this second example is equivalent to the first one, but here we call `
|
375
|
+
Now, this second example is equivalent to the first one, but here we call `contract` with a block instead and no parameter; this block will be used as definition body for a contract class that will be stored internally. Thus keeping the contract and operation code at the same place, this is convenient when you have a rather simpler contract and don't need to reuse it.
|
376
|
+
|
377
|
+
One interesting nuance to keep in mind regarding the inline block contract is that, when doing operation inheritance, if the parent operation already has a contract, the child operation will define a new one inheriting from the parent's. This is very useful to share validation logic among related operations in the same class hierarchy.
|
378
|
+
|
379
|
+
As a side note, if your contract is simple enough and only have params, you can call the `params` method directly instead, the following code is essentially equivalent to previous example:
|
380
|
+
|
381
|
+
```ruby
|
382
|
+
class CreateNugget < Pathway::Operation
|
383
|
+
plugin :dry_validation
|
384
|
+
|
385
|
+
params do
|
386
|
+
required(:owner).filled(:string)
|
387
|
+
required(:price).filled(:integer)
|
388
|
+
end
|
389
|
+
|
390
|
+
process do
|
391
|
+
step :validate
|
392
|
+
step :create_nugget
|
393
|
+
end
|
371
394
|
|
372
|
-
|
395
|
+
# ...
|
396
|
+
end
|
397
|
+
```
|
373
398
|
|
374
|
-
#####
|
399
|
+
##### Contract options
|
375
400
|
|
376
|
-
If you are familiar with `dry-validation` you probably know it provides a way to [inject options](
|
401
|
+
If you are familiar with `dry-validation` you probably know it provides a way to [inject options](https://dry-rb.org/gems/dry-validation/1.4/external-dependencies/) before calling the contract.
|
377
402
|
|
378
|
-
On those scenarios you must either use the `auto_wire_options: true` plugin argument, or specify how to map options from the execution state to the
|
379
|
-
Lets see and example for
|
403
|
+
On those scenarios you must either use the `auto_wire_options: true` plugin argument, or specify how to map options from the execution state to the contract when calling `step :validate`.
|
404
|
+
Lets see and example for the first case:
|
380
405
|
|
381
406
|
```ruby
|
382
407
|
class CreateNugget < Pathway::Operation
|
@@ -384,11 +409,17 @@ class CreateNugget < Pathway::Operation
|
|
384
409
|
|
385
410
|
context :user_name
|
386
411
|
|
387
|
-
|
388
|
-
|
412
|
+
contract do
|
413
|
+
option :user_name
|
414
|
+
|
415
|
+
params do
|
416
|
+
required(:owner).filled(:string)
|
417
|
+
required(:price).filled(:integer)
|
418
|
+
end
|
389
419
|
|
390
|
-
|
391
|
-
|
420
|
+
rule(:owner) do
|
421
|
+
key.failure("invalid owner") unless user_name == values[:owner]
|
422
|
+
end
|
392
423
|
end
|
393
424
|
|
394
425
|
process do
|
@@ -400,25 +431,33 @@ class CreateNugget < Pathway::Operation
|
|
400
431
|
end
|
401
432
|
```
|
402
433
|
|
403
|
-
Here the defined
|
434
|
+
Here the defined contract needs a `:user_name` option, so we tell the operation to grab the attribute with the same name from the state by activating `:auto_wire_options`, afterwards, when the validation runs, the contract will already have the user name available.
|
404
435
|
|
405
436
|
Mind you, this option is `false` by default, so be sure to set it to `true` at `Pathway::Operation` if you'd rather have it enabled for all your operations.
|
406
437
|
|
438
|
+
On the other hand, if for some reason the name of the contract's option and state attribute don't match, we can just pass `with: {...}` when calling to `step :validate`, indicating how to wire the attributes, the following example illustrates just that:
|
439
|
+
|
407
440
|
```ruby
|
408
441
|
class CreateNugget < Pathway::Operation
|
409
442
|
plugin :dry_validation
|
410
443
|
|
411
444
|
context :current_user_name
|
412
445
|
|
413
|
-
|
414
|
-
|
446
|
+
contract do
|
447
|
+
option :user_name
|
448
|
+
|
449
|
+
params do
|
450
|
+
required(:owner).filled(:string)
|
451
|
+
required(:price).filled(:integer)
|
452
|
+
end
|
415
453
|
|
416
|
-
|
417
|
-
|
454
|
+
rule(:owner) do
|
455
|
+
key.failure("invalid owner") unless user_name == values[:owner]
|
456
|
+
end
|
418
457
|
end
|
419
458
|
|
420
459
|
process do
|
421
|
-
step :validate, with: { user_name: :current_user_name } # Inject :user_name to the
|
460
|
+
step :validate, with: { user_name: :current_user_name } # Inject :user_name to the contract object with the state's :current_user_name
|
422
461
|
step :create_nugget
|
423
462
|
end
|
424
463
|
|
@@ -426,10 +465,12 @@ class CreateNugget < Pathway::Operation
|
|
426
465
|
end
|
427
466
|
```
|
428
467
|
|
429
|
-
On the other hand, if for some reason the name of the form's option and state attribute don't match, we can just pass `with: {...}` when calling to `step :validate`, indicating how to wire the attributes, the example above illustrates just that.
|
430
|
-
|
431
468
|
The `with:` parameter can always be specified, at `step :validate`, and allows you to override the default mapping regardless if auto-wiring is active or not.
|
432
469
|
|
470
|
+
##### Older versions of `dry-validation`
|
471
|
+
|
472
|
+
Pathway supports the `dry-validation` gem down to version `0.11` (inclusive) in case you still have unmigrated code. When using versions bellow `1.0` the concept of contract is not present and instead of calling the `contract` method to setup your validation logic you must use the `form` method. Everything else remains the same except, obviously, that you would have to use `dry-definition`'s [old API](https://dry-rb.org/gems/dry-validation/0.13/) which is a bit different from the current one.
|
473
|
+
|
433
474
|
#### `SimpleAuth` plugin
|
434
475
|
|
435
476
|
This very simple plugin adds a custom step called `:authorize`, that can be used to check for permissions and halt the operation with a `:forbidden` error when they aren't fulfilled.
|
@@ -673,7 +714,7 @@ require 'pathway/rspec'
|
|
673
714
|
|
674
715
|
#### Rspec matchers
|
675
716
|
|
676
|
-
Pathway
|
717
|
+
Pathway provides a few matchers in order to tests your operation easier.
|
677
718
|
Let's go through a full example and break it up in the following subsections:
|
678
719
|
|
679
720
|
```ruby
|
@@ -682,10 +723,10 @@ Let's go through a full example and break it up in the following subsections:
|
|
682
723
|
class CreateNugget < Pathway::Operation
|
683
724
|
plugin :dry_validation
|
684
725
|
|
685
|
-
|
686
|
-
required(:owner).filled(:
|
687
|
-
required(:price).filled(:
|
688
|
-
optional(:disabled).maybe(:bool
|
726
|
+
params do
|
727
|
+
required(:owner).filled(:string)
|
728
|
+
required(:price).filled(:integer)
|
729
|
+
optional(:disabled).maybe(:bool)
|
689
730
|
end
|
690
731
|
|
691
732
|
process do
|
@@ -721,8 +762,8 @@ describe CreateNugget do
|
|
721
762
|
end
|
722
763
|
end
|
723
764
|
|
724
|
-
describe '.
|
725
|
-
subject(:
|
765
|
+
describe '.contract' do
|
766
|
+
subject(:contract) { CreateNugget.build_contract }
|
726
767
|
|
727
768
|
it { is_expected.to require_fields(:owner, :price) }
|
728
769
|
it { is_expected.to accept_optional_field(:disabled) }
|
@@ -739,13 +780,13 @@ The assertion it performs is simply is that the operation was successful, also y
|
|
739
780
|
|
740
781
|
This second matcher is analog to `succeed_on` but it asserts that operation execution was a failure instead. Also if you return an error object, and you need to, you can assert the error type using the `type` chain method (aliased as `and_type` and `with_type`); the error message (`and_message`, `with_message` or `message`); and the error details (`and_details`, `with_details` or `details`). Mind you, the chain methods for the message and details accept nested matchers while the `type` chain can only test by equality.
|
741
782
|
|
742
|
-
##### form matchers
|
783
|
+
##### contract/form matchers
|
743
784
|
|
744
|
-
Finally we can see that we are also testing the operation's form, implemented here with the `dry-validation` gem.
|
785
|
+
Finally we can see that we are also testing the operation's contract (or form), implemented here with the `dry-validation` gem.
|
745
786
|
|
746
|
-
Two more matchers are provided
|
787
|
+
Two more matchers are provided: `require_fields` (aliased `require_field`) to test when a contract is expected to define a required set of fields, and `accept_optional_fields` (aliased `accept_optional_field`) to test when a contract must define a certain set of optional fields, both the contract class (at operation class method `contract_class`) or an instance (operation class method `build_contract`) can be provided.
|
747
788
|
|
748
|
-
These matchers are only useful when using `dry-validation` and will
|
789
|
+
These matchers are only useful when using `dry-validation` (on every version newer or equal to `0.11.0`) and will probably be extracted to their own gem in the future.
|
749
790
|
|
750
791
|
## Development
|
751
792
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pathway
|
4
|
+
module Plugins
|
5
|
+
module AutoDeconstructState
|
6
|
+
module DSLMethods
|
7
|
+
private
|
8
|
+
|
9
|
+
def _callable(callable)
|
10
|
+
if callable.is_a?(Symbol) && @operation.respond_to?(callable, true) &&
|
11
|
+
@operation.method(callable).parameters.all? { _1 in [:key|:keyreq|:keyrest|:block,*] }
|
12
|
+
|
13
|
+
-> state { @operation.send(callable, **state) }
|
14
|
+
else
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -19,7 +19,7 @@ module Pathway
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
def params(*args, &block)
|
22
|
+
ruby2_keywords def params(*args, &block)
|
23
23
|
contract { params(*args, &block) }
|
24
24
|
end
|
25
25
|
|
@@ -29,8 +29,8 @@ module Pathway
|
|
29
29
|
@builded_contract = @contract_options.empty? && klass.schema ? klass.new : nil
|
30
30
|
end
|
31
31
|
|
32
|
-
def build_contract(opts
|
33
|
-
@builded_contract || contract_class.new(opts)
|
32
|
+
def build_contract(**opts)
|
33
|
+
@builded_contract || contract_class.new(**opts)
|
34
34
|
end
|
35
35
|
|
36
36
|
def inherited(subclass)
|
@@ -57,12 +57,12 @@ module Pathway
|
|
57
57
|
with ||= contract_options.zip(contract_options).to_h
|
58
58
|
end
|
59
59
|
opts = Hash(with).map { |to, from| [to, state[from]] }.to_h
|
60
|
-
validate_with(state[:input], opts)
|
60
|
+
validate_with(state[:input], **opts)
|
61
61
|
.then { |params| state.update(params: params) }
|
62
62
|
end
|
63
63
|
|
64
|
-
def validate_with(input, opts
|
65
|
-
result = contract(opts).call(input)
|
64
|
+
def validate_with(input, **opts)
|
65
|
+
result = contract(**opts).call(input)
|
66
66
|
|
67
67
|
result.success? ? wrap(result.values.to_h) : error(:validation, details: result.errors.to_h)
|
68
68
|
end
|
@@ -4,8 +4,8 @@ module Pathway
|
|
4
4
|
module Plugins
|
5
5
|
module Responder
|
6
6
|
module ClassMethods
|
7
|
-
def call(
|
8
|
-
result = super(
|
7
|
+
ruby2_keywords def call(*args, &bl)
|
8
|
+
result = super(*args)
|
9
9
|
block_given? ? Responder.respond(result, &bl) : result
|
10
10
|
end
|
11
11
|
end
|
@@ -63,10 +63,11 @@ module Pathway
|
|
63
63
|
delegate :db => :model_class
|
64
64
|
|
65
65
|
def fetch_model(state, from: model_class, search_by: search_field, using: search_by, to: result_key, overwrite: false, error_message: nil)
|
66
|
-
error_message ||= if from
|
67
|
-
Inflector.humanize(Inflector.underscore(Inflector.demodulize(from.name))) + ' not found'
|
68
|
-
else
|
66
|
+
error_message ||= if (from == model_class)
|
69
67
|
model_not_found
|
68
|
+
elsif from.respond_to?(:name) || from.respond_to?(:model)
|
69
|
+
from_name = (from.respond_to?(:name) ? from : from.model).name
|
70
|
+
Inflector.humanize(Inflector.underscore(Inflector.demodulize(from_name))) + ' not found'
|
70
71
|
end
|
71
72
|
|
72
73
|
if state[to].nil? || overwrite
|
@@ -83,8 +84,8 @@ module Pathway
|
|
83
84
|
end
|
84
85
|
end
|
85
86
|
|
86
|
-
def self.apply(operation, model: nil, **
|
87
|
-
operation.model(model,
|
87
|
+
def self.apply(operation, model: nil, **kwargs)
|
88
|
+
operation.model(model, **kwargs) if model
|
88
89
|
end
|
89
90
|
end
|
90
91
|
end
|
data/lib/pathway/version.rb
CHANGED
data/lib/pathway.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'ruby2_keywords'
|
3
4
|
require 'forwardable'
|
4
5
|
require 'dry/inflector'
|
5
6
|
require 'contextualizer'
|
@@ -9,21 +10,23 @@ require 'pathway/result'
|
|
9
10
|
module Pathway
|
10
11
|
Inflector = Dry::Inflector.new
|
11
12
|
class Operation
|
12
|
-
|
13
|
-
|
13
|
+
class << self
|
14
|
+
ruby2_keywords def plugin(name, *args)
|
15
|
+
require "pathway/plugins/#{Inflector.underscore(name)}" if name.is_a?(Symbol)
|
14
16
|
|
15
|
-
|
17
|
+
plugin = name.is_a?(Module) ? name : Plugins.const_get(Inflector.camelize(name))
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
19
|
+
self.extend plugin::ClassMethods if plugin.const_defined? :ClassMethods
|
20
|
+
self.include plugin::InstanceMethods if plugin.const_defined? :InstanceMethods
|
21
|
+
self::DSL.include plugin::DSLMethods if plugin.const_defined? :DSLMethods
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
+
plugin.apply(self, *args) if plugin.respond_to?(:apply)
|
24
|
+
end
|
23
25
|
|
24
|
-
|
25
|
-
|
26
|
-
|
26
|
+
def inherited(subclass)
|
27
|
+
super
|
28
|
+
subclass.const_set :DSL, Class.new(self::DSL)
|
29
|
+
end
|
27
30
|
end
|
28
31
|
|
29
32
|
class DSL
|
@@ -90,7 +93,7 @@ module Pathway
|
|
90
93
|
end
|
91
94
|
end
|
92
95
|
|
93
|
-
def call(ctx, *params)
|
96
|
+
ruby2_keywords def call(ctx, *params)
|
94
97
|
new(ctx).call(*params)
|
95
98
|
end
|
96
99
|
|
@@ -137,7 +140,7 @@ module Pathway
|
|
137
140
|
end
|
138
141
|
|
139
142
|
# Execute step and preserve the former state
|
140
|
-
def step(callable, *args)
|
143
|
+
ruby2_keywords def step(callable, *args)
|
141
144
|
bl = _callable(callable)
|
142
145
|
|
143
146
|
@result = @result.tee { |state| bl.call(state, *args) }
|
@@ -190,9 +193,9 @@ module Pathway
|
|
190
193
|
def _callable(callable)
|
191
194
|
case callable
|
192
195
|
when Proc
|
193
|
-
-> *args { @operation.instance_exec(*args, &callable) }
|
196
|
+
-> *args { @operation.instance_exec(*args, &callable) }.ruby2_keywords
|
194
197
|
when Symbol
|
195
|
-
-> *args { @operation.send(callable, *args) }
|
198
|
+
-> *args { @operation.send(callable, *args) }.ruby2_keywords
|
196
199
|
else
|
197
200
|
callable
|
198
201
|
end
|
data/pathway.gemspec
CHANGED
@@ -31,13 +31,15 @@ Gem::Specification.new do |spec|
|
|
31
31
|
|
32
32
|
spec.add_dependency "dry-inflector", ">= 0.1.0"
|
33
33
|
spec.add_dependency "contextualizer", "~> 0.0.4"
|
34
|
+
spec.add_dependency "ruby2_keywords"
|
34
35
|
|
35
36
|
spec.add_development_dependency "dry-validation", ">= 0.11"
|
36
|
-
spec.add_development_dependency "bundler", "
|
37
|
-
spec.add_development_dependency "sequel", "~> 5.
|
37
|
+
spec.add_development_dependency "bundler", "~> 2.3.7"
|
38
|
+
spec.add_development_dependency "sequel", "~> 5.0"
|
38
39
|
spec.add_development_dependency "rake", "~> 13.0"
|
39
|
-
spec.add_development_dependency "rspec", "~> 3.
|
40
|
-
spec.add_development_dependency "
|
40
|
+
spec.add_development_dependency "rspec", "~> 3.11"
|
41
|
+
spec.add_development_dependency "simplecov-lcov", '~> 0.8.0'
|
42
|
+
spec.add_development_dependency "simplecov"
|
41
43
|
spec.add_development_dependency "pry"
|
42
44
|
spec.add_development_dependency "pry-byebug"
|
43
45
|
spec.add_development_dependency "pry-doc"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pathway
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pablo Herrero
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-06-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-inflector
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 0.0.4
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ruby2_keywords
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: dry-validation
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -56,30 +70,30 @@ dependencies:
|
|
56
70
|
name: bundler
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
|
-
- - "
|
73
|
+
- - "~>"
|
60
74
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
75
|
+
version: 2.3.7
|
62
76
|
type: :development
|
63
77
|
prerelease: false
|
64
78
|
version_requirements: !ruby/object:Gem::Requirement
|
65
79
|
requirements:
|
66
|
-
- - "
|
80
|
+
- - "~>"
|
67
81
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
82
|
+
version: 2.3.7
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: sequel
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
72
86
|
requirements:
|
73
87
|
- - "~>"
|
74
88
|
- !ruby/object:Gem::Version
|
75
|
-
version: 5.
|
89
|
+
version: '5.0'
|
76
90
|
type: :development
|
77
91
|
prerelease: false
|
78
92
|
version_requirements: !ruby/object:Gem::Requirement
|
79
93
|
requirements:
|
80
94
|
- - "~>"
|
81
95
|
- !ruby/object:Gem::Version
|
82
|
-
version: 5.
|
96
|
+
version: '5.0'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: rake
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -100,16 +114,30 @@ dependencies:
|
|
100
114
|
requirements:
|
101
115
|
- - "~>"
|
102
116
|
- !ruby/object:Gem::Version
|
103
|
-
version: '3.
|
117
|
+
version: '3.11'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '3.11'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: simplecov-lcov
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.8.0
|
104
132
|
type: :development
|
105
133
|
prerelease: false
|
106
134
|
version_requirements: !ruby/object:Gem::Requirement
|
107
135
|
requirements:
|
108
136
|
- - "~>"
|
109
137
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
138
|
+
version: 0.8.0
|
111
139
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
140
|
+
name: simplecov
|
113
141
|
requirement: !ruby/object:Gem::Requirement
|
114
142
|
requirements:
|
115
143
|
- - ">="
|
@@ -171,7 +199,7 @@ executables: []
|
|
171
199
|
extensions: []
|
172
200
|
extra_rdoc_files: []
|
173
201
|
files:
|
174
|
-
- ".
|
202
|
+
- ".github/workflows/tests.yml"
|
175
203
|
- ".gitignore"
|
176
204
|
- ".rspec"
|
177
205
|
- CHANGELOG.md
|
@@ -185,6 +213,8 @@ files:
|
|
185
213
|
- bin/rspec
|
186
214
|
- bin/setup
|
187
215
|
- lib/pathway.rb
|
216
|
+
- lib/pathway/plugins/auto_deconstruct_state.rb
|
217
|
+
- lib/pathway/plugins/auto_deconstruct_state/ruby3.rb
|
188
218
|
- lib/pathway/plugins/dry_validation.rb
|
189
219
|
- lib/pathway/plugins/dry_validation/v0_11.rb
|
190
220
|
- lib/pathway/plugins/dry_validation/v0_12.rb
|
@@ -225,7 +255,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
225
255
|
- !ruby/object:Gem::Version
|
226
256
|
version: '0'
|
227
257
|
requirements: []
|
228
|
-
rubygems_version: 3.
|
258
|
+
rubygems_version: 3.3.7
|
229
259
|
signing_key:
|
230
260
|
specification_version: 4
|
231
261
|
summary: Define your business logic in simple steps.
|
data/.circleci/config.yml
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
version: 2.0
|
2
|
-
shared: &shared
|
3
|
-
steps:
|
4
|
-
- checkout
|
5
|
-
- run:
|
6
|
-
name: Bundle gems
|
7
|
-
command: |
|
8
|
-
echo '--no-rdoc --no-ri' > '.gemrc'
|
9
|
-
bundle install --jobs=4 --retry=3 --path vendor/bundle
|
10
|
-
- run:
|
11
|
-
name: Run tests
|
12
|
-
command: bundle exec rspec --format documentation --color --format progress spec
|
13
|
-
|
14
|
-
jobs:
|
15
|
-
"ruby-2.4":
|
16
|
-
<<: *shared
|
17
|
-
docker:
|
18
|
-
- image: circleci/ruby:2.4
|
19
|
-
|
20
|
-
"ruby-2.5":
|
21
|
-
<<: *shared
|
22
|
-
docker:
|
23
|
-
- image: circleci/ruby:2.5
|
24
|
-
|
25
|
-
"ruby-2.6":
|
26
|
-
<<: *shared
|
27
|
-
docker:
|
28
|
-
- image: circleci/ruby:2.6
|
29
|
-
environment:
|
30
|
-
REPORT_COVERAGE: 'true'
|
31
|
-
|
32
|
-
workflows:
|
33
|
-
version: 2
|
34
|
-
build:
|
35
|
-
jobs:
|
36
|
-
- "ruby-2.4"
|
37
|
-
- "ruby-2.5"
|
38
|
-
- "ruby-2.6"
|