trailblazer-endpoint 0.0.5 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +21 -0
- data/README.md +40 -5
- data/lib/trailblazer/endpoint/controller.rb +55 -22
- data/lib/trailblazer/endpoint/dsl.rb +2 -0
- data/lib/trailblazer/endpoint/options.rb +0 -23
- data/lib/trailblazer/endpoint/protocol/cipher.rb +27 -0
- data/lib/trailblazer/endpoint/protocol/controller.rb +102 -0
- data/lib/trailblazer/endpoint/protocol/find_process_model.rb +15 -0
- data/lib/trailblazer/endpoint/protocol.rb +2 -3
- data/lib/trailblazer/endpoint/version.rb +1 -1
- data/lib/trailblazer/endpoint.rb +45 -25
- data/test/docs/controller_test.rb +61 -1
- data/test/rails-app/Gemfile +17 -1
- data/test/rails-app/Gemfile.lock +134 -83
- data/test/rails-app/app/concepts/song/cell/create.rb +4 -0
- data/test/rails-app/app/concepts/song/cell/new.rb +4 -0
- data/test/rails-app/app/concepts/song/operation/create.rb +5 -1
- data/test/rails-app/app/concepts/song/view/create.erb +1 -0
- data/test/rails-app/app/concepts/song/view/new.erb +1 -0
- data/test/rails-app/app/controllers/api/v1/songs_controller.rb +13 -7
- data/test/rails-app/app/controllers/application_controller/api.rb +3 -1
- data/test/rails-app/app/controllers/application_controller/web.rb +35 -19
- data/test/rails-app/app/controllers/application_controller.rb +3 -1
- data/test/rails-app/app/controllers/auth_controller.rb +44 -0
- data/test/rails-app/app/controllers/home_controller.rb +5 -0
- data/test/rails-app/app/controllers/songs_controller.rb +255 -12
- data/test/rails-app/app/models/song.rb +4 -1
- data/test/rails-app/app/models/user.rb +3 -1
- data/test/rails-app/config/application.rb +1 -1
- data/test/rails-app/config/environments/test.rb +2 -0
- data/test/rails-app/config/routes.rb +21 -3
- data/test/rails-app/test/controllers/api_songs_controller_test.rb +6 -2
- data/test/rails-app/test/controllers/songs_controller_test.rb +125 -10
- data/trailblazer-endpoint.gemspec +2 -2
- metadata +23 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 31aba7b848760da16d7c5ad9a7ee24f821acac0b4fd72884e263a5e52468d868
|
4
|
+
data.tar.gz: 71d47c02a27874bbc9e16d64d2844d6d930888b77b653c582e9ebd5d94960f71
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 24837b2a2fc722d9101b56fe9f35d648f7408574a518bc66fbefcd539ffd4363c44232fb8c51373902782d15986134cecb8d5c811fd504cc6a425ee53d90f8a1
|
7
|
+
data.tar.gz: b25455ecd91f83b3e4bf067b6fbb9e1188d162f894379ec01de91fbb09bff5e38e0517684d1220ef49d7a26a6ab1f477bed554ee8bf407f84950629f7329a435
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
# 0.0.9
|
2
|
+
|
3
|
+
* Fix Ruby 3.0 code.
|
4
|
+
* Update dependencies.
|
5
|
+
|
6
|
+
# 0.0.8
|
7
|
+
|
8
|
+
* Add `Protocol.insert_copy_from_domain_ctx!` to copy from `domain_ctx` to the `endpoint_ctx`.
|
9
|
+
|
10
|
+
# 0.0.7
|
11
|
+
|
12
|
+
* BREAKING: Remove `:domain_ctx_filter` in favor of `Controller.insert_copy_to_domain_ctx!`.
|
13
|
+
* Add support for serializing `:suspend_data` and deserializing `:resume_data` so session data can get automatically encrypted and passed to the next action. This used to sit in `workflow`.
|
14
|
+
* Add `:find_process_model`. This introduces a new protocol step before `policy` to find the "process model" instead of letting the domain operation or even the policy (or both!) find the "current model".
|
15
|
+
|
16
|
+
# 0.0.6
|
17
|
+
|
18
|
+
* `Controller::endpoint` short form introduced.
|
19
|
+
* Minor changes for `Controller.module`.
|
20
|
+
* Lots of cleanups.
|
21
|
+
|
1
22
|
# 0.0.5
|
2
23
|
|
3
24
|
* Removed `Protocol::Failure`. Until we have `Railway::End::Failure`, use a normal `Activity::End` everywhere instead of introducing our own.
|
data/README.md
CHANGED
@@ -1,11 +1,46 @@
|
|
1
1
|
# Trailblazer::Endpoint
|
2
2
|
|
3
|
-
*
|
3
|
+
*Endpoints handle authentication, authorization, calling the business logic and response rendering.*
|
4
4
|
|
5
|
-
|
5
|
+
## Overview
|
6
6
|
|
7
|
-
|
7
|
+
An endpoint links your routing with your business code. The idea is that your controllers are pure HTTP routers, calling the respective endpoint for each action. From there, the endpoint takes over, handles authentication, policies, executing the domain code, interpreting the result, and providing hooks to render a response.
|
8
8
|
|
9
|
-
|
9
|
+
Instead of dealing with a mix of `before_filter`s, Rack-middlewares, controller code and callbacks, an endpoint is just another activity and allows to be customized with the well-established Trailblazer mechanics.
|
10
10
|
|
11
|
-
|
11
|
+
|
12
|
+
In a Rails controller, a controller action could look as follows.
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
class DiagramsController < ApplicationController
|
16
|
+
endpoint Diagram::Operation::Create, [:is_logged_in?, :can_add_diagram?]
|
17
|
+
|
18
|
+
def create
|
19
|
+
endpoint Diagram::Operation::Create do |ctx, **|
|
20
|
+
redirect_to diagram_path(ctx[:diagram].id)
|
21
|
+
end.Or do |ctx, **|
|
22
|
+
render :form
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
While routing and redirecting/rendering still happens in Rails, all remaining steps are handled in the endpoint.
|
29
|
+
|
30
|
+
An API controller action, where the rendering is done generically, could look much simpler.
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
class API::V1::DiagramsController < ApplicationController
|
34
|
+
endpoint Diagram::Operation::Create, [:is_logged_in?, :can_add_diagram?]
|
35
|
+
|
36
|
+
def create
|
37
|
+
endpoint Diagram::Operation::Create
|
38
|
+
end
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
Endpoints are easily customized but their main intent is to reduce fuzzy controller code and providing best practices for both HTML-rendering controllers and APIs.
|
43
|
+
|
44
|
+
## Documentation
|
45
|
+
|
46
|
+
Read the [full documentation for endpoint](https://trailblazer.to/2.1/docs/endpoint.html) on our website.
|
@@ -2,9 +2,9 @@ module Trailblazer
|
|
2
2
|
class Endpoint
|
3
3
|
module Controller
|
4
4
|
def self.extended(extended)
|
5
|
-
extended.extend Trailblazer::Endpoint::Options::DSL
|
5
|
+
extended.extend Trailblazer::Endpoint::Options::DSL # ::directive
|
6
6
|
extended.extend Trailblazer::Endpoint::Options::DSL::Inherit
|
7
|
-
extended.extend Trailblazer::Endpoint::Options
|
7
|
+
extended.extend Trailblazer::Endpoint::Options # ::options_for
|
8
8
|
extended.extend DSL::Endpoint
|
9
9
|
|
10
10
|
extended.include InstanceMethods # {#endpoint_for}
|
@@ -17,26 +17,41 @@ module Trailblazer
|
|
17
17
|
end
|
18
18
|
|
19
19
|
# @experimental
|
20
|
+
# TODO: test application_controller with and without dsl/api
|
21
|
+
|
20
22
|
def self.module(framework: :rails, api: false, dsl: false, application_controller: false)
|
21
|
-
if api
|
23
|
+
if application_controller && !api && !dsl # FIXME: not tested! this is useful for an actual AppController with block_options or flow_options settings, "globally"
|
24
|
+
Module.new do
|
25
|
+
def self.included(includer)
|
26
|
+
includer.extend(Controller) # only ::directive and friends.
|
27
|
+
end
|
28
|
+
end
|
29
|
+
elsif api
|
22
30
|
Module.new do
|
31
|
+
@application_controller = application_controller
|
23
32
|
def self.included(includer)
|
24
|
-
|
25
|
-
|
33
|
+
if @application_controller
|
34
|
+
includer.extend Controller
|
35
|
+
end
|
26
36
|
includer.include(InstanceMethods::API)
|
27
37
|
end
|
28
38
|
end
|
29
|
-
elsif dsl
|
39
|
+
elsif dsl
|
30
40
|
Module.new do
|
41
|
+
@application_controller = application_controller
|
31
42
|
def self.included(includer)
|
32
|
-
|
43
|
+
if @application_controller
|
44
|
+
includer.extend Controller
|
45
|
+
end
|
33
46
|
includer.include Trailblazer::Endpoint::Controller::InstanceMethods::DSL
|
34
47
|
includer.include Trailblazer::Endpoint::Controller::Rails
|
35
48
|
includer.extend Trailblazer::Endpoint::Controller::Rails::DefaultBlocks
|
36
49
|
includer.extend Trailblazer::Endpoint::Controller::Rails::DefaultParams
|
37
50
|
includer.include Trailblazer::Endpoint::Controller::Rails::Process
|
38
51
|
end
|
39
|
-
end
|
52
|
+
end # Module
|
53
|
+
else
|
54
|
+
raise
|
40
55
|
end
|
41
56
|
end
|
42
57
|
|
@@ -77,18 +92,19 @@ module Trailblazer
|
|
77
92
|
extended.directive(:endpoints, ->(*) { {} })
|
78
93
|
end
|
79
94
|
|
80
|
-
|
95
|
+
# Builds and registers an endpoint in a controller class.
|
96
|
+
def endpoint(name=nil, **options, &block)
|
81
97
|
options = options.merge(protocol_block: block) if block_given?
|
82
98
|
|
83
|
-
return generic_endpoint_config(**
|
84
|
-
|
99
|
+
return generic_endpoint_config(**options) if name.nil?
|
100
|
+
|
101
|
+
build_endpoint(name, **options)
|
85
102
|
end
|
86
103
|
|
87
|
-
|
104
|
+
# Configures generic {:adapter}, {:protocol}, etc.
|
105
|
+
def generic_endpoint_config(**options)
|
88
106
|
self.singleton_class.define_method :generic_options do |ctx,**|
|
89
107
|
{
|
90
|
-
protocol: protocol,
|
91
|
-
adapter: adapter,
|
92
108
|
**options
|
93
109
|
}
|
94
110
|
end
|
@@ -96,13 +112,12 @@ module Trailblazer
|
|
96
112
|
directive :generic_options, method(:generic_options) # FIXME: do we need this?
|
97
113
|
end
|
98
114
|
|
99
|
-
def
|
100
|
-
|
101
|
-
build_options = options_for(:generic_options, {}).merge(options) # DISCUSS: why don't we add this as another directive option/step?
|
115
|
+
def build_endpoint(name, domain_activity: name, **options)
|
116
|
+
build_options = options_for(:generic_options, {}).merge(domain_activity: domain_activity, **options) # DISCUSS: why don't we add this as another directive option/step?
|
102
117
|
|
103
|
-
endpoint = Trailblazer::Endpoint.build(build_options)
|
118
|
+
endpoint = Trailblazer::Endpoint.build(**build_options)
|
104
119
|
|
105
|
-
directive :endpoints, ->(*) { {name => endpoint} }
|
120
|
+
directive :endpoints, ->(*) { {name.to_s => endpoint} }
|
106
121
|
end
|
107
122
|
|
108
123
|
end
|
@@ -111,15 +126,33 @@ module Trailblazer
|
|
111
126
|
module InstanceMethods
|
112
127
|
|
113
128
|
def endpoint_for(name, config_source: self.class)
|
114
|
-
config_source.options_for(:endpoints, {}).fetch(name) # TODO: test non-existant endpoint
|
129
|
+
config_source.options_for(:endpoints, {}).fetch(name.to_s) # TODO: test non-existant endpoint
|
115
130
|
end
|
116
131
|
|
117
132
|
module DSL
|
118
133
|
def endpoint(name, **action_options, &block)
|
119
|
-
action_options = {controller: self}.merge(action_options) # FIXME: redundant with {
|
134
|
+
action_options = {controller: self}.merge(action_options) # FIXME: redundant with {API#endpoint}
|
120
135
|
|
121
136
|
endpoint = endpoint_for(name)
|
122
137
|
|
138
|
+
# raise name.inspect unless block_given?
|
139
|
+
# TODO: check {dsl: false}
|
140
|
+
# unless block_given? # FIXME
|
141
|
+
# config_source = self.class # FIXME
|
142
|
+
# block_options = config_source.options_for(:options_for_block_options, **action_options)
|
143
|
+
# block_options = Trailblazer::Endpoint::Options.merge_with(action_options, block_options)
|
144
|
+
|
145
|
+
# signal, (ctx, _) = Trailblazer::Endpoint::Controller.advance_endpoint_for_controller(
|
146
|
+
# endpoint: endpoint,
|
147
|
+
# block_options: block_options,
|
148
|
+
# config_source: config_source,
|
149
|
+
# **action_options
|
150
|
+
# )
|
151
|
+
|
152
|
+
# return ctx
|
153
|
+
# end
|
154
|
+
|
155
|
+
|
123
156
|
invoke_endpoint_with_dsl(endpoint: endpoint, **action_options, &block)
|
124
157
|
end
|
125
158
|
|
@@ -132,7 +165,7 @@ module Trailblazer
|
|
132
165
|
def endpoint(name, config_source: self.class, **action_options)
|
133
166
|
endpoint = endpoint_for(name, config_source: config_source)
|
134
167
|
|
135
|
-
action_options = {controller: self}.merge(action_options) # FIXME: redundant with {
|
168
|
+
action_options = {controller: self}.merge(action_options) # FIXME: redundant with {InstanceMethods#endpoint}
|
136
169
|
|
137
170
|
block_options = config_source.options_for(:options_for_block_options, **action_options)
|
138
171
|
block_options = Trailblazer::Endpoint::Options.merge_with(action_options, block_options)
|
@@ -19,9 +19,11 @@ module Trailblazer
|
|
19
19
|
# #call
|
20
20
|
def to_args(default_block_options)
|
21
21
|
return options,
|
22
|
+
default_block_options.merge( # this adds :invoke.
|
22
23
|
success_block: success_block || default_block_options[:success_block],
|
23
24
|
failure_block: failure_block || default_block_options[:failure_block],
|
24
25
|
protocol_failure_block: protocol_failure_block || default_block_options[:protocol_failure_block]
|
26
|
+
)
|
25
27
|
end
|
26
28
|
end
|
27
29
|
end
|
@@ -67,29 +67,6 @@ module Trailblazer
|
|
67
67
|
target.instance_variable_set(:@config, config)
|
68
68
|
end
|
69
69
|
|
70
|
-
class State < Module
|
71
|
-
def initialize(normalizer, config)
|
72
|
-
@normalizer = normalizer
|
73
|
-
@config = config
|
74
|
-
end
|
75
|
-
|
76
|
-
# called once when extended in {ApplicationController}.
|
77
|
-
def extended(extended)
|
78
|
-
super
|
79
|
-
|
80
|
-
extended.extend(Inherited)
|
81
|
-
Normalizer.add_normalizer!(extended, @normalizer, @config)
|
82
|
-
end
|
83
|
-
|
84
|
-
end
|
85
|
-
module Inherited
|
86
|
-
def inherited(subclass)
|
87
|
-
super
|
88
|
-
|
89
|
-
Normalizer.add_normalizer!(subclass, @normalizer, @config)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
70
|
def self.add(normalizer, directive_name, options)
|
94
71
|
Class.new(normalizer) do
|
95
72
|
options.collect do |callable|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "openssl"
|
2
|
+
|
3
|
+
module Trailblazer
|
4
|
+
class Endpoint::Protocol
|
5
|
+
module Controller
|
6
|
+
module Cipher # FIXME: copied from Tyrant!
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def encrypt_value(ctx, value:, cipher_key:, **)
|
10
|
+
cipher = OpenSSL::Cipher.new('DES-EDE3-CBC').encrypt
|
11
|
+
cipher.key = Digest::SHA1.hexdigest(cipher_key)[0..23] # ArgumentError: key must be 24 bytes
|
12
|
+
s = cipher.update(value) + cipher.final
|
13
|
+
|
14
|
+
ctx[:encrypted_value] = s.unpack('H*')[0].upcase
|
15
|
+
end
|
16
|
+
|
17
|
+
def decrypt_value(ctx, encrypted_value:, cipher_key:, **)
|
18
|
+
cipher = OpenSSL::Cipher.new('DES-EDE3-CBC').decrypt
|
19
|
+
cipher.key = Digest::SHA1.hexdigest(cipher_key)[0..23]
|
20
|
+
s = [encrypted_value].pack("H*").unpack("C*").pack("c*")
|
21
|
+
|
22
|
+
ctx[:decrypted_value] = cipher.update(s) + cipher.final
|
23
|
+
end
|
24
|
+
end # Cipher
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Endpoint::Protocol
|
3
|
+
# Deserialize incoming state.
|
4
|
+
# Serialize outgoing state.
|
5
|
+
# What else?
|
6
|
+
module Controller
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def decrypt?(ctx, encrypted_resume_data:, **)
|
10
|
+
encrypted_resume_data
|
11
|
+
end
|
12
|
+
|
13
|
+
def deserialize_resume_data(ctx, decrypted_value:, **)
|
14
|
+
ctx[:resume_data] = JSON.parse(decrypted_value)
|
15
|
+
end
|
16
|
+
|
17
|
+
def deserialize_process_model?(ctx, process_model_from_resume_data:, **)
|
18
|
+
process_model_from_resume_data
|
19
|
+
end
|
20
|
+
|
21
|
+
def deserialize_process_model_id(ctx, resume_data:, **)
|
22
|
+
ctx[:process_model_id] = resume_data["id"] # DISCUSS: overriding {:process_model_id}?
|
23
|
+
end
|
24
|
+
|
25
|
+
def encrypt?(ctx, domain_ctx:, **)
|
26
|
+
ctx[:suspend_data] = domain_ctx[:suspend_data]
|
27
|
+
end
|
28
|
+
|
29
|
+
def serialize_suspend_data(ctx, suspend_data:, **)
|
30
|
+
ctx[:serialized_suspend_data] = JSON.dump(suspend_data)
|
31
|
+
end
|
32
|
+
|
33
|
+
def copy_suspend_data_to_endpoint_ctx(ctx, domain_ctx:, **)
|
34
|
+
ctx[:suspend_data] = domain_ctx[:suspend_data] # FIXME: use {#insert_copy_from_domain_ctx!}
|
35
|
+
end
|
36
|
+
|
37
|
+
# FIXME: use Model() mechanics.
|
38
|
+
def deserialize_process_model_id_from_resume_data(ctx, resume_data:, **)
|
39
|
+
# DISCUSS: should we warn when overriding an existing {process_model_id}?
|
40
|
+
ctx[:process_model_id] = resume_data["id"] # DISCUSS: overriding {:process_model_id}? # FIXME: stolen from Advance___::Controller
|
41
|
+
end
|
42
|
+
|
43
|
+
def insert_deserialize_steps!(activity, deserialize_before: :policy)
|
44
|
+
activity.module_eval do
|
45
|
+
step Controller.method(:decrypt?), id: :decrypt?, before: deserialize_before # error out if no serialized_resume_data given.
|
46
|
+
step Controller::Cipher.method(:decrypt_value), id: :decrypt,
|
47
|
+
input: {cipher_key: :cipher_key, encrypted_resume_data: :encrypted_value} , before: deserialize_before,
|
48
|
+
# Output(:failure) => Track(:success),
|
49
|
+
Output(:success) => Path(connect_to: Track(:success), track_color: :deserialize, before: deserialize_before) do # usually, Path goes into {policy}
|
50
|
+
|
51
|
+
step Controller.method(:deserialize_resume_data), id: :deserialize_resume_data
|
52
|
+
# DISCUSS: unmarshall?
|
53
|
+
# step Controller.method(:deserialize_process_model_id?), id: :deserialize_process_model_id?, activity.Output(Trailblazer::Activity::Left, :failure) => activity.Id(around_activity_id)
|
54
|
+
# step Controller.method(:deserialize_process_model_id), id: :deserialize_process_model_id
|
55
|
+
|
56
|
+
step ->(*) { true } # FIXME: otherwise we can't insert an element AFTER :deserialize_resume_data
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def insert_serialize_steps!(activity, serialize_after: :domain_activity)
|
62
|
+
activity.module_eval do
|
63
|
+
# FIXME: reverse order for insertion
|
64
|
+
step Controller::Cipher.method(:encrypt_value), id: :encrypt , after: serialize_after,
|
65
|
+
input: {cipher_key: :cipher_key, serialized_suspend_data: :value}, output: {encrypted_value: :encrypted_suspend_data}
|
66
|
+
step Controller.method(:serialize_suspend_data), id: :serialize_suspend_data , after: serialize_after
|
67
|
+
pass Controller.method(:copy_suspend_data_to_endpoint_ctx), id: :copy_suspend_data_to_endpoint_ctx , after: serialize_after
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Insert the "experimental" {find_process_model} steps
|
72
|
+
def insert_find_process_model!(protocol, **options)
|
73
|
+
protocol.module_eval do
|
74
|
+
step Subprocess(FindProcessModel), Output(:failure) => End(:not_found),
|
75
|
+
id: :find_process_model,
|
76
|
+
**options
|
77
|
+
# after: :authenticate
|
78
|
+
end
|
79
|
+
|
80
|
+
insert_copy_to_domain_ctx!(protocol, {:process_model => :model})
|
81
|
+
end
|
82
|
+
|
83
|
+
def insert_copy_to_domain_ctx!(protocol, variables, before: :domain_activity) # FIXME: `:before` untested!
|
84
|
+
variables.each do |original_name, domain_name|
|
85
|
+
protocol.module_eval do
|
86
|
+
pass ->(ctx, domain_ctx:, **) { domain_ctx[domain_name] = ctx[original_name] if ctx.key?(original_name) },
|
87
|
+
id: :"copy_[#{original_name.inspect}]_to_domain_ctx[#{domain_name.inspect}]", before: before
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def insert_copy_from_domain_ctx!(protocol, variables, after: :domain_activity) # FIXME: `:after` untested!
|
93
|
+
variables.each do |domain_name, endpoint_name|
|
94
|
+
protocol.module_eval do
|
95
|
+
pass ->(ctx, domain_ctx:, **) { ctx[endpoint_name] = domain_ctx[domain_name] if domain_ctx.key?(domain_name) },
|
96
|
+
id: :"copy_[#{endpoint_name.inspect}]_from_domain_ctx[#{domain_name.inspect}]", after: after
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Trailblazer::Endpoint::Protocol
|
2
|
+
class FindProcessModel < Trailblazer::Activity::Railway
|
3
|
+
# step :find_process_model?, Output(:failure) => Id("End.success")
|
4
|
+
step :find_process_model#, Output(:failure) => End(:not_found) # DISCUSS: currently, {End.failure} implies {not_found}.
|
5
|
+
|
6
|
+
# DISCUSS: should the implementation remain in {Activity}?
|
7
|
+
# def find_process_model?(ctx, find_process_model:, **)
|
8
|
+
# find_process_model
|
9
|
+
# end
|
10
|
+
|
11
|
+
def find_process_model(ctx, process_model_class:, process_model_id:, **)
|
12
|
+
ctx[:process_model] = process_model_class.find_by(id: process_model_id)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -24,7 +24,7 @@ module Trailblazer
|
|
24
24
|
|
25
25
|
step :authenticate, Output(:failure) => _Path(semantic: :not_authenticated) do
|
26
26
|
# step :handle_not_authenticated
|
27
|
-
|
27
|
+
end
|
28
28
|
|
29
29
|
step :policy, Output(:failure) => _Path(semantic: :not_authorized) do # user from cookie, etc
|
30
30
|
# step :handle_not_authorized
|
@@ -113,7 +113,6 @@ module Trailblazer
|
|
113
113
|
[[Trailblazer::Activity::TaskWrap::Pipeline.method(:insert_after), "task_wrap.call_task", ["endpoint.end_signal", method(:terminus_handler)]]]
|
114
114
|
end
|
115
115
|
end
|
116
|
-
|
117
|
-
end
|
116
|
+
end # Protocol
|
118
117
|
end
|
119
118
|
end
|
data/lib/trailblazer/endpoint.rb
CHANGED
@@ -2,7 +2,12 @@ module Trailblazer
|
|
2
2
|
class Endpoint
|
3
3
|
# Create an {Endpoint} class with the provided adapter and protocol.
|
4
4
|
# This builder also sets up taskWrap filters around the {domain_activity} execution.
|
5
|
-
def self.build(protocol:, adapter:, domain_activity:, scope_domain_ctx: true,
|
5
|
+
def self.build(protocol:, adapter:, domain_activity:, scope_domain_ctx: true, protocol_block: ->(*) { Hash.new },
|
6
|
+
serialize: false, # TODO: plug-in, not hardcoded!
|
7
|
+
deserialize: false,# TODO: plug-in, not hardcoded!
|
8
|
+
find_process_model: false, # TODO: plug-in, not hardcoded!
|
9
|
+
deserialize_process_model_id_from_resume_data: false # TODO: plug-in, not hardcoded!
|
10
|
+
)
|
6
11
|
# special considerations around the {domain_activity} and its taskWrap:
|
7
12
|
#
|
8
13
|
# 1. domain_ctx_filter (e.g. to filter {current_user})
|
@@ -19,14 +24,41 @@ module Trailblazer
|
|
19
24
|
# scoping: {:domain_ctx} becomes ctx
|
20
25
|
extensions_options.merge!(Endpoint.options_for_scope_domain_ctx) if scope_domain_ctx # TODO: test flag
|
21
26
|
|
27
|
+
app_protocol = build_protocol(protocol, domain_activity: domain_activity, extensions_options: extensions_options, protocol_block: protocol_block, serialize: serialize, deserialize: deserialize,
|
28
|
+
find_process_model: find_process_model, deserialize_process_model_id_from_resume_data: deserialize_process_model_id_from_resume_data
|
29
|
+
)
|
30
|
+
|
31
|
+
# puts Trailblazer::Developer.render(app_protocol)
|
32
|
+
|
33
|
+
Class.new(adapter) do
|
34
|
+
step(Subprocess(app_protocol), {inherit: true, id: :protocol, replace: :protocol})
|
35
|
+
end # app_adapter
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
# @private
|
40
|
+
def self.build_protocol(protocol, domain_activity:, extensions_options:, protocol_block:, serialize:, deserialize:, find_process_model:, deserialize_process_model_id_from_resume_data:)
|
41
|
+
Class.new(protocol) do
|
42
|
+
if serialize
|
43
|
+
Protocol::Controller.insert_serialize_steps!(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
if deserialize
|
47
|
+
Protocol::Controller.insert_deserialize_steps!(self)
|
48
|
+
end
|
22
49
|
|
23
|
-
|
24
|
-
|
50
|
+
if serialize || deserialize
|
51
|
+
Protocol::Controller.insert_copy_to_domain_ctx!(self, {:resume_data => :resume_data})
|
52
|
+
end
|
25
53
|
|
26
|
-
|
27
|
-
|
54
|
+
if find_process_model
|
55
|
+
Protocol::Controller.insert_find_process_model!(self, before: :policy) # TODO: test before: :policy
|
56
|
+
end
|
57
|
+
|
58
|
+
if deserialize_process_model_id_from_resume_data
|
59
|
+
pass Protocol::Controller.method(:deserialize_process_model_id_from_resume_data), after: :deserialize_resume_data, magnetic_to: :deserialize, Output(:success) => Track(:deserialize)
|
60
|
+
end
|
28
61
|
|
29
|
-
app_protocol = Class.new(protocol) do
|
30
62
|
step(Subprocess(domain_activity), {inherit: true, id: :domain_activity, replace: :domain_activity,
|
31
63
|
|
32
64
|
# FIXME: where does this go?
|
@@ -35,13 +67,6 @@ module Trailblazer
|
|
35
67
|
merge(instance_exec(&protocol_block)) # the block is evaluated in the {Protocol} context.
|
36
68
|
)
|
37
69
|
end
|
38
|
-
|
39
|
-
# puts Trailblazer::Developer.render(app_protocol)
|
40
|
-
|
41
|
-
Class.new(adapter) do
|
42
|
-
step(Subprocess(app_protocol), {inherit: true, id: :protocol, replace: :protocol})
|
43
|
-
end # app_adapter
|
44
|
-
|
45
70
|
end
|
46
71
|
|
47
72
|
def self.options_for_scope_domain_ctx()
|
@@ -51,19 +76,11 @@ module Trailblazer
|
|
51
76
|
}
|
52
77
|
end
|
53
78
|
|
54
|
-
def self.domain_ctx_filter(variables)
|
55
|
-
->(_ctx, ((ctx, a), b)) do # taskWrap interface
|
56
|
-
variables.each do |variable|
|
57
|
-
ctx[:domain_ctx][variable] = ctx[variable]
|
58
|
-
end
|
59
|
-
|
60
|
-
[_ctx, [[ctx, a], b]]
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
79
|
# Runtime
|
65
80
|
# Invokes the endpoint for you and runs one of the three outcome blocks.
|
66
|
-
def self.with_or_etc(activity, args, failure_block:, success_block:, protocol_failure_block:, invoke: Trailblazer::Activity::TaskWrap.method(:invoke))
|
81
|
+
def self.with_or_etc(activity, args, failure_block:, success_block:, protocol_failure_block:, invoke: Trailblazer::Activity::TaskWrap.method(:invoke))
|
82
|
+
# def self.with_or_etc(activity, args, failure_block:, success_block:, protocol_failure_block:, invoke: Trailblazer::Developer.method(:wtf?))
|
83
|
+
|
67
84
|
# args[1] = args[1].merge(focus_on: { variables: [:returned], steps: :invoke_workflow })
|
68
85
|
|
69
86
|
# signal, (endpoint_ctx, _ ) = Trailblazer::Developer.wtf?(activity, args)
|
@@ -114,7 +131,7 @@ module Trailblazer
|
|
114
131
|
end
|
115
132
|
|
116
133
|
# FIXME: name will change! this is for controllers, only!
|
117
|
-
def self.advance_from_controller(endpoint, success_block:, failure_block:, protocol_failure_block
|
134
|
+
def self.advance_from_controller(endpoint, success_block:, failure_block:, protocol_failure_block:, **argument_options)
|
118
135
|
args = Trailblazer::Endpoint.arguments_for(argument_options)
|
119
136
|
|
120
137
|
signal, (ctx, _ ) = Trailblazer::Endpoint.with_or_etc(
|
@@ -144,3 +161,6 @@ require "trailblazer/endpoint/adapter"
|
|
144
161
|
require "trailblazer/endpoint/dsl"
|
145
162
|
require "trailblazer/endpoint/controller"
|
146
163
|
require "trailblazer/endpoint/options"
|
164
|
+
require "trailblazer/endpoint/protocol/controller"
|
165
|
+
require "trailblazer/endpoint/protocol/find_process_model"
|
166
|
+
require "trailblazer/endpoint/protocol/cipher"
|
@@ -58,7 +58,8 @@ class DocsControllerTest < Minitest::Spec
|
|
58
58
|
include T.def_steps(:authenticate, :policy)
|
59
59
|
end
|
60
60
|
|
61
|
-
endpoint protocol: Protocol, adapter: Trailblazer::Endpoint::Adapter::Web,
|
61
|
+
endpoint protocol: Protocol, adapter: Trailblazer::Endpoint::Adapter::Web,
|
62
|
+
scope_domain_ctx: true
|
62
63
|
end
|
63
64
|
|
64
65
|
class HtmlController < ApplicationController
|
@@ -311,4 +312,63 @@ class DocsControllerTest < Minitest::Spec
|
|
311
312
|
end
|
312
313
|
end
|
313
314
|
|
315
|
+
class ControllerEndpointMethodTest < Minitest::Spec
|
316
|
+
# Test {Controller::endpoint}
|
317
|
+
|
318
|
+
class Protocol < Trailblazer::Endpoint::Protocol
|
319
|
+
def policy(*); true; end
|
320
|
+
def authenticate(*); true; end
|
321
|
+
end
|
322
|
+
|
323
|
+
class BasicController
|
324
|
+
include Trailblazer::Endpoint::Controller.module(api: true, application_controller: true)
|
325
|
+
|
326
|
+
directive :options_for_block_options, Trailblazer::Endpoint::Controller.method(:options_for_block_options)
|
327
|
+
|
328
|
+
endpoint protocol: Protocol, adapter: Trailblazer::Endpoint::Adapter::Web
|
329
|
+
|
330
|
+
def head(status)
|
331
|
+
@status = status
|
332
|
+
end
|
333
|
+
|
334
|
+
def self.options_for_block_options(ctx, controller:, **)
|
335
|
+
{
|
336
|
+
success_block: ->(ctx, endpoint_ctx:, **) { controller.head("#{ctx[:op]}") },
|
337
|
+
failure_block: ->(ctx, status:, **) { },
|
338
|
+
protocol_failure_block: ->(ctx, status:, **) { }
|
339
|
+
}
|
340
|
+
end
|
341
|
+
|
342
|
+
directive :options_for_block_options, method(:options_for_block_options)
|
343
|
+
end
|
344
|
+
|
345
|
+
class RodaController < BasicController
|
346
|
+
class Create < Trailblazer::Activity::Railway
|
347
|
+
def save(ctx, **); ctx[:op] = self.class; end;
|
348
|
+
step :save
|
349
|
+
end
|
350
|
+
class Update < Create
|
351
|
+
end
|
352
|
+
|
353
|
+
# {Controller::endpoint}: {:domain_activity} defaults to {name} when not given
|
354
|
+
endpoint Create # class {name}s are ok
|
355
|
+
endpoint :update, domain_activity: Update # symbol {name} is ok
|
356
|
+
|
357
|
+
def show
|
358
|
+
endpoint Create
|
359
|
+
@status
|
360
|
+
end
|
361
|
+
|
362
|
+
def update
|
363
|
+
endpoint :update
|
364
|
+
@status
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
|
369
|
+
it "what" do
|
370
|
+
RodaController.new.show.must_equal %{ControllerEndpointMethodTest::RodaController::Create}
|
371
|
+
RodaController.new.update.must_equal %{ControllerEndpointMethodTest::RodaController::Update}
|
372
|
+
end
|
373
|
+
end
|
314
374
|
|
data/test/rails-app/Gemfile
CHANGED
@@ -13,7 +13,23 @@ gem 'sqlite3', '~> 1.4'
|
|
13
13
|
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
|
14
14
|
# gem 'rack-cors'
|
15
15
|
|
16
|
-
gem "trailblazer-operation"
|
16
|
+
# gem "trailblazer-operation"
|
17
|
+
gem "trailblazer-operation", path: "../../../trailblazer-operation"
|
18
|
+
gem "trailblazer-context", path: "../../../trailblazer-context"
|
19
|
+
gem "trailblazer-activity-dsl-linear", path: "../../../trailblazer-activity-dsl-linear"
|
20
|
+
gem "trailblazer-activity", path: "../../../trailblazer-activity"
|
21
|
+
|
22
|
+
|
17
23
|
gem "trailblazer-endpoint", path: "../../."
|
18
24
|
gem "jwt"
|
19
25
|
gem "multi_json"
|
26
|
+
gem "minitest-line"
|
27
|
+
gem "trailblazer-cells"
|
28
|
+
gem "cells-erb"
|
29
|
+
gem "cells-rails"
|
30
|
+
|
31
|
+
|
32
|
+
# FIXME
|
33
|
+
# gem "trailblazer-workflow", path: "../../../trailblazer-workflow"
|
34
|
+
# gem "trailblazer-activity-dsl-linear", path: "../../../trailblazer-activity-dsl-linear"
|
35
|
+
# gem "trailblazer-activity-dsl-linear", ">= 0.3.4"
|