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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4f2bbae1e08a02af0dbabf4b235ece3823d17aec26e76b24ad7f4b514ea8526c
4
- data.tar.gz: 6097f177bb4b5dff0583c21c40d45c67aeb4bd81eb6551b52e59c440b3e5e027
3
+ metadata.gz: 777be794ab7e9eb091472b8f886b0b8da55c338c6c617afb9ee95acc9a946cba
4
+ data.tar.gz: faf687684919e61cd2bb9a2b8217f029945971898ae4469593f166d7ab34508a
5
5
  SHA512:
6
- metadata.gz: 51cfd314a65879813c1e8c8c23ea80b32621a1479a5c4ea09c789557ddcc80b05b29af2d84181fec90984c4137f8cc4735bd24e76a6eb2e38d81cfa543605ea7
7
- data.tar.gz: 2ea0d89e66aa2a3c89f3b011a84bed5628a9e2e1de78c506f24d93393ab389922581ba1429509c3f8e30883a6788216069078aec3d38cd692c6eda1314b6a69f
6
+ metadata.gz: 20bb5768a3dcf00582d6f32b99c600c51a9e83c9e5b7ef78ecf81c7e560d63f7d83ea02005d01ef19cd4e2a87a5161f47627e7c305c45d2c18dab6744cd5134a
7
+ data.tar.gz: 7e866bc6a9fe14cd56b8f08985a2a83ce10e57c54560e54e3c954116508638dc0128878d0dc6b2d16f7c8c2c11775ba2681515a58229632bc752e20b76fd79dd
data/CHANGELOG.md CHANGED
@@ -53,4 +53,6 @@
53
53
  - Introduce Plugin system
54
54
  - Global refactoring
55
55
 
56
+ # [0.4.1] - 2024-01-14
56
57
 
58
+ - YARD Doc Added
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # ActiveFunction
2
+ [![Build](https://github.com/DanilMaximov/activefunction/actions/workflows/build.yml/badge.svg)](DanilMaximov/activefunction/actions)
3
+ [![Gem Version](https://badge.fury.io/rb/activefunction.svg)](https://badge.fury.io/rb/activefunction)
4
+ [![RubyDoc](https://img.shields.io/badge/RubyDoc-Documentation-blue.svg)](https://rubydoc.info/gems/activefunction)
2
5
 
3
- Playground gem for Ruby 3.2+ features and more.
4
6
 
5
- Collection of gems designed to be used with FaaS (Function as a Service) computing instances. Inspired by aws-sdk v3 gem structure & rails/activesupport.
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** Provides a collection of gems designed to be used within ActiveFunction or standalone.
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.status = 200
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` and `:after_action` callbacks with `:if`, `:unless` & `:only` options.
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
- ```ruby
86
- # handler.rb
87
- AppFunction.process(:index, {data: { id: 1, name: 2}}) # => { :statusCode=>200, :headers=> {"Content-Type"=>"application/json"}, :body=>"{\"id\":1,\"name\":2}"}
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
- More details in [ActiveFunctionCore::Plugins::Hooks readme](https://github.com/DanilMaximov/activefunction/tree/master/gems/activefunction-core#hooks)
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`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
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
- # Redefines RubyNext::Core::Data instance methods
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 params, accessible through `#params` method
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
- # @param block [Proc]
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 symbol [Symbol]
26
- # @param mod [Module]
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
- # Monkey patch ActiveFunction::Base with provided plugin.
40
+ # Add plugin to ActiveFunction::Base.
41
+ #
42
+ # @example
43
+ # ActiveFunction.plugin :callbacks
44
+ # ActiveFunction.plugin CustomPlugin
32
45
  #
33
- # @param mod [Symbol, Module]
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
@@ -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 params, accessible through `#params` method
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
- # Redefines RubyNext::Core::Data instance methods
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,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveFunction
4
- VERSION = "0.4.0"
4
+ VERSION = "0.4.1"
5
5
  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
- # @param block [Proc]
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 symbol [Symbol]
26
- # @param mod [Module]
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
- # Monkey patch ActiveFunction::Base with provided plugin.
40
+ # Add plugin to ActiveFunction::Base.
41
+ #
42
+ # @example
43
+ # ActiveFunction.plugin :callbacks
44
+ # ActiveFunction.plugin CustomPlugin
32
45
  #
33
- # @param mod [Symbol, Module]
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.0
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-12 00:00:00.000000000 Z
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
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here