activefunction 0.3.5 → 0.4.0

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: 291381221a57e279bfad4dddf88cbeb3695a83e2b4882960b1255304e01a78db
4
- data.tar.gz: 8a2a8d432700fff63717b76198bc032986bc2c6656e7eb9dbf034f78552bcb29
3
+ metadata.gz: 4f2bbae1e08a02af0dbabf4b235ece3823d17aec26e76b24ad7f4b514ea8526c
4
+ data.tar.gz: 6097f177bb4b5dff0583c21c40d45c67aeb4bd81eb6551b52e59c440b3e5e027
5
5
  SHA512:
6
- metadata.gz: 70f3d994a4703b373f91a4df21e0b02a424f0a049a391caf04558ffbcebd80546a3f6683fce5c5a66111336d0827aaa2049ce4b5fbafa64fa7720e13278cf2b7
7
- data.tar.gz: fc0bd5b0c69e64147e7d1729c179c5ef44a931733b9b71b3adec4aa623a7f01f50d70553c7bd58ade6080c2f4e8dab52d2716ebea0b042556296a17d61276e0c
6
+ metadata.gz: 51cfd314a65879813c1e8c8c23ea80b32621a1479a5c4ea09c789557ddcc80b05b29af2d84181fec90984c4137f8cc4735bd24e76a6eb2e38d81cfa543605ea7
7
+ data.tar.gz: 2ea0d89e66aa2a3c89f3b011a84bed5628a9e2e1de78c506f24d93393ab389922581ba1429509c3f8e30883a6788216069078aec3d38cd692c6eda1314b6a69f
data/CHANGELOG.md CHANGED
@@ -46,4 +46,11 @@
46
46
 
47
47
  - Start gem restructuring for modularizaition
48
48
  - Introduce `activefunction-core` gem with RubyNext integration
49
-
49
+
50
+ # [0.4.0] - 2024-01-11
51
+
52
+ - Replace `ActiveFunction::Functions::Callbacks` with `ActiveFunctionCore::Plugins::Hooks`
53
+ - Introduce Plugin system
54
+ - Global refactoring
55
+
56
+
data/README.md CHANGED
@@ -1,144 +1,213 @@
1
1
  # ActiveFunction
2
2
 
3
- rails/action_controller like gem which provides lightweight callbacks, strong parameters & rendering features. It's designed to be used with AWS Lambda functions, but can be also used with any Ruby application.
3
+ Playground gem for Ruby 3.2+ features and more.
4
4
 
5
- Implemented with some of ruby 3.x features, but also supports ruby 2.6.x thanks to [RubyNext](https://github.com/ruby-next/ruby-next) transpiler. Type safety achieved by RBS and [Steep](https://github.com/soutaro/steep).
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.
6
6
 
7
+ ## Features
7
8
 
9
+ - **Ruby Version Compatibility:** Implemented with most of ruby 3.2+ features, BUT supports Ruby versions >= 2.6 through the use of the [RubyNext](https://github.com/ruby-next/ruby-next) transpiler. (CI'ed)
10
+ - **Type Safety:** Achieves type safety through the use of RBS and [Steep](https://github.com/soutaro/steep). (CI'ed)
11
+ - Note: disabled due to the presence of Ruby::UnsupportedSyntax errors.
12
+ - **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.
8
14
 
9
- ## A Short Example
15
+ # Gems
16
+
17
+ - [activefunction](/) - Main gem, provides rails/action-controller like API with callbacks, strong parameters and rendering under plugins.
18
+ - [activefunction-core](/gems/activefunction-core/README.md) - Provides RubyNext integration and External Standalone Plugins
19
+ - activefunction-orm - WIP (ORM around AWS PartiQL)
20
+
21
+ ## Quick Start
10
22
 
11
23
  Here's a simple example of a function that uses ActiveFunction:
12
24
 
13
25
  ```ruby
14
- require 'active_function'
26
+ require "active_function"
15
27
 
16
28
  class AppFunction < ActiveFunction::Base
17
- def index
18
- render json: SomeTable.all
19
- end
29
+ def index
30
+ response_with_error if @request[:data].nil?
31
+
32
+ return if performed?
33
+
34
+ @response.status = 200
35
+ @response.headers = {"Content-Type" => "application/json"}
36
+ @response.body = @request[:data]
37
+ end
38
+
39
+ private def response_with_error
40
+ @response.status = 400
41
+ @response.commit!
42
+ end
20
43
  end
44
+
45
+ AppFunction.process(:index, {data: {id: 1}}) # => { :statusCode=>200, :headers=> {"Content-Type"=>"application/json"}, :body=>{id: 1}"}
21
46
  ```
22
47
 
23
- Use `#process` method to proceed the request:
48
+ ## Plugins
24
49
 
25
- ```ruby
26
- AppFunction.process(:index) # processes index action of AppFunction instance
27
- ```
28
- Also check extended [example](https://github.com/DanilMaximov/activefunction/tree/master/active_function_example)
29
- ## Callbacks
30
- ActiveFunction supports simple callbacks `:before` and `:after` which runs around provided action in `#process`.
50
+ 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.
54
+
55
+ ## Configuration
56
+
57
+ To configure ActiveFunction with plugins, use the `ActiveFunction.config` method.
31
58
 
32
59
  ```ruby
33
- class AppFunction < ActiveFunction::Base
34
- before_action :set_user
35
- after_action :log_response
36
-
37
- # some action ...
60
+ # config/initializers/active_function.rb
61
+ require "active_function"
38
62
 
39
- private
63
+ ActiveFunction.config do
64
+ plugin :callbacks
65
+ plugin :strong_parameters
66
+ plugin :rendering
67
+ end
68
+ ```
40
69
 
41
- def set_user
42
- @user = User.first
43
- end
70
+ ```ruby
71
+ # app/functions/app_function.rb
72
+ class AppFunction < ActiveFunction::Base
73
+ before_action :parse_user_data
44
74
 
45
- def log_response
46
- Logger.info @response
75
+ def index
76
+ render json: @user_data
47
77
  end
78
+
79
+ private def parse_user_data = @user_data = params.require(:data).permit(:id, :name).to_h
48
80
  end
49
81
  ```
50
82
 
51
- Callbacks also can be user with `only: Array[Symbol]` and `if: Symbol` options.
83
+ Use `#process` method to proceed the request:
52
84
 
53
85
  ```ruby
54
- class AppFunction < ActiveFunction::Base
55
- before_action :set_user, only: %i[show update destroy], if: :request_valid?
56
-
57
- # some actions ...
58
-
59
- private def request_valid? = true
60
- end
86
+ # handler.rb
87
+ AppFunction.process(:index, {data: { id: 1, name: 2}}) # => { :statusCode=>200, :headers=> {"Content-Type"=>"application/json"}, :body=>"{\"id\":1,\"name\":2}"}
61
88
  ```
62
89
 
63
- Callbacks are inheritable so all callbacks calls will be inherited from base class
90
+
91
+ ## Callbacks
92
+
93
+ Simple callbacks engined by [ActiveFunctionCore::Plugins::Hooks](https://github.com/DanilMaximov/activefunction/tree/master/gems/activefunction-core#hooks) external plugin and provides `:before_action` and `:after_action` which runs around provided action in `#process`.
94
+
64
95
  ```ruby
65
- class BaseFunction < ActiveFunction::Base
66
- before_action :set_current_user
96
+ require "active_function"
67
97
 
68
- def set_current_user
69
- @current_user = User.first
70
- end
98
+ ActiveFunction.config do
99
+ plugin :callbacks
71
100
  end
72
101
 
73
- class PostsFunction < BaseFunction
102
+ class AppFunction < ActiveFunction::Base
103
+ before_action :set_user
104
+ after_action :log_response
105
+
74
106
  def index
75
- render json: @current_user
107
+ # some actions ...
76
108
  end
109
+
110
+ private
111
+
112
+ def set_user = @_user ||= User.find(@request[:id])
113
+ def log_response = Logger.info(@response)
77
114
  end
78
115
  ```
79
- ## Strong Parameters
80
- ActiveFunction supports strong parameters which can be accessed by `#params` instance method. Strong parameters hash can be passed in `#process` as second argument.
116
+
117
+ ### Callbacks options
118
+
119
+ Supports default [ActiveFunctionCore::Plugins::Hooks::Hook::Callback options](https://github.com/DanilMaximov/activefunction/tree/master/gems/activefunction-core#options) `:if => Symbol` & `:unless => Symbol` options.
120
+
121
+ Support custom defined in ActiveFunction::Function::Callbacks `only: Array[Symbol]` option.
81
122
 
82
123
  ```ruby
83
- PostFunction.process(:index, data: { id: 1, name: "Pupa" })
124
+ class AppFunction < ActiveFunction::Base
125
+ before_action :set_user, only: %i[show update destroy], if: :request_valid?
126
+
127
+ # some actions ...
128
+
129
+ private def request_valid? = true
130
+ end
84
131
  ```
132
+ More details in [ActiveFunctionCore::Plugins::Hooks readme](https://github.com/DanilMaximov/activefunction/tree/master/gems/activefunction-core#hooks)
133
+
134
+ ## Strong Parameters
135
+
136
+ ActiveFunction supports strong parameters which can be accessed by `#params` instance method.
137
+
138
+ The `#params` method represents a Ruby 3.2 Data class that allows the manipulation of request parameters. It supports the following methods:
139
+
140
+ - `[]`: Access parameters by key.
141
+ - `permit`: Specify the allowed parameters.
142
+ - `require`: Ensure the presence of a specific parameter.
143
+ - `to_h`: Convert the parameters to a hash.
144
+
145
+ Usage Example:
85
146
 
86
- Simple usage:
87
147
  ```ruby
148
+ require "active_function"
149
+
150
+ ActiveFunction.config do
151
+ plugin :strong_parameters
152
+ end
153
+
88
154
  class PostsFunction < ActiveFunction::Base
89
- def index
90
- render json: permitted_params
91
- end
155
+ def index
156
+ @response.body = permitted_params
157
+ end
92
158
 
93
159
  def permitted_params = params
94
160
  .require(:data)
95
161
  .permit(:id, :name)
96
162
  .to_h
97
- end
163
+ end
164
+
165
+ PostFunction.process(:index, data: {id: 1, name: "Pupa"})
98
166
  ```
167
+
99
168
  Strong params supports nested attributes
100
- ```ruby
169
+ ```ruby
101
170
  params.permit(:id, :name, :address => [:city, :street])
102
171
  ```
103
172
 
104
173
  ## Rendering
105
- ActiveFunction supports rendering of JSON. Rendering is obligatory for any function naction and can be done by `#render` method.
106
- ```ruby
107
- class PostsFunction < ActiveFunction::Base
108
- def index
109
- render json: { id: 1, name: "Pupa" }
110
- end
111
- end
112
- ```
113
- default status code is 200, but it can be changed by `:status` option
174
+
175
+ ActiveFunction supports rendering of JSON. The #render method is used for rendering responses. It accepts the following options:
176
+
177
+ - `head`: Set response headers. Defaults to `{ "Content-Type" => "application/json" }`.
178
+ - `json`: Set the response body with a JSON-like object. Defaults to `{}`.
179
+ - `status`: Set the HTTP status code. Defaults to `200`.
180
+
181
+ Additionally, the method automatically commits the response and JSONifies the body.
182
+
183
+ Usage Example:
114
184
  ```ruby
115
- class PostsFunction < ActiveFunction::Base
116
- def index
117
- render json: { id: 1, name: "Pupa" }, status: 201
118
- end
185
+ require "active_function"
186
+
187
+ ActiveFunction.config do
188
+ plugin :rendering
119
189
  end
120
- ```
121
- Headers can be passed by `:headers` option. Default headers are `{"Content-Type" => "application/json"}`.
122
- ```ruby
190
+
123
191
  class PostsFunction < ActiveFunction::Base
124
- def index
125
- render json: { id: 1, name: "Pupa" }, headers: { "X-Request-Id" => "123" }
126
- end
192
+ def index
193
+ render json: {id: 1, name: "Pupa"}, status: 200, head: {"Some-Header" => "Some-Value"}
194
+ end
127
195
  end
128
- ```
129
196
 
197
+ PostFunction.process(:index) # => { :statusCode=>200, :headers=> {"Content-Type"=>"application/json", "Some-Header" => "Some-Value"}, :body=>"{\"id\":1,\"name\":\"Pupa\"}"}
198
+ ```
130
199
 
131
200
  ## Installation
132
201
 
133
202
  Add this line to your application's Gemfile:
134
203
 
135
204
  ```ruby
136
- gem 'activefunction', git: "https://github.com/DanilMaximov/activefunction.git"
205
+ gem "activefunction"
137
206
  ```
138
207
 
139
208
  ## Development
140
209
 
141
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/rake test` to run the tests and `bin/rake steep` to run type checker.
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.
142
211
 
143
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).
144
213
 
@@ -3,52 +3,45 @@
3
3
  require "forwardable"
4
4
 
5
5
  module ActiveFunction
6
- class ParameterMissingError < Error
7
- MESSAGE_TEMPLATE = "Missing parameter: %s"
6
+ module Functions
7
+ module StrongParameters
8
+ ActiveFunction.register_plugin :strong_parameters, self
8
9
 
9
- attr_reader :message
10
+ Error = Class.new(StandardError)
10
11
 
11
- def initialize(param)
12
- MESSAGE_TEMPLATE % param
13
- end
14
- end
12
+ class Parameters < Data.define(:params, :permitted)
13
+ class ParameterMissingError < Error
14
+ MESSAGE_TEMPLATE = "Missing parameter: %s"
15
15
 
16
- class UnpermittedParameterError < Error
17
- MESSAGE_TEMPLATE = "Unpermitted parameter: %s"
16
+ attr_reader :message
18
17
 
19
- attr_reader :message
20
-
21
- def initialize(param)
22
- MESSAGE_TEMPLATE % param
23
- end
24
- end
18
+ def initialize(param)
19
+ MESSAGE_TEMPLATE % param
20
+ end
21
+ end
25
22
 
26
- module Functions
27
- module StrongParameters
28
- def params
29
- @_params ||= Parameters.new(@request)
30
- end
23
+ class UnpermittedParameterError < Error
24
+ MESSAGE_TEMPLATE = "Unpermitted parameter: %s"
31
25
 
32
- class Parameters
33
- extend Forwardable
34
- def_delegators :@parameters, :each, :map
35
- include Enumerable
26
+ attr_reader :message
36
27
 
37
- def initialize(parameters, permitted: false)
38
- @parameters = parameters
39
- @permitted = permitted
28
+ def initialize(param)
29
+ MESSAGE_TEMPLATE % param
30
+ end
40
31
  end
41
32
 
33
+ protected :params
34
+
42
35
  def [](attribute)
43
- nested_attribute(parameters[attribute])
36
+ nested_attribute(params[attribute])
44
37
  end
45
38
 
46
39
  def require(attribute)
47
- value = self[attribute]
48
-
49
- raise ParameterMissingError, attribute if value.nil?
50
-
51
- value
40
+ if (value = self[attribute])
41
+ value
42
+ else
43
+ raise ParameterMissingError, attribute
44
+ end
52
45
  end
53
46
 
54
47
  def permit(*attributes)
@@ -60,28 +53,37 @@ module ActiveFunction
60
53
  pparams[k] = process_nested(self[k], :permit, v)
61
54
  end
62
55
  else
63
- next unless parameters.key?(attribute)
56
+ next unless params.key?(attribute)
64
57
 
65
58
  pparams[attribute] = self[attribute]
66
59
  end
67
60
  end
68
61
 
69
- Parameters.new(pparams, permitted: true)
62
+ with(params: pparams, permitted: true)
70
63
  end
71
64
 
65
+ # Redefines RubyNext::Core::Data instance methods
72
66
  def to_h
73
- raise UnpermittedParameterError, parameters.keys unless @permitted
67
+ raise UnpermittedParameterError, params.keys unless permitted
74
68
 
75
- parameters.transform_values { |_1| process_nested(_1, :to_h) }
69
+ params.transform_values { |_1| process_nested(_1, :to_h) }
70
+ end
71
+
72
+ def hash
73
+ @attributes.to_h.hash
74
+ end
75
+
76
+ def with(params:, permitted: false)
77
+ self.class.new(params, permitted)
76
78
  end
77
79
 
78
80
  private
79
81
 
80
82
  def nested_attribute(attribute)
81
83
  if attribute.is_a? Hash
82
- Parameters.new(attribute)
84
+ with(params: attribute)
83
85
  elsif attribute.is_a?(Array) && attribute[0].is_a?(Hash)
84
- attribute.map { |_1| Parameters.new(_1) }
86
+ attribute.map { |it| with(params: it) }
85
87
  else
86
88
  attribute
87
89
  end
@@ -96,8 +98,10 @@ module ActiveFunction
96
98
  attribute
97
99
  end
98
100
  end
101
+ end
99
102
 
100
- attr_reader :parameters
103
+ def params
104
+ @_params ||= Parameters.new(@request, false)
101
105
  end
102
106
  end
103
107
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveFunction
4
+ class SuperBase
5
+ attr_reader :action_name, :request, :response
6
+
7
+ def initialize(action_name, request, response)
8
+ @action_name = action_name
9
+ @request = request
10
+ @response = response
11
+ end
12
+
13
+ def dispatch
14
+ process(action_name)
15
+
16
+ @response.commit! unless performed?
17
+
18
+ @response.to_h
19
+ end
20
+
21
+ def process(action) ; public_send(action); end
22
+
23
+ private def performed? ; @response.committed?; end
24
+ end
25
+
26
+ class Base < SuperBase
27
+ Error = Class.new(StandardError)
28
+
29
+ # @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
32
+ def self.process(action_name, request = {}, response = Response.new)
33
+ raise ArgumentError, "Action method #{action_name} is not defined" unless method_defined?(action_name)
34
+
35
+ new(action_name, request, response).dispatch
36
+ end
37
+ end
38
+ end
@@ -2,29 +2,26 @@
2
2
 
3
3
  module ActiveFunction
4
4
  module Functions
5
- class Response
6
- attr_accessor :status, :headers, :body
5
+ module Response
6
+ ActiveFunction.register_plugin :response, self
7
7
 
8
- def initialize(status: 200, headers: {}, body: nil)
9
- @status = status
10
- @headers = headers
11
- @body = body
12
- @committed = false
13
- end
8
+ class Response < Struct.new(:status, :headers, :body, :committed)
9
+ def initialize(status: 200, headers: {}, body: nil, committed: false) ; super(status, headers, body, committed); end
14
10
 
15
- def to_h
16
- {
17
- statusCode: status,
18
- headers: headers,
19
- body: body
20
- }
21
- end
11
+ def to_h
12
+ {
13
+ statusCode: status,
14
+ headers: headers,
15
+ body: body
16
+ }
17
+ end
22
18
 
23
- def commit!
24
- @committed = true
25
- end
19
+ def commit!
20
+ self.committed = true
21
+ end
26
22
 
27
- def committed? ; @committed; end
23
+ alias_method :committed?, :committed
24
+ end
28
25
  end
29
26
  end
30
27
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_function_core"
4
+ require "active_function/version"
5
+ require "active_function/base"
6
+
7
+ RubyNext::Language.setup_gem_load_path(transpile: true)
8
+
9
+ module ActiveFunction
10
+ class << self
11
+ # Configure ActiveFunction.
12
+ #
13
+ # @param block [Proc]
14
+ # @return [void]
15
+ def config(&block)
16
+ class_eval(&block)
17
+ @_plugins.freeze
18
+ self::Base.freeze
19
+ end
20
+
21
+ def plugins ; @_plugins ||= {}; end
22
+
23
+ # Register plugin.
24
+ #
25
+ # @param symbol [Symbol]
26
+ # @param mod [Module]
27
+ def register_plugin(symbol, mod)
28
+ plugins[symbol] = mod
29
+ end
30
+
31
+ # Monkey patch ActiveFunction::Base with provided plugin.
32
+ #
33
+ # @param mod [Symbol, Module]
34
+ # @return [void]
35
+ def plugin(mod)
36
+ if mod.is_a? Symbol
37
+ begin
38
+ require "active_function/functions/#{mod}"
39
+ mod = plugins.fetch(mod)
40
+ rescue LoadError
41
+ raise ArgumentError, "Unknown plugin #{mod}"
42
+ end
43
+ end
44
+
45
+ self::Base.include(mod)
46
+ end
47
+ end
48
+
49
+ plugin :response
50
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveFunction
4
+ module Functions
5
+ module Response
6
+ ActiveFunction.register_plugin :response, self
7
+
8
+ class Response < Struct.new(:status, :headers, :body, :committed)
9
+ def initialize(status: 200, headers: {}, body: nil, committed: false) = super(status, headers, body, committed)
10
+
11
+ def to_h
12
+ {
13
+ statusCode: status,
14
+ headers: headers,
15
+ body: body
16
+ }
17
+ end
18
+
19
+ def commit!
20
+ self.committed = true
21
+ end
22
+
23
+ alias_method :committed?, :committed
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,20 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveFunction
4
- class Base
5
- require "active_function/functions/core"
6
- require "active_function/functions/callbacks"
7
- require "active_function/functions/strong_parameters"
8
- require "active_function/functions/rendering"
9
- require "active_function/functions/response"
10
-
11
- include Functions::Core
12
- include Functions::Callbacks
13
- include Functions::Rendering
14
- include Functions::StrongParameters
15
-
16
- def self.process(action_name, request = {}, response = Functions::Response.new)
17
- new.dispatch(action_name, request, response)
4
+ class SuperBase
5
+ attr_reader :action_name, :request, :response
6
+
7
+ def initialize(action_name, request, response)
8
+ @action_name = action_name
9
+ @request = request
10
+ @response = response
11
+ end
12
+
13
+ def dispatch
14
+ process(action_name)
15
+
16
+ @response.commit! unless performed?
17
+
18
+ @response.to_h
19
+ end
20
+
21
+ def process(action) = public_send(action)
22
+
23
+ private def performed? = @response.committed?
24
+ end
25
+
26
+ class Base < SuperBase
27
+ Error = Class.new(StandardError)
28
+
29
+ # @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
32
+ def self.process(action_name, request = {}, response = Response.new)
33
+ raise ArgumentError, "Action method #{action_name} is not defined" unless method_defined?(action_name)
34
+
35
+ new(action_name, request, response).dispatch
18
36
  end
19
37
  end
20
38
  end
@@ -1,68 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveFunction
4
- class MissingCallbackContext < Error
5
- MESSAGE_TEMPLATE = "Missing callback context: %s"
6
-
7
- attr_reader :message
8
-
9
- def initialize(context)
10
- @message = MESSAGE_TEMPLATE % context
11
- end
12
- end
13
-
14
4
  module Functions
15
5
  module Callbacks
16
- def self.included(base)
17
- base.extend(ClassMethods)
18
- end
19
-
20
- private
21
-
22
- def process(*)
23
- _run_callbacks :before
24
-
25
- super
26
-
27
- _run_callbacks :after
28
- end
6
+ ActiveFunction.register_plugin :callbacks, self
29
7
 
30
- def _run_callbacks(type)
31
- self.class.callbacks[type].each do |callback_method, options|
32
- raise MissingCallbackContext, callback_method unless respond_to?(callback_method, true)
33
-
34
- send(callback_method) if _executable?(options)
35
- end
36
- end
37
-
38
- def _executable?(options)
39
- return false if options[:only] && !options[:only].include?(@action_name)
40
- return false if options[:if] && !send(options[:if])
41
- true
42
- end
43
-
44
- module ClassMethods
45
- def inherited(subclass)
46
- subclass.instance_variable_set(:@__callbacks, @__callbacks)
47
- end
48
-
49
- def before_action(method, options = {})
50
- set_callback :before, method, options
51
- end
52
-
53
- def after_action(method, options = {})
54
- set_callback :after, method, options
55
- end
56
-
57
- def set_callback(type, method, options = {})
58
- callbacks[type][method] = options
59
- end
60
-
61
- def callbacks
62
- @__callbacks ||= {before: {}, after: {}}
63
-
64
- @__callbacks
65
- end
8
+ def self.included(base)
9
+ base.include ActiveFunctionCore::Plugins::Hooks
10
+ base.define_hooks_for :process, name: :action
11
+ base.set_callback_options only: ->(args, context:) { args.to_set === context.action_name }
66
12
  end
67
13
  end
68
14
  end
@@ -3,18 +3,22 @@
3
3
  require "json"
4
4
 
5
5
  module ActiveFunction
6
- class DoubleRenderError < Error
7
- MESSAGE_TEMPLATE = "#render was called multiple times in action: %s"
6
+ module Functions
7
+ module Rendering
8
+ ActiveFunction.register_plugin :rendering, self
8
9
 
9
- attr_reader :message
10
+ Error = Class.new(StandardError)
10
11
 
11
- def initialize(context)
12
- @message = MESSAGE_TEMPLATE % context
13
- end
14
- end
12
+ class DoubleRenderError < Error
13
+ MESSAGE_TEMPLATE = "#render was called multiple times in action: %s"
14
+
15
+ attr_reader :message
16
+
17
+ def initialize(context)
18
+ @message = MESSAGE_TEMPLATE % context
19
+ end
20
+ end
15
21
 
16
- module Functions
17
- module Rendering
18
22
  DEFAULT_HEADER = {"Content-Type" => "application/json"}.freeze
19
23
 
20
24
  def render(status: 200, json: {}, head: {})
@@ -2,29 +2,26 @@
2
2
 
3
3
  module ActiveFunction
4
4
  module Functions
5
- class Response
6
- attr_accessor :status, :headers, :body
5
+ module Response
6
+ ActiveFunction.register_plugin :response, self
7
7
 
8
- def initialize(status: 200, headers: {}, body: nil)
9
- @status = status
10
- @headers = headers
11
- @body = body
12
- @committed = false
13
- end
8
+ class Response < Struct.new(:status, :headers, :body, :committed)
9
+ def initialize(status: 200, headers: {}, body: nil, committed: false) = super(status, headers, body, committed)
14
10
 
15
- def to_h
16
- {
17
- statusCode: status,
18
- headers: headers,
19
- body: body
20
- }
21
- end
11
+ def to_h
12
+ {
13
+ statusCode: status,
14
+ headers:,
15
+ body:
16
+ }
17
+ end
22
18
 
23
- def commit!
24
- @committed = true
25
- end
19
+ def commit!
20
+ self.committed = true
21
+ end
26
22
 
27
- def committed? = @committed
23
+ alias_method :committed?, :committed
24
+ end
28
25
  end
29
26
  end
30
27
  end
@@ -3,52 +3,45 @@
3
3
  require "forwardable"
4
4
 
5
5
  module ActiveFunction
6
- class ParameterMissingError < Error
7
- MESSAGE_TEMPLATE = "Missing parameter: %s"
6
+ module Functions
7
+ module StrongParameters
8
+ ActiveFunction.register_plugin :strong_parameters, self
8
9
 
9
- attr_reader :message
10
+ Error = Class.new(StandardError)
10
11
 
11
- def initialize(param)
12
- MESSAGE_TEMPLATE % param
13
- end
14
- end
12
+ class Parameters < Data.define(:params, :permitted)
13
+ class ParameterMissingError < Error
14
+ MESSAGE_TEMPLATE = "Missing parameter: %s"
15
15
 
16
- class UnpermittedParameterError < Error
17
- MESSAGE_TEMPLATE = "Unpermitted parameter: %s"
16
+ attr_reader :message
18
17
 
19
- attr_reader :message
20
-
21
- def initialize(param)
22
- MESSAGE_TEMPLATE % param
23
- end
24
- end
18
+ def initialize(param)
19
+ MESSAGE_TEMPLATE % param
20
+ end
21
+ end
25
22
 
26
- module Functions
27
- module StrongParameters
28
- def params
29
- @_params ||= Parameters.new(@request)
30
- end
23
+ class UnpermittedParameterError < Error
24
+ MESSAGE_TEMPLATE = "Unpermitted parameter: %s"
31
25
 
32
- class Parameters
33
- extend Forwardable
34
- def_delegators :@parameters, :each, :map
35
- include Enumerable
26
+ attr_reader :message
36
27
 
37
- def initialize(parameters, permitted: false)
38
- @parameters = parameters
39
- @permitted = permitted
28
+ def initialize(param)
29
+ MESSAGE_TEMPLATE % param
30
+ end
40
31
  end
41
32
 
33
+ protected :params
34
+
42
35
  def [](attribute)
43
- nested_attribute(parameters[attribute])
36
+ nested_attribute(params[attribute])
44
37
  end
45
38
 
46
39
  def require(attribute)
47
- value = self[attribute]
48
-
49
- raise ParameterMissingError, attribute if value.nil?
50
-
51
- value
40
+ if (value = self[attribute])
41
+ value
42
+ else
43
+ raise ParameterMissingError, attribute
44
+ end
52
45
  end
53
46
 
54
47
  def permit(*attributes)
@@ -60,28 +53,37 @@ module ActiveFunction
60
53
  pparams[k] = process_nested(self[k], :permit, v)
61
54
  end
62
55
  else
63
- next unless parameters.key?(attribute)
56
+ next unless params.key?(attribute)
64
57
 
65
58
  pparams[attribute] = self[attribute]
66
59
  end
67
60
  end
68
61
 
69
- Parameters.new(pparams, permitted: true)
62
+ with(params: pparams, permitted: true)
70
63
  end
71
64
 
65
+ # Redefines RubyNext::Core::Data instance methods
72
66
  def to_h
73
- raise UnpermittedParameterError, parameters.keys unless @permitted
67
+ raise UnpermittedParameterError, params.keys unless permitted
74
68
 
75
- parameters.transform_values { process_nested(_1, :to_h) }
69
+ params.transform_values { process_nested(_1, :to_h) }
70
+ end
71
+
72
+ def hash
73
+ @attributes.to_h.hash
74
+ end
75
+
76
+ def with(params:, permitted: false)
77
+ self.class.new(params, permitted)
76
78
  end
77
79
 
78
80
  private
79
81
 
80
82
  def nested_attribute(attribute)
81
83
  if attribute.is_a? Hash
82
- Parameters.new(attribute)
84
+ with(params: attribute)
83
85
  elsif attribute.is_a?(Array) && attribute[0].is_a?(Hash)
84
- attribute.map { Parameters.new(_1) }
86
+ attribute.map { |it| with(params: it) }
85
87
  else
86
88
  attribute
87
89
  end
@@ -96,8 +98,10 @@ module ActiveFunction
96
98
  attribute
97
99
  end
98
100
  end
101
+ end
99
102
 
100
- attr_reader :parameters
103
+ def params
104
+ @_params ||= Parameters.new(@request, false)
101
105
  end
102
106
  end
103
107
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveFunction
4
- VERSION = "0.3.5"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -1,12 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_function_core"
4
+ require "active_function/version"
5
+ require "active_function/base"
4
6
 
5
7
  RubyNext::Language.setup_gem_load_path(transpile: true)
6
8
 
7
9
  module ActiveFunction
8
- class Error < StandardError; end
10
+ class << self
11
+ # Configure ActiveFunction.
12
+ #
13
+ # @param block [Proc]
14
+ # @return [void]
15
+ def config(&block)
16
+ class_eval(&block)
17
+ @_plugins.freeze
18
+ self::Base.freeze
19
+ end
9
20
 
10
- require "active_function/version"
11
- require "active_function/base"
21
+ def plugins = @_plugins ||= {}
22
+
23
+ # Register plugin.
24
+ #
25
+ # @param symbol [Symbol]
26
+ # @param mod [Module]
27
+ def register_plugin(symbol, mod)
28
+ plugins[symbol] = mod
29
+ end
30
+
31
+ # Monkey patch ActiveFunction::Base with provided plugin.
32
+ #
33
+ # @param mod [Symbol, Module]
34
+ # @return [void]
35
+ def plugin(mod)
36
+ if mod.is_a? Symbol
37
+ begin
38
+ require "active_function/functions/#{mod}"
39
+ mod = plugins.fetch(mod)
40
+ rescue LoadError
41
+ raise ArgumentError, "Unknown plugin #{mod}"
42
+ end
43
+ end
44
+
45
+ self::Base.include(mod)
46
+ end
47
+ end
48
+
49
+ plugin :response
12
50
  end
metadata CHANGED
@@ -1,76 +1,39 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activefunction
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.5
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nerbyk
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-20 00:00:00.000000000 Z
11
+ date: 2024-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activefunction-core
15
15
  requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: 0.0.1
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: 0.0.1
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: minitest
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: 5.15.0
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
16
  requirements:
52
17
  - - "~>"
53
18
  - !ruby/object:Gem::Version
54
- version: 5.15.0
55
- - !ruby/object:Gem::Dependency
56
- name: minitest-reporters
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: 1.4.3
62
- type: :development
19
+ version: 0.2.2
20
+ type: :runtime
63
21
  prerelease: false
64
22
  version_requirements: !ruby/object:Gem::Requirement
65
23
  requirements:
66
24
  - - "~>"
67
25
  - !ruby/object:Gem::Version
68
- version: 1.4.3
69
- description: "\n rails/action_controller like gem which provides lightweight callbacks,\n
70
- \ strong parameters & rendering features. It's designed to be used with\n AWS
71
- Lambda functions, but can be also used with any Ruby application.\n\n Implemented
72
- with some of ruby 3.x features, but also supports\n ruby 2.6.x thanks to RubyNext
73
- transpiler. Type safety achieved\n by RBS and Steep.\n "
26
+ version: 0.2.2
27
+ description: "\n ActiveFunction is a collection of gems designed to be used with
28
+ Function as a Service (FaaS) computing instances. Inspired by aws-sdk v3 gem structure
29
+ and rails/activesupport.\n\n Features:\n - Ruby Version Compatibility: Implemented
30
+ with most of Ruby 3.2+ features, with support for Ruby versions >= 2.6 through the
31
+ RubyNext transpiler (CI'ed).\n - Type Safety: Achieves type safety through the
32
+ use of RBS and Steep (CI'ed) [Note: disabled due to the presence of Ruby::UnsupportedSyntax
33
+ errors].\n - Plugins System: Provides a simple Plugin system inspired by Polishing
34
+ Ruby Programming by Jeremy Evans to load gem plugins and self-defined plugins.\n
35
+ \ - Gem Collection: Offers a collection of gems designed for use within ActiveFunction
36
+ or as standalone components.\n "
74
37
  email:
75
38
  - danil.maximov2000@gmail.com
76
39
  executables: []
@@ -86,12 +49,13 @@ files:
86
49
  - bin/ruby-next
87
50
  - bin/setup
88
51
  - lib/.rbnext/2.7/active_function/functions/strong_parameters.rb
89
- - lib/.rbnext/3.0/active_function/functions/core.rb
52
+ - lib/.rbnext/3.0/active_function.rb
53
+ - lib/.rbnext/3.0/active_function/base.rb
90
54
  - lib/.rbnext/3.0/active_function/functions/response.rb
55
+ - lib/.rbnext/3.1/active_function/functions/response.rb
91
56
  - lib/active_function.rb
92
57
  - lib/active_function/base.rb
93
58
  - lib/active_function/functions/callbacks.rb
94
- - lib/active_function/functions/core.rb
95
59
  - lib/active_function/functions/rendering.rb
96
60
  - lib/active_function/functions/response.rb
97
61
  - lib/active_function/functions/strong_parameters.rb
@@ -105,7 +69,7 @@ metadata:
105
69
  homepage_uri: https://github.com/DanilMaximov/activefunction
106
70
  source_code_uri: https://github.com/DanilMaximov/activefunction
107
71
  changelog_uri: https://github.com/DanilMaximov/activefunction/CHANGELOG.md
108
- post_install_message:
72
+ post_install_message:
109
73
  rdoc_options: []
110
74
  require_paths:
111
75
  - lib
@@ -120,9 +84,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
84
  - !ruby/object:Gem::Version
121
85
  version: '0'
122
86
  requirements: []
123
- rubygems_version: 3.4.10
124
- signing_key:
87
+ rubygems_version: 3.5.4
88
+ signing_key:
125
89
  specification_version: 4
126
- summary: rails/action_controller like gem which provides callbacks, strong parameters
127
- & rendering features.
90
+ summary: Playground gem for Ruby 3.2+ features and more, designed for FaaS computing
91
+ instances, but mostly used for experiments.
128
92
  test_files: []
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveFunction
4
- class MissingRouteMethod < Error
5
- MESSAGE_TEMPLATE = "Missing function route: %s"
6
-
7
- attr_reader :message
8
-
9
- def initialize(context)
10
- @message = MESSAGE_TEMPLATE % context
11
- end
12
- end
13
-
14
- class NotRenderedError < Error
15
- MESSAGE_TEMPLATE = "render was not called: %s"
16
-
17
- attr_reader :message
18
-
19
- def initialize(context)
20
- @message = MESSAGE_TEMPLATE % context
21
- end
22
- end
23
-
24
- module Functions
25
- module Core
26
- attr_reader :action_name, :request, :response
27
-
28
- def dispatch(action_name, request, response)
29
- @action_name = action_name
30
- @request = request
31
- @response = response
32
-
33
- raise MissingRouteMethod, @action_name unless respond_to?(action_name)
34
-
35
- process(@action_name)
36
-
37
- raise NotRenderedError, @action_name unless performed?
38
-
39
- @response.to_h
40
- end
41
-
42
- private
43
-
44
- def process(action) ; public_send(action); end
45
-
46
- def performed? ; @response.committed?; end
47
- end
48
- end
49
- end
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveFunction
4
- class MissingRouteMethod < Error
5
- MESSAGE_TEMPLATE = "Missing function route: %s"
6
-
7
- attr_reader :message
8
-
9
- def initialize(context)
10
- @message = MESSAGE_TEMPLATE % context
11
- end
12
- end
13
-
14
- class NotRenderedError < Error
15
- MESSAGE_TEMPLATE = "render was not called: %s"
16
-
17
- attr_reader :message
18
-
19
- def initialize(context)
20
- @message = MESSAGE_TEMPLATE % context
21
- end
22
- end
23
-
24
- module Functions
25
- module Core
26
- attr_reader :action_name, :request, :response
27
-
28
- def dispatch(action_name, request, response)
29
- @action_name = action_name
30
- @request = request
31
- @response = response
32
-
33
- raise MissingRouteMethod, @action_name unless respond_to?(action_name)
34
-
35
- process(@action_name)
36
-
37
- raise NotRenderedError, @action_name unless performed?
38
-
39
- @response.to_h
40
- end
41
-
42
- private
43
-
44
- def process(action) = public_send(action)
45
-
46
- def performed? = @response.committed?
47
- end
48
- end
49
- end