activefunction 0.3.5 → 0.4.1
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/CHANGELOG.md +10 -1
- data/README.md +182 -69
- data/lib/.rbnext/2.7/active_function/functions/strong_parameters.rb +84 -41
- data/lib/.rbnext/3.0/active_function/base.rb +58 -0
- data/lib/.rbnext/3.0/active_function/functions/response.rb +38 -19
- data/lib/.rbnext/3.0/active_function.rb +63 -0
- data/lib/.rbnext/3.1/active_function/functions/response.rb +49 -0
- data/lib/active_function/base.rb +52 -14
- data/lib/active_function/functions/callbacks.rb +58 -58
- data/lib/active_function/functions/rendering.rb +36 -9
- data/lib/active_function/functions/response.rb +38 -19
- data/lib/active_function/functions/strong_parameters.rb +84 -41
- data/lib/active_function/version.rb +1 -1
- data/lib/active_function.rb +54 -3
- metadata +55 -26
- data/bin/console +0 -15
- data/bin/rake +0 -27
- data/bin/rubocop +0 -27
- data/bin/ruby-next +0 -16
- data/bin/setup +0 -8
- data/lib/.rbnext/3.0/active_function/functions/core.rb +0 -49
- data/lib/active_function/functions/core.rb +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 777be794ab7e9eb091472b8f886b0b8da55c338c6c617afb9ee95acc9a946cba
|
4
|
+
data.tar.gz: faf687684919e61cd2bb9a2b8217f029945971898ae4469593f166d7ab34508a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20bb5768a3dcf00582d6f32b99c600c51a9e83c9e5b7ef78ecf81c7e560d63f7d83ea02005d01ef19cd4e2a87a5161f47627e7c305c45d2c18dab6744cd5134a
|
7
|
+
data.tar.gz: 7e866bc6a9fe14cd56b8f08985a2a83ce10e57c54560e54e3c954116508638dc0128878d0dc6b2d16f7c8c2c11775ba2681515a58229632bc752e20b76fd79dd
|
data/CHANGELOG.md
CHANGED
@@ -46,4 +46,13 @@
|
|
46
46
|
|
47
47
|
- Start gem restructuring for modularizaition
|
48
48
|
- Introduce `activefunction-core` gem with RubyNext integration
|
49
|
-
|
49
|
+
|
50
|
+
# [0.4.0] - 2024-01-11
|
51
|
+
|
52
|
+
- Replace `ActiveFunction::Functions::Callbacks` with `ActiveFunctionCore::Plugins::Hooks`
|
53
|
+
- Introduce Plugin system
|
54
|
+
- Global refactoring
|
55
|
+
|
56
|
+
# [0.4.1] - 2024-01-14
|
57
|
+
|
58
|
+
- YARD Doc Added
|
data/README.md
CHANGED
@@ -1,146 +1,259 @@
|
|
1
1
|
# ActiveFunction
|
2
|
+
[](DanilMaximov/activefunction/actions)
|
3
|
+
[](https://badge.fury.io/rb/activefunction)
|
4
|
+
[](https://rubydoc.info/gems/activefunction)
|
2
5
|
|
3
|
-
rails/action_controller like gem which provides lightweight callbacks, strong parameters & rendering features. It's designed to be used with AWS Lambda functions, but can be also used with any Ruby application.
|
4
6
|
|
5
|
-
|
7
|
+
Playground gem for Ruby 3.2+ features initially designed to be used with FaaS (Function as a Service) computing instances. Inspired by aws-sdk v3 gem structure & rails/activesupport.
|
6
8
|
|
9
|
+
## Features
|
7
10
|
|
11
|
+
- **Ruby Version Compatibility:** Implemented with most of ruby 3.2+ features, BUT supports Ruby versions >= 2.6 through the use of the [RubyNext](https://github.com/ruby-next/ruby-next) transpiler. (CI'ed)
|
12
|
+
- **Type Safety:** Achieves type safety through the use of RBS and [Steep](https://github.com/soutaro/steep). (CI'ed)
|
13
|
+
- Note: disabled due to the presence of Ruby::UnsupportedSyntax errors.
|
14
|
+
- **Plugins System:** Provides a simple Plugin system (inspired by [Polishing Ruby Programming by Jeremy Evans](https://github.com/PacktPublishing/Polished-Ruby-Programming)) to load gem plugins as well as self-defined plugins.
|
15
|
+
- **Gem Collection:** Provides a collection of gems designed to be used within ActiveFunction or standalone.
|
8
16
|
|
9
|
-
|
17
|
+
# Gems
|
10
18
|
|
11
|
-
|
19
|
+
- [activefunction](/) - Main gem, provides rails/action-controller like API with callbacks, strong parameters and rendering under plugins.
|
20
|
+
- [activefunction-core](/gems/activefunction-core/README.md) - Provides RubyNext integration and External Standalone Plugins
|
21
|
+
- activefunction-orm - WIP (ORM around AWS PartiQL)
|
22
|
+
|
23
|
+
## Quick Start
|
24
|
+
|
25
|
+
Here's a simple example of a function that uses ActiveFunction(w/o plugins):
|
12
26
|
|
13
27
|
```ruby
|
14
|
-
require
|
28
|
+
require "active_function"
|
15
29
|
|
16
30
|
class AppFunction < ActiveFunction::Base
|
17
|
-
def index
|
18
|
-
|
19
|
-
|
31
|
+
def index
|
32
|
+
response_with_error if @request[:data].nil?
|
33
|
+
|
34
|
+
return if performed?
|
35
|
+
|
36
|
+
@response.body = @request[:data]
|
37
|
+
end
|
38
|
+
|
39
|
+
private def response_with_error
|
40
|
+
@response.status = 400
|
41
|
+
@response.commit!
|
42
|
+
end
|
20
43
|
end
|
44
|
+
|
21
45
|
```
|
22
46
|
|
23
|
-
|
47
|
+
The `#process` method is used to run the function.
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
AppFunction.process(:index, {data: {id: 1}}) # => {
|
51
|
+
# :statusCode => 200,
|
52
|
+
# :headers => { },
|
53
|
+
# :body => {id: 1}"
|
54
|
+
# }
|
55
|
+
```
|
24
56
|
|
25
|
-
|
26
|
-
|
57
|
+
## Plugins
|
58
|
+
|
59
|
+
ActiveFunction supports plugins which can be loaded by `ActiveFunction.config` method. Currently, there are 3 plugins available:
|
60
|
+
- [:callbacks](#callbacks) - provides `:before_action`, `:after_action` & `:set_callback` DSL with `:if`, `:unless` & `:only` options.
|
61
|
+
- [:strong_parameters](#strong-parameters) - provides strong parameters support via `#params` instance method around `@request` object.
|
62
|
+
- [:rendering](#rendering) - provides rendering support via `#render` instance method around `@response` object.
|
63
|
+
|
64
|
+
## Configuration
|
65
|
+
|
66
|
+
To configure ActiveFunction with plugins, use the `ActiveFunction.config` method.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
# config/initializers/active_function.rb
|
70
|
+
require "active_function"
|
71
|
+
|
72
|
+
ActiveFunction.config do
|
73
|
+
plugin :callbacks
|
74
|
+
plugin :strong_parameters
|
75
|
+
plugin :rendering
|
76
|
+
end
|
27
77
|
```
|
28
|
-
Also check extended [example](https://github.com/DanilMaximov/activefunction/tree/master/active_function_example)
|
29
|
-
## Callbacks
|
30
|
-
ActiveFunction supports simple callbacks `:before` and `:after` which runs around provided action in `#process`.
|
31
78
|
|
32
79
|
```ruby
|
80
|
+
# app/functions/app_function.rb
|
33
81
|
class AppFunction < ActiveFunction::Base
|
34
|
-
before_action :
|
35
|
-
after_action :log_response
|
36
|
-
|
37
|
-
# some action ...
|
82
|
+
before_action :parse_user_data
|
38
83
|
|
39
|
-
|
84
|
+
def index
|
85
|
+
render json: @user_data
|
86
|
+
end
|
40
87
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
88
|
+
private def parse_user_data = @user_data = params.require(:data).permit(:id, :name).to_h
|
89
|
+
end
|
90
|
+
|
91
|
+
AppFunction.process(:index, {data: { id: 1, name: 2}}) # => {
|
92
|
+
# :statusCode => 200,
|
93
|
+
# :headers => {"Content-Type"=>"application/json"},
|
94
|
+
# :body=>"{\"id\":1,\"name\":2}"
|
95
|
+
# }
|
96
|
+
```
|
97
|
+
|
98
|
+
[See Plugins Docs](https://rubydoc.info/gems/activefunction/ActiveFunction#plugin-class_method) for more details.
|
99
|
+
|
100
|
+
## Callbacks
|
101
|
+
|
102
|
+
Simple callbacks engined by [ActiveFunctionCore::Plugins::Hooks](https://github.com/DanilMaximov/activefunction/tree/master/gems/activefunction-core#hooks) external plugin and provides `:before_action` and `:after_action` which runs around provided action in `#process`.
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
require "active_function"
|
106
|
+
|
107
|
+
ActiveFunction.config do
|
108
|
+
plugin :callbacks
|
109
|
+
end
|
44
110
|
|
45
|
-
|
46
|
-
|
111
|
+
class AppFunction < ActiveFunction::Base
|
112
|
+
before_action :set_user
|
113
|
+
after_action :log_response
|
114
|
+
|
115
|
+
def index
|
116
|
+
# some actions ...
|
47
117
|
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def set_user = @_user ||= User.find(@request[:id])
|
122
|
+
def log_response = Logger.info(@response)
|
48
123
|
end
|
49
124
|
```
|
50
125
|
|
51
|
-
Callbacks
|
126
|
+
### Callbacks options
|
127
|
+
|
128
|
+
Supports default [ActiveFunctionCore::Plugins::Hooks::Hook::Callback options](https://github.com/DanilMaximov/activefunction/tree/master/gems/activefunction-core#options) `:if => Symbol` & `:unless => Symbol` options.
|
129
|
+
|
130
|
+
Support custom defined in ActiveFunction::Function::Callbacks `only: Array[Symbol]` option.
|
52
131
|
|
53
132
|
```ruby
|
54
133
|
class AppFunction < ActiveFunction::Base
|
55
134
|
before_action :set_user, only: %i[show update destroy], if: :request_valid?
|
56
|
-
|
135
|
+
|
57
136
|
# some actions ...
|
58
|
-
|
137
|
+
|
59
138
|
private def request_valid? = true
|
60
139
|
end
|
61
140
|
```
|
62
141
|
|
63
|
-
|
142
|
+
### Defining Custom Callbacks
|
143
|
+
|
144
|
+
External Plugin `ActiveFunctionCore::Plugins::Hooks` provides `:define_hooks_for` & `:set_callback_options` DSL to define custom callbacks & options.
|
145
|
+
|
64
146
|
```ruby
|
65
|
-
class
|
66
|
-
|
147
|
+
class MessagingApp < ActiveFunction::Base
|
148
|
+
set_callback_options retries: ->(times, context:) { context.retry if context.retries < times }
|
149
|
+
define_hooks_for :retry
|
67
150
|
|
68
|
-
|
69
|
-
|
70
|
-
end
|
71
|
-
end
|
151
|
+
after_action :retry, if: :failed?, only: %i[send_message], retries: 3
|
152
|
+
after_retry :increment_retries
|
72
153
|
|
73
|
-
|
74
|
-
|
75
|
-
|
154
|
+
def send_message
|
155
|
+
@response.status = 200 if SomeApi.send(@request[:message_content]).success?
|
156
|
+
end
|
157
|
+
|
158
|
+
def retry
|
159
|
+
@response.committed = false
|
160
|
+
|
161
|
+
process
|
76
162
|
end
|
163
|
+
|
164
|
+
private def increment_retries = @response.body[:tries] += 1
|
165
|
+
private def failed? = @response.status != 200
|
166
|
+
private def retries = @response.body[:tries] ||= 0
|
77
167
|
end
|
168
|
+
|
169
|
+
MessagingApp.process(:send_message, { sender_name: "Alice", message_content: "How are you?" })
|
78
170
|
```
|
171
|
+
|
172
|
+
[See Callbacks Doc](https://rubydoc.info/gems/activefunction/ActiveFunction/Functions/Callbacks) for more details.
|
173
|
+
|
79
174
|
## Strong Parameters
|
80
|
-
ActiveFunction supports strong parameters which can be accessed by `#params` instance method. Strong parameters hash can be passed in `#process` as second argument.
|
81
175
|
|
82
|
-
|
83
|
-
|
84
|
-
|
176
|
+
ActiveFunction supports strong parameters which can be accessed by `#params` instance method.
|
177
|
+
|
178
|
+
The `#params` method represents a Ruby 3.2 Data class that allows the manipulation of request parameters. It supports the following methods:
|
179
|
+
|
180
|
+
- `[]`: Access parameters by key.
|
181
|
+
- `permit`: Specify the allowed parameters.
|
182
|
+
- `require`: Ensure the presence of a specific parameter.
|
183
|
+
- `to_h`: Convert the parameters to a hash.
|
184
|
+
|
185
|
+
Usage Example:
|
85
186
|
|
86
|
-
Simple usage:
|
87
187
|
```ruby
|
188
|
+
require "active_function"
|
189
|
+
|
190
|
+
ActiveFunction.config do
|
191
|
+
plugin :strong_parameters
|
192
|
+
end
|
193
|
+
|
88
194
|
class PostsFunction < ActiveFunction::Base
|
89
|
-
def index
|
90
|
-
|
91
|
-
end
|
195
|
+
def index
|
196
|
+
@response.body = permitted_params
|
197
|
+
end
|
92
198
|
|
93
199
|
def permitted_params = params
|
94
200
|
.require(:data)
|
95
201
|
.permit(:id, :name)
|
96
202
|
.to_h
|
97
|
-
end
|
203
|
+
end
|
204
|
+
|
205
|
+
PostFunction.process(:index, data: {id: 1, name: "Pupa"})
|
98
206
|
```
|
207
|
+
|
99
208
|
Strong params supports nested attributes
|
100
|
-
```ruby
|
209
|
+
```ruby
|
101
210
|
params.permit(:id, :name, :address => [:city, :street])
|
102
211
|
```
|
103
212
|
|
213
|
+
[See StrongParameters Doc](https://rubydoc.info/gems/activefunction/ActiveFunction/Functions/StrongParameters) for more details.
|
214
|
+
|
104
215
|
## Rendering
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
216
|
+
|
217
|
+
ActiveFunction supports rendering of JSON. The #render method is used for rendering responses. It accepts the following options:
|
218
|
+
|
219
|
+
- `head`: Set response headers. Defaults to `{ "Content-Type" => "application/json" }`.
|
220
|
+
- `json`: Set the response body with a JSON-like object. Defaults to `{}`.
|
221
|
+
- `status`: Set the HTTP status code. Defaults to `200`.
|
222
|
+
|
223
|
+
Additionally, the method automatically commits the response and JSONifies the body.
|
224
|
+
|
225
|
+
Usage Example:
|
114
226
|
```ruby
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
227
|
+
require "active_function"
|
228
|
+
|
229
|
+
ActiveFunction.config do
|
230
|
+
plugin :rendering
|
119
231
|
end
|
120
|
-
|
121
|
-
Headers can be passed by `:headers` option. Default headers are `{"Content-Type" => "application/json"}`.
|
122
|
-
```ruby
|
232
|
+
|
123
233
|
class PostsFunction < ActiveFunction::Base
|
124
|
-
def index
|
125
|
-
render json: {
|
126
|
-
end
|
234
|
+
def index
|
235
|
+
render json: {id: 1, name: "Pupa"}, status: 200, head: {"Some-Header" => "Some-Value"}
|
236
|
+
end
|
127
237
|
end
|
238
|
+
|
239
|
+
PostFunction.process(:index) # => { :statusCode=>200, :headers=> {"Content-Type"=>"application/json", "Some-Header" => "Some-Value"}, :body=>"{\"id\":1,\"name\":\"Pupa\"}"}
|
128
240
|
```
|
129
241
|
|
242
|
+
[See Rendering Doc](https://rubydoc.info/gems/activefunction/ActiveFunction/Functions/Rendering) for more details.
|
130
243
|
|
131
244
|
## Installation
|
132
245
|
|
133
246
|
Add this line to your application's Gemfile:
|
134
247
|
|
135
248
|
```ruby
|
136
|
-
gem
|
249
|
+
gem "activefunction"
|
137
250
|
```
|
138
251
|
|
139
252
|
## Development
|
140
253
|
|
141
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/rake test` to run the tests and `bin/rake steep` to run type checker.
|
254
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/rake test:all` to run the tests and `bin/rake steep` to run type checker.
|
142
255
|
|
143
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
256
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
144
257
|
|
145
258
|
## Contributing
|
146
259
|
|
@@ -3,54 +3,80 @@
|
|
3
3
|
require "forwardable"
|
4
4
|
|
5
5
|
module ActiveFunction
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
module Functions
|
7
|
+
# Allows manipulations with {ActiveFunction::SuperBase#request} via {params} instance method and {Parameters} object.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# require "active_function"
|
11
|
+
#
|
12
|
+
# ActiveFunction.config do
|
13
|
+
# plugin :strong_parameters
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# class PostsFunction < ActiveFunction::Base
|
17
|
+
# def index
|
18
|
+
# @response.body = permitted_params
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# def permitted_params
|
22
|
+
# params.require(:data).permit(:id, :name).to_h
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# PostsFunction.process(:index, data: { id: 1, name: "Pupa" })
|
27
|
+
module StrongParameters
|
28
|
+
ActiveFunction.register_plugin :strong_parameters, self
|
15
29
|
|
16
|
-
|
17
|
-
|
30
|
+
Error = Class.new(StandardError)
|
31
|
+
# The Parameters class encapsulates the parameter handling logic.
|
32
|
+
class Parameters < Data.define(:params, :permitted)
|
33
|
+
class ParameterMissingError < Error
|
34
|
+
MESSAGE_TEMPLATE = "Missing parameter: %s"
|
18
35
|
|
19
|
-
|
36
|
+
attr_reader :message
|
20
37
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
38
|
+
def initialize(param)
|
39
|
+
MESSAGE_TEMPLATE % param
|
40
|
+
end
|
41
|
+
end
|
25
42
|
|
26
|
-
|
27
|
-
|
28
|
-
def params
|
29
|
-
@_params ||= Parameters.new(@request)
|
30
|
-
end
|
43
|
+
class UnpermittedParameterError < Error
|
44
|
+
MESSAGE_TEMPLATE = "Unpermitted parameter: %s"
|
31
45
|
|
32
|
-
|
33
|
-
extend Forwardable
|
34
|
-
def_delegators :@parameters, :each, :map
|
35
|
-
include Enumerable
|
46
|
+
attr_reader :message
|
36
47
|
|
37
|
-
|
38
|
-
|
39
|
-
|
48
|
+
def initialize(param)
|
49
|
+
MESSAGE_TEMPLATE % param
|
50
|
+
end
|
40
51
|
end
|
41
52
|
|
53
|
+
protected :params
|
54
|
+
|
55
|
+
# Allows access to parameters by key.
|
56
|
+
#
|
57
|
+
# @param attribute [Symbol] The key of the parameter.
|
58
|
+
# @return [Parameters, Object] The value of the parameter.
|
42
59
|
def [](attribute)
|
43
|
-
nested_attribute(
|
60
|
+
nested_attribute(params[attribute])
|
44
61
|
end
|
45
62
|
|
63
|
+
# Requires the presence of a specific parameter.
|
64
|
+
#
|
65
|
+
# @param attribute [Symbol] The key of the required parameter.
|
66
|
+
# @return [Parameters, Object] The value of the required parameter.
|
67
|
+
# @raise [ParameterMissingError] if the required parameter is missing.
|
46
68
|
def require(attribute)
|
47
|
-
value = self[attribute]
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
69
|
+
if (value = self[attribute])
|
70
|
+
value
|
71
|
+
else
|
72
|
+
raise ParameterMissingError, attribute
|
73
|
+
end
|
52
74
|
end
|
53
75
|
|
76
|
+
# Specifies the allowed parameters.
|
77
|
+
#
|
78
|
+
# @param attributes [Array<Symbol, Hash<Symbol, Array<Symbol>>>] The attributes to permit.
|
79
|
+
# @return [Parameters] A new instance with permitted parameters.
|
54
80
|
def permit(*attributes)
|
55
81
|
pparams = {}
|
56
82
|
|
@@ -60,28 +86,40 @@ module ActiveFunction
|
|
60
86
|
pparams[k] = process_nested(self[k], :permit, v)
|
61
87
|
end
|
62
88
|
else
|
63
|
-
next unless
|
89
|
+
next unless params.key?(attribute)
|
64
90
|
|
65
91
|
pparams[attribute] = self[attribute]
|
66
92
|
end
|
67
93
|
end
|
68
94
|
|
69
|
-
|
95
|
+
with(params: pparams, permitted: true)
|
70
96
|
end
|
71
97
|
|
98
|
+
# Converts parameters to a hash.
|
99
|
+
#
|
100
|
+
# @return [Hash] The hash representation of the parameters.
|
101
|
+
# @raise [UnpermittedParameterError] if any parameters are unpermitted.
|
72
102
|
def to_h
|
73
|
-
raise UnpermittedParameterError,
|
103
|
+
raise UnpermittedParameterError, params.keys unless permitted
|
74
104
|
|
75
|
-
|
105
|
+
params.transform_values { |_1| process_nested(_1, :to_h) }
|
106
|
+
end
|
107
|
+
|
108
|
+
def hash
|
109
|
+
@attributes.to_h.hash
|
110
|
+
end
|
111
|
+
|
112
|
+
def with(params:, permitted: false)
|
113
|
+
self.class.new(params, permitted)
|
76
114
|
end
|
77
115
|
|
78
116
|
private
|
79
117
|
|
80
118
|
def nested_attribute(attribute)
|
81
119
|
if attribute.is_a? Hash
|
82
|
-
|
120
|
+
with(params: attribute)
|
83
121
|
elsif attribute.is_a?(Array) && attribute[0].is_a?(Hash)
|
84
|
-
attribute.map { |
|
122
|
+
attribute.map { |it| with(params: it) }
|
85
123
|
else
|
86
124
|
attribute
|
87
125
|
end
|
@@ -96,8 +134,13 @@ module ActiveFunction
|
|
96
134
|
attribute
|
97
135
|
end
|
98
136
|
end
|
137
|
+
end
|
99
138
|
|
100
|
-
|
139
|
+
# Return params object with {ActiveFunction::SuperBase#request}.
|
140
|
+
#
|
141
|
+
# @return [Parameters] instance of {Parameters} class.
|
142
|
+
def params
|
143
|
+
@_params ||= Parameters.new(@request, false)
|
101
144
|
end
|
102
145
|
end
|
103
146
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveFunction
|
4
|
+
# Abstract base class with request processing logic.
|
5
|
+
class SuperBase
|
6
|
+
attr_reader :action_name, :request, :response
|
7
|
+
|
8
|
+
def initialize(action_name, request, response)
|
9
|
+
@action_name = action_name
|
10
|
+
@request = request
|
11
|
+
@response = response
|
12
|
+
end
|
13
|
+
|
14
|
+
# Executes specified @action_name instance method and returns Hash'ed response object
|
15
|
+
def dispatch
|
16
|
+
process(action_name)
|
17
|
+
|
18
|
+
@response.commit! unless performed?
|
19
|
+
|
20
|
+
@response.to_h
|
21
|
+
end
|
22
|
+
|
23
|
+
def process(action) ; public_send(action); end
|
24
|
+
|
25
|
+
private def performed? ; @response.committed?; end
|
26
|
+
end
|
27
|
+
|
28
|
+
# The main base class for defining functions using the ActiveFunction framework.
|
29
|
+
# Public methods of this class are considered as actions and be proceeded on {ActiveFunction::Base.process} call.
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# class MyFunction < ActiveFunction::Base
|
33
|
+
# def index
|
34
|
+
# if user = User.find(@request.dig(:data, :user, :id))
|
35
|
+
# @response.body = user.to_h
|
36
|
+
# else
|
37
|
+
# @response.status = 404
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
class Base < SuperBase
|
42
|
+
Error = Class.new(StandardError)
|
43
|
+
|
44
|
+
# Processes specified action and returns Hash'ed {ActiveFunction::Functions::Response::Response} object.
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# MyFunction.process :index, { data: { user: { id: 1 } } } # => { statusCode: 200, body: { id: 1, name: "Pupa" }, headers: {} }
|
48
|
+
#
|
49
|
+
# @param [String, Symbol] action_name - name of method to call
|
50
|
+
# @param [Hash] request - request parameters.
|
51
|
+
# @param [Response] response - Functions::Response response object.
|
52
|
+
def self.process(action_name, request = {}, response = Response.new)
|
53
|
+
raise ArgumentError, "Action method #{action_name} is not defined" unless method_defined?(action_name)
|
54
|
+
|
55
|
+
new(action_name, request, response).dispatch
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -2,29 +2,48 @@
|
|
2
2
|
|
3
3
|
module ActiveFunction
|
4
4
|
module Functions
|
5
|
-
|
6
|
-
|
5
|
+
# The only required plugin for {ActiveFunction::Base} to work.
|
6
|
+
# Provides a simple {Response} object to manage response details.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# response = Response.new.tap do |r|
|
10
|
+
# r.body = "Hello World!"
|
11
|
+
# r.headers = {"Content-Type" => "text/plain"}
|
12
|
+
# r.commit!
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# response.performed? # => true
|
16
|
+
# response.to_h # => { statusCode: 200, headers: { "Content-Type" => "text/plain" }, body: "Hello World!" }
|
17
|
+
module Response
|
18
|
+
ActiveFunction.register_plugin :response, self
|
7
19
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
@
|
12
|
-
@
|
13
|
-
|
20
|
+
class Response < Struct.new(:status, :headers, :body, :committed)
|
21
|
+
# Initializes a new Response instance with default values.
|
22
|
+
#
|
23
|
+
# @param status [Integer] HTTP status code.
|
24
|
+
# @param headers [Hash] HTTP headers.
|
25
|
+
# @param body [Object] Response body.
|
26
|
+
# @param committed [Boolean] Indicates whether the response has been committed (default is false).
|
27
|
+
def initialize(status: 200, headers: {}, body: nil, committed: false) ; super(status, headers, body, committed); end
|
14
28
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
29
|
+
# Converts the Response instance to a hash for JSON serialization.
|
30
|
+
#
|
31
|
+
# @return [Hash{statusCode: Integer, headers: Hash, body: Object}]
|
32
|
+
def to_h
|
33
|
+
{
|
34
|
+
statusCode: status,
|
35
|
+
headers: headers,
|
36
|
+
body: body
|
37
|
+
}
|
38
|
+
end
|
22
39
|
|
23
|
-
|
24
|
-
|
25
|
-
|
40
|
+
# Marks the response as committed.
|
41
|
+
def commit!
|
42
|
+
self.committed = true
|
43
|
+
end
|
26
44
|
|
27
|
-
|
45
|
+
alias_method :committed?, :committed
|
46
|
+
end
|
28
47
|
end
|
29
48
|
end
|
30
49
|
end
|