activefunction 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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