activefunction 0.4.0 → 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 +2 -0
- data/README.md +64 -20
- data/lib/.rbnext/2.7/active_function/functions/strong_parameters.rb +41 -2
- data/lib/.rbnext/3.0/active_function/base.rb +22 -2
- data/lib/.rbnext/3.0/active_function/functions/response.rb +22 -0
- data/lib/.rbnext/3.0/active_function.rb +20 -7
- data/lib/.rbnext/3.1/active_function/functions/response.rb +22 -0
- data/lib/active_function/base.rb +22 -2
- data/lib/active_function/functions/callbacks.rb +54 -0
- data/lib/active_function/functions/rendering.rb +23 -0
- data/lib/active_function/functions/response.rb +22 -0
- data/lib/active_function/functions/strong_parameters.rb +41 -2
- data/lib/active_function/version.rb +1 -1
- data/lib/active_function.rb +20 -7
- metadata +72 -7
- 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
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
data/README.md
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# ActiveFunction
|
2
|
+
[](DanilMaximov/activefunction/actions)
|
3
|
+
[](https://badge.fury.io/rb/activefunction)
|
4
|
+
[](https://rubydoc.info/gems/activefunction)
|
2
5
|
|
3
|
-
Playground gem for Ruby 3.2+ features and more.
|
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
|
|
7
9
|
## Features
|
8
10
|
|
@@ -10,7 +12,7 @@ Collection of gems designed to be used with FaaS (Function as a Service) computi
|
|
10
12
|
- **Type Safety:** Achieves type safety through the use of RBS and [Steep](https://github.com/soutaro/steep). (CI'ed)
|
11
13
|
- Note: disabled due to the presence of Ruby::UnsupportedSyntax errors.
|
12
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.
|
13
|
-
- **Gem Collection
|
15
|
+
- **Gem Collection:** Provides a collection of gems designed to be used within ActiveFunction or standalone.
|
14
16
|
|
15
17
|
# Gems
|
16
18
|
|
@@ -20,7 +22,7 @@ Collection of gems designed to be used with FaaS (Function as a Service) computi
|
|
20
22
|
|
21
23
|
## Quick Start
|
22
24
|
|
23
|
-
Here's a simple example of a function that uses ActiveFunction:
|
25
|
+
Here's a simple example of a function that uses ActiveFunction(w/o plugins):
|
24
26
|
|
25
27
|
```ruby
|
26
28
|
require "active_function"
|
@@ -31,9 +33,7 @@ class AppFunction < ActiveFunction::Base
|
|
31
33
|
|
32
34
|
return if performed?
|
33
35
|
|
34
|
-
@response.
|
35
|
-
@response.headers = {"Content-Type" => "application/json"}
|
36
|
-
@response.body = @request[:data]
|
36
|
+
@response.body = @request[:data]
|
37
37
|
end
|
38
38
|
|
39
39
|
private def response_with_error
|
@@ -42,15 +42,24 @@ class AppFunction < ActiveFunction::Base
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
AppFunction.process(:index, {data: {id: 1}}) # => { :statusCode=>200, :headers=> {"Content-Type"=>"application/json"}, :body=>{id: 1}"}
|
46
45
|
```
|
47
46
|
|
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
|
+
```
|
56
|
+
|
48
57
|
## Plugins
|
49
58
|
|
50
59
|
ActiveFunction supports plugins which can be loaded by `ActiveFunction.config` method. Currently, there are 3 plugins available:
|
51
|
-
- [:callbacks](#callbacks) - provides `:before_action`
|
52
|
-
- [:strong_parameters](#strong-parameters) - provides strong parameters support via `#params` instance method.
|
53
|
-
- [:rendering](#rendering) - provides rendering support via `#render` instance method.
|
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.
|
54
63
|
|
55
64
|
## Configuration
|
56
65
|
|
@@ -78,15 +87,15 @@ class AppFunction < ActiveFunction::Base
|
|
78
87
|
|
79
88
|
private def parse_user_data = @user_data = params.require(:data).permit(:id, :name).to_h
|
80
89
|
end
|
81
|
-
```
|
82
|
-
|
83
|
-
Use `#process` method to proceed the request:
|
84
90
|
|
85
|
-
|
86
|
-
#
|
87
|
-
|
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
|
+
# }
|
88
96
|
```
|
89
97
|
|
98
|
+
[See Plugins Docs](https://rubydoc.info/gems/activefunction/ActiveFunction#plugin-class_method) for more details.
|
90
99
|
|
91
100
|
## Callbacks
|
92
101
|
|
@@ -129,7 +138,38 @@ class AppFunction < ActiveFunction::Base
|
|
129
138
|
private def request_valid? = true
|
130
139
|
end
|
131
140
|
```
|
132
|
-
|
141
|
+
|
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
|
+
|
146
|
+
```ruby
|
147
|
+
class MessagingApp < ActiveFunction::Base
|
148
|
+
set_callback_options retries: ->(times, context:) { context.retry if context.retries < times }
|
149
|
+
define_hooks_for :retry
|
150
|
+
|
151
|
+
after_action :retry, if: :failed?, only: %i[send_message], retries: 3
|
152
|
+
after_retry :increment_retries
|
153
|
+
|
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
|
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
|
167
|
+
end
|
168
|
+
|
169
|
+
MessagingApp.process(:send_message, { sender_name: "Alice", message_content: "How are you?" })
|
170
|
+
```
|
171
|
+
|
172
|
+
[See Callbacks Doc](https://rubydoc.info/gems/activefunction/ActiveFunction/Functions/Callbacks) for more details.
|
133
173
|
|
134
174
|
## Strong Parameters
|
135
175
|
|
@@ -170,6 +210,8 @@ Strong params supports nested attributes
|
|
170
210
|
params.permit(:id, :name, :address => [:city, :street])
|
171
211
|
```
|
172
212
|
|
213
|
+
[See StrongParameters Doc](https://rubydoc.info/gems/activefunction/ActiveFunction/Functions/StrongParameters) for more details.
|
214
|
+
|
173
215
|
## Rendering
|
174
216
|
|
175
217
|
ActiveFunction supports rendering of JSON. The #render method is used for rendering responses. It accepts the following options:
|
@@ -197,6 +239,8 @@ end
|
|
197
239
|
PostFunction.process(:index) # => { :statusCode=>200, :headers=> {"Content-Type"=>"application/json", "Some-Header" => "Some-Value"}, :body=>"{\"id\":1,\"name\":\"Pupa\"}"}
|
198
240
|
```
|
199
241
|
|
242
|
+
[See Rendering Doc](https://rubydoc.info/gems/activefunction/ActiveFunction/Functions/Rendering) for more details.
|
243
|
+
|
200
244
|
## Installation
|
201
245
|
|
202
246
|
Add this line to your application's Gemfile:
|
@@ -207,9 +251,9 @@ gem "activefunction"
|
|
207
251
|
|
208
252
|
## Development
|
209
253
|
|
210
|
-
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.
|
211
255
|
|
212
|
-
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`.
|
213
257
|
|
214
258
|
## Contributing
|
215
259
|
|
@@ -4,11 +4,31 @@ require "forwardable"
|
|
4
4
|
|
5
5
|
module ActiveFunction
|
6
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" })
|
7
27
|
module StrongParameters
|
8
28
|
ActiveFunction.register_plugin :strong_parameters, self
|
9
29
|
|
10
30
|
Error = Class.new(StandardError)
|
11
|
-
|
31
|
+
# The Parameters class encapsulates the parameter handling logic.
|
12
32
|
class Parameters < Data.define(:params, :permitted)
|
13
33
|
class ParameterMissingError < Error
|
14
34
|
MESSAGE_TEMPLATE = "Missing parameter: %s"
|
@@ -32,10 +52,19 @@ module ActiveFunction
|
|
32
52
|
|
33
53
|
protected :params
|
34
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.
|
35
59
|
def [](attribute)
|
36
60
|
nested_attribute(params[attribute])
|
37
61
|
end
|
38
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.
|
39
68
|
def require(attribute)
|
40
69
|
if (value = self[attribute])
|
41
70
|
value
|
@@ -44,6 +73,10 @@ module ActiveFunction
|
|
44
73
|
end
|
45
74
|
end
|
46
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.
|
47
80
|
def permit(*attributes)
|
48
81
|
pparams = {}
|
49
82
|
|
@@ -62,7 +95,10 @@ module ActiveFunction
|
|
62
95
|
with(params: pparams, permitted: true)
|
63
96
|
end
|
64
97
|
|
65
|
-
#
|
98
|
+
# Converts parameters to a hash.
|
99
|
+
#
|
100
|
+
# @return [Hash] The hash representation of the parameters.
|
101
|
+
# @raise [UnpermittedParameterError] if any parameters are unpermitted.
|
66
102
|
def to_h
|
67
103
|
raise UnpermittedParameterError, params.keys unless permitted
|
68
104
|
|
@@ -100,6 +136,9 @@ module ActiveFunction
|
|
100
136
|
end
|
101
137
|
end
|
102
138
|
|
139
|
+
# Return params object with {ActiveFunction::SuperBase#request}.
|
140
|
+
#
|
141
|
+
# @return [Parameters] instance of {Parameters} class.
|
103
142
|
def params
|
104
143
|
@_params ||= Parameters.new(@request, false)
|
105
144
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveFunction
|
4
|
+
# Abstract base class with request processing logic.
|
4
5
|
class SuperBase
|
5
6
|
attr_reader :action_name, :request, :response
|
6
7
|
|
@@ -10,6 +11,7 @@ module ActiveFunction
|
|
10
11
|
@response = response
|
11
12
|
end
|
12
13
|
|
14
|
+
# Executes specified @action_name instance method and returns Hash'ed response object
|
13
15
|
def dispatch
|
14
16
|
process(action_name)
|
15
17
|
|
@@ -23,12 +25,30 @@ module ActiveFunction
|
|
23
25
|
private def performed? ; @response.committed?; end
|
24
26
|
end
|
25
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
|
26
41
|
class Base < SuperBase
|
27
42
|
Error = Class.new(StandardError)
|
28
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
|
+
#
|
29
49
|
# @param [String, Symbol] action_name - name of method to call
|
30
|
-
# @param [Hash] request - request
|
31
|
-
# @param [Response] response - response object
|
50
|
+
# @param [Hash] request - request parameters.
|
51
|
+
# @param [Response] response - Functions::Response response object.
|
32
52
|
def self.process(action_name, request = {}, response = Response.new)
|
33
53
|
raise ArgumentError, "Action method #{action_name} is not defined" unless method_defined?(action_name)
|
34
54
|
|
@@ -2,12 +2,33 @@
|
|
2
2
|
|
3
3
|
module ActiveFunction
|
4
4
|
module Functions
|
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!" }
|
5
17
|
module Response
|
6
18
|
ActiveFunction.register_plugin :response, self
|
7
19
|
|
8
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).
|
9
27
|
def initialize(status: 200, headers: {}, body: nil, committed: false) ; super(status, headers, body, committed); end
|
10
28
|
|
29
|
+
# Converts the Response instance to a hash for JSON serialization.
|
30
|
+
#
|
31
|
+
# @return [Hash{statusCode: Integer, headers: Hash, body: Object}]
|
11
32
|
def to_h
|
12
33
|
{
|
13
34
|
statusCode: status,
|
@@ -16,6 +37,7 @@ module ActiveFunction
|
|
16
37
|
}
|
17
38
|
end
|
18
39
|
|
40
|
+
# Marks the response as committed.
|
19
41
|
def commit!
|
20
42
|
self.committed = true
|
21
43
|
end
|
@@ -8,9 +8,16 @@ RubyNext::Language.setup_gem_load_path(transpile: true)
|
|
8
8
|
|
9
9
|
module ActiveFunction
|
10
10
|
class << self
|
11
|
-
# Configure ActiveFunction.
|
11
|
+
# Configure ActiveFunction through DSL method calls.
|
12
|
+
# Setups {ActiveFunction::Base} with provided internal and custom plugins.
|
13
|
+
# Also freezes plugins and {ActiveFunction::Base}.
|
12
14
|
#
|
13
|
-
# @
|
15
|
+
# @example
|
16
|
+
# ActiveFunction.config do
|
17
|
+
# plugin :callbacks
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# @param block [Proc] class_eval'ed block in ActiveFunction module.
|
14
21
|
# @return [void]
|
15
22
|
def config(&block)
|
16
23
|
class_eval(&block)
|
@@ -18,19 +25,25 @@ module ActiveFunction
|
|
18
25
|
self::Base.freeze
|
19
26
|
end
|
20
27
|
|
28
|
+
# List of registered internal plugins.
|
21
29
|
def plugins ; @_plugins ||= {}; end
|
22
30
|
|
23
|
-
# Register plugin.
|
31
|
+
# Register internal Symbol'ed plugin.
|
24
32
|
#
|
25
|
-
# @param
|
26
|
-
#
|
33
|
+
# @param [Symbol] symbol name of internal plugin,
|
34
|
+
# should match file name in ./lib/active_function/functions/*.rb
|
35
|
+
# @param [Module] mod module to register.
|
27
36
|
def register_plugin(symbol, mod)
|
28
37
|
plugins[symbol] = mod
|
29
38
|
end
|
30
39
|
|
31
|
-
#
|
40
|
+
# Add plugin to ActiveFunction::Base.
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# ActiveFunction.plugin :callbacks
|
44
|
+
# ActiveFunction.plugin CustomPlugin
|
32
45
|
#
|
33
|
-
# @param
|
46
|
+
# @param [Symbol, Module] mod
|
34
47
|
# @return [void]
|
35
48
|
def plugin(mod)
|
36
49
|
if mod.is_a? Symbol
|
@@ -2,12 +2,33 @@
|
|
2
2
|
|
3
3
|
module ActiveFunction
|
4
4
|
module Functions
|
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!" }
|
5
17
|
module Response
|
6
18
|
ActiveFunction.register_plugin :response, self
|
7
19
|
|
8
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).
|
9
27
|
def initialize(status: 200, headers: {}, body: nil, committed: false) = super(status, headers, body, committed)
|
10
28
|
|
29
|
+
# Converts the Response instance to a hash for JSON serialization.
|
30
|
+
#
|
31
|
+
# @return [Hash{statusCode: Integer, headers: Hash, body: Object}]
|
11
32
|
def to_h
|
12
33
|
{
|
13
34
|
statusCode: status,
|
@@ -16,6 +37,7 @@ module ActiveFunction
|
|
16
37
|
}
|
17
38
|
end
|
18
39
|
|
40
|
+
# Marks the response as committed.
|
19
41
|
def commit!
|
20
42
|
self.committed = true
|
21
43
|
end
|
data/lib/active_function/base.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveFunction
|
4
|
+
# Abstract base class with request processing logic.
|
4
5
|
class SuperBase
|
5
6
|
attr_reader :action_name, :request, :response
|
6
7
|
|
@@ -10,6 +11,7 @@ module ActiveFunction
|
|
10
11
|
@response = response
|
11
12
|
end
|
12
13
|
|
14
|
+
# Executes specified @action_name instance method and returns Hash'ed response object
|
13
15
|
def dispatch
|
14
16
|
process(action_name)
|
15
17
|
|
@@ -23,12 +25,30 @@ module ActiveFunction
|
|
23
25
|
private def performed? = @response.committed?
|
24
26
|
end
|
25
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
|
26
41
|
class Base < SuperBase
|
27
42
|
Error = Class.new(StandardError)
|
28
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
|
+
#
|
29
49
|
# @param [String, Symbol] action_name - name of method to call
|
30
|
-
# @param [Hash] request - request
|
31
|
-
# @param [Response] response - response object
|
50
|
+
# @param [Hash] request - request parameters.
|
51
|
+
# @param [Response] response - Functions::Response response object.
|
32
52
|
def self.process(action_name, request = {}, response = Response.new)
|
33
53
|
raise ArgumentError, "Action method #{action_name} is not defined" unless method_defined?(action_name)
|
34
54
|
|
@@ -2,14 +2,68 @@
|
|
2
2
|
|
3
3
|
module ActiveFunction
|
4
4
|
module Functions
|
5
|
+
# Setups {before_action} and {after_action} callbacks around {ActiveFunction::SuperBase#process}
|
6
|
+
# using {ActiveFunctionCore::Plugins::Hooks}. Also provides {define_hooks_for} and {set_callback_options} for
|
7
|
+
# defining custom hooks & options.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# ActiveFunction.plugin :callbacks
|
11
|
+
#
|
12
|
+
# class MessagingApp < ActiveFunction::Base
|
13
|
+
# set_callback_options retries: ->(times, context:) { context.retry if context.retries < times }
|
14
|
+
# define_hooks_for :retry
|
15
|
+
#
|
16
|
+
# after_action :retry, if: :failed?, only: %i[send_message], retries: 3
|
17
|
+
# after_retry :increment_retries
|
18
|
+
#
|
19
|
+
# def send_message
|
20
|
+
# @response.status = 200 if SomeApi.send(@request[:message_content]).success?
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# def retry
|
24
|
+
# @response.committed = false
|
25
|
+
# process
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# private def increment_retries = @response.body[:tries] += 1
|
29
|
+
# private def failed? = @response.status != 200
|
30
|
+
# private def retries = @response.body[:tries] ||= 0
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# MessagingApp.process(:send_message, { sender_name: "Alice", message_content: "How are you?" })
|
34
|
+
# defining custom hooks & options.
|
5
35
|
module Callbacks
|
6
36
|
ActiveFunction.register_plugin :callbacks, self
|
7
37
|
|
38
|
+
# Setup callbacks around {ActiveFunction::Base#process} method using {ActiveFunctionCore::Plugins::Hooks}.
|
39
|
+
# Also provides :only option for filtering callbacks by action name.
|
8
40
|
def self.included(base)
|
9
41
|
base.include ActiveFunctionCore::Plugins::Hooks
|
10
42
|
base.define_hooks_for :process, name: :action
|
11
43
|
base.set_callback_options only: ->(args, context:) { args.to_set === context.action_name }
|
12
44
|
end
|
45
|
+
|
46
|
+
# @!method before_action(target, options)
|
47
|
+
# @param [Symbol, String] target - method name to call
|
48
|
+
# @option options [Symbol, String] :if - method name to check before executing the callback.
|
49
|
+
# @option options [Symbol, String] :unless - method name to check before executing the callback.
|
50
|
+
# @option options [Array<Symbol, String>] :only - array of action names.
|
51
|
+
# @see ActiveFunctionCore::Plugins::Hooks::ClassMethods#set_callback
|
52
|
+
# @!method after_action(target, options)
|
53
|
+
# @param [Symbol, String] target - method name to call
|
54
|
+
# @option options [Symbol, String] :if - method name to check before executing the callback.
|
55
|
+
# @option options [Symbol, String] :unless - method name to check before executing the callback.
|
56
|
+
# @option options [Array<Symbol, String>] :only - array of action names.
|
57
|
+
# @see ActiveFunctionCore::Plugins::Hooks::ClassMethods#set_callback
|
58
|
+
|
59
|
+
# @!method set_callback(type, hook_name, target, options)
|
60
|
+
# @see ActiveFunctionCore::Plugins::Hooks::ClassMethods#set_callback
|
61
|
+
|
62
|
+
# @!method define_hooks_for(method_name, name: method_name)
|
63
|
+
# @see ActiveFunctionCore::Plugins::Hooks::ClassMethods#define_hooks_for
|
64
|
+
|
65
|
+
# @!method set_callback_options(options)
|
66
|
+
# @see ActiveFunctionCore::Plugins::Hooks::ClassMethods#set_callback_options
|
13
67
|
end
|
14
68
|
end
|
15
69
|
end
|
@@ -4,6 +4,22 @@ require "json"
|
|
4
4
|
|
5
5
|
module ActiveFunction
|
6
6
|
module Functions
|
7
|
+
# Allows manipulations with {ActiveFunction::SuperBase#response} via {render} instance method.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# require "active_function"
|
11
|
+
#
|
12
|
+
# ActiveFunction.config do
|
13
|
+
# plugin :rendering
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# class PostsFunction < ActiveFunction::Base
|
17
|
+
# def index
|
18
|
+
# render json: {id: 1, name: "Pupa"}, status: 200, head: {"Some-Header" => "Some-Value"}
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# PostFunction.process(:index) # => { :statusCode=>200, :headers=> {"Content-Type"=>"application/json", "Some-Header" => "Some-Value"}, :body=>"{\"id\":1,\"name\":\"Pupa\"}" }
|
7
23
|
module Rendering
|
8
24
|
ActiveFunction.register_plugin :rendering, self
|
9
25
|
|
@@ -21,6 +37,13 @@ module ActiveFunction
|
|
21
37
|
|
22
38
|
DEFAULT_HEADER = {"Content-Type" => "application/json"}.freeze
|
23
39
|
|
40
|
+
# Render JSON response.
|
41
|
+
#
|
42
|
+
# @param status [Integer] HTTP status code (default is 200).
|
43
|
+
# @param json [Hash] JSON data to be rendered (default is an empty hash).
|
44
|
+
# @param head [Hash] Additional headers to be included in the response (default is an empty hash).
|
45
|
+
#
|
46
|
+
# @raise [DoubleRenderError] Raised if #render is called multiple times in the same action.
|
24
47
|
def render(status: 200, json: {}, head: {})
|
25
48
|
raise DoubleRenderError, @action_name if performed?
|
26
49
|
|
@@ -2,12 +2,33 @@
|
|
2
2
|
|
3
3
|
module ActiveFunction
|
4
4
|
module Functions
|
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!" }
|
5
17
|
module Response
|
6
18
|
ActiveFunction.register_plugin :response, self
|
7
19
|
|
8
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).
|
9
27
|
def initialize(status: 200, headers: {}, body: nil, committed: false) = super(status, headers, body, committed)
|
10
28
|
|
29
|
+
# Converts the Response instance to a hash for JSON serialization.
|
30
|
+
#
|
31
|
+
# @return [Hash{statusCode: Integer, headers: Hash, body: Object}]
|
11
32
|
def to_h
|
12
33
|
{
|
13
34
|
statusCode: status,
|
@@ -16,6 +37,7 @@ module ActiveFunction
|
|
16
37
|
}
|
17
38
|
end
|
18
39
|
|
40
|
+
# Marks the response as committed.
|
19
41
|
def commit!
|
20
42
|
self.committed = true
|
21
43
|
end
|
@@ -4,11 +4,31 @@ require "forwardable"
|
|
4
4
|
|
5
5
|
module ActiveFunction
|
6
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" })
|
7
27
|
module StrongParameters
|
8
28
|
ActiveFunction.register_plugin :strong_parameters, self
|
9
29
|
|
10
30
|
Error = Class.new(StandardError)
|
11
|
-
|
31
|
+
# The Parameters class encapsulates the parameter handling logic.
|
12
32
|
class Parameters < Data.define(:params, :permitted)
|
13
33
|
class ParameterMissingError < Error
|
14
34
|
MESSAGE_TEMPLATE = "Missing parameter: %s"
|
@@ -32,10 +52,19 @@ module ActiveFunction
|
|
32
52
|
|
33
53
|
protected :params
|
34
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.
|
35
59
|
def [](attribute)
|
36
60
|
nested_attribute(params[attribute])
|
37
61
|
end
|
38
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.
|
39
68
|
def require(attribute)
|
40
69
|
if (value = self[attribute])
|
41
70
|
value
|
@@ -44,6 +73,10 @@ module ActiveFunction
|
|
44
73
|
end
|
45
74
|
end
|
46
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.
|
47
80
|
def permit(*attributes)
|
48
81
|
pparams = {}
|
49
82
|
|
@@ -62,7 +95,10 @@ module ActiveFunction
|
|
62
95
|
with(params: pparams, permitted: true)
|
63
96
|
end
|
64
97
|
|
65
|
-
#
|
98
|
+
# Converts parameters to a hash.
|
99
|
+
#
|
100
|
+
# @return [Hash] The hash representation of the parameters.
|
101
|
+
# @raise [UnpermittedParameterError] if any parameters are unpermitted.
|
66
102
|
def to_h
|
67
103
|
raise UnpermittedParameterError, params.keys unless permitted
|
68
104
|
|
@@ -100,6 +136,9 @@ module ActiveFunction
|
|
100
136
|
end
|
101
137
|
end
|
102
138
|
|
139
|
+
# Return params object with {ActiveFunction::SuperBase#request}.
|
140
|
+
#
|
141
|
+
# @return [Parameters] instance of {Parameters} class.
|
103
142
|
def params
|
104
143
|
@_params ||= Parameters.new(@request, false)
|
105
144
|
end
|
data/lib/active_function.rb
CHANGED
@@ -8,9 +8,16 @@ RubyNext::Language.setup_gem_load_path(transpile: true)
|
|
8
8
|
|
9
9
|
module ActiveFunction
|
10
10
|
class << self
|
11
|
-
# Configure ActiveFunction.
|
11
|
+
# Configure ActiveFunction through DSL method calls.
|
12
|
+
# Setups {ActiveFunction::Base} with provided internal and custom plugins.
|
13
|
+
# Also freezes plugins and {ActiveFunction::Base}.
|
12
14
|
#
|
13
|
-
# @
|
15
|
+
# @example
|
16
|
+
# ActiveFunction.config do
|
17
|
+
# plugin :callbacks
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# @param block [Proc] class_eval'ed block in ActiveFunction module.
|
14
21
|
# @return [void]
|
15
22
|
def config(&block)
|
16
23
|
class_eval(&block)
|
@@ -18,19 +25,25 @@ module ActiveFunction
|
|
18
25
|
self::Base.freeze
|
19
26
|
end
|
20
27
|
|
28
|
+
# List of registered internal plugins.
|
21
29
|
def plugins = @_plugins ||= {}
|
22
30
|
|
23
|
-
# Register plugin.
|
31
|
+
# Register internal Symbol'ed plugin.
|
24
32
|
#
|
25
|
-
# @param
|
26
|
-
#
|
33
|
+
# @param [Symbol] symbol name of internal plugin,
|
34
|
+
# should match file name in ./lib/active_function/functions/*.rb
|
35
|
+
# @param [Module] mod module to register.
|
27
36
|
def register_plugin(symbol, mod)
|
28
37
|
plugins[symbol] = mod
|
29
38
|
end
|
30
39
|
|
31
|
-
#
|
40
|
+
# Add plugin to ActiveFunction::Base.
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# ActiveFunction.plugin :callbacks
|
44
|
+
# ActiveFunction.plugin CustomPlugin
|
32
45
|
#
|
33
|
-
# @param
|
46
|
+
# @param [Symbol, Module] mod
|
34
47
|
# @return [void]
|
35
48
|
def plugin(mod)
|
36
49
|
if mod.is_a? Symbol
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activefunction
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nerbyk
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-01-
|
11
|
+
date: 2024-01-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activefunction-core
|
@@ -24,6 +24,76 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 0.2.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '13.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '13.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ruby-next
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.0.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.0.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 5.15.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 5.15.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest-reporters
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.4.3
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.4.3
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: mocha
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 2.1.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 2.1.0
|
27
97
|
description: "\n ActiveFunction is a collection of gems designed to be used with
|
28
98
|
Function as a Service (FaaS) computing instances. Inspired by aws-sdk v3 gem structure
|
29
99
|
and rails/activesupport.\n\n Features:\n - Ruby Version Compatibility: Implemented
|
@@ -43,11 +113,6 @@ files:
|
|
43
113
|
- CHANGELOG.md
|
44
114
|
- LICENSE.txt
|
45
115
|
- README.md
|
46
|
-
- bin/console
|
47
|
-
- bin/rake
|
48
|
-
- bin/rubocop
|
49
|
-
- bin/ruby-next
|
50
|
-
- bin/setup
|
51
116
|
- lib/.rbnext/2.7/active_function/functions/strong_parameters.rb
|
52
117
|
- lib/.rbnext/3.0/active_function.rb
|
53
118
|
- lib/.rbnext/3.0/active_function/base.rb
|
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 "active_function"
|
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__)
|
data/bin/rake
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
#
|
5
|
-
# This file was generated by Bundler.
|
6
|
-
#
|
7
|
-
# The application 'rake' is installed as part of a gem, and
|
8
|
-
# this file is here to facilitate running it.
|
9
|
-
#
|
10
|
-
|
11
|
-
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
12
|
-
|
13
|
-
bundle_binstub = File.expand_path("bundle", __dir__)
|
14
|
-
|
15
|
-
if File.file?(bundle_binstub)
|
16
|
-
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
17
|
-
load(bundle_binstub)
|
18
|
-
else
|
19
|
-
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
20
|
-
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
require "rubygems"
|
25
|
-
require "bundler/setup"
|
26
|
-
|
27
|
-
load Gem.bin_path("rake", "rake")
|
data/bin/rubocop
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
#
|
5
|
-
# This file was generated by Bundler.
|
6
|
-
#
|
7
|
-
# The application 'rubocop' is installed as part of a gem, and
|
8
|
-
# this file is here to facilitate running it.
|
9
|
-
#
|
10
|
-
|
11
|
-
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
12
|
-
|
13
|
-
bundle_binstub = File.expand_path("bundle", __dir__)
|
14
|
-
|
15
|
-
if File.file?(bundle_binstub)
|
16
|
-
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
|
17
|
-
load(bundle_binstub)
|
18
|
-
else
|
19
|
-
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
20
|
-
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
require "rubygems"
|
25
|
-
require "bundler/setup"
|
26
|
-
|
27
|
-
load Gem.bin_path("rubocop", "rubocop")
|
data/bin/ruby-next
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
lib_path = File.expand_path("../lib", __dir__)
|
4
|
-
$LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
|
5
|
-
|
6
|
-
require "ruby-next/cli"
|
7
|
-
|
8
|
-
begin
|
9
|
-
cli = RubyNext::CLI.new
|
10
|
-
cli.run(ARGV)
|
11
|
-
rescue => e
|
12
|
-
raise e if $DEBUG
|
13
|
-
STDERR.puts e.message
|
14
|
-
STDERR.puts e.backtrace.join("\n") if ENV["RUBY_NEXT_DEBUG"] == "1"
|
15
|
-
exit 1
|
16
|
-
end
|