better_controller 0.1.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 +7 -0
- data/.DS_Store +0 -0
- data/.rspec +3 -0
- data/.rubocop.yml +95 -0
- data/CHANGELOG.md +18 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +624 -0
- data/Rakefile +12 -0
- data/examples/api_controller.rb +31 -0
- data/examples/application_controller.rb +51 -0
- data/examples/products_controller.rb +25 -0
- data/examples/user_serializer.rb +15 -0
- data/examples/user_service.rb +31 -0
- data/examples/users_controller.rb +86 -0
- data/lib/better_controller/action_helpers.rb +76 -0
- data/lib/better_controller/base.rb +61 -0
- data/lib/better_controller/configuration.rb +69 -0
- data/lib/better_controller/logging.rb +101 -0
- data/lib/better_controller/method_not_overridden_error.rb +13 -0
- data/lib/better_controller/pagination.rb +44 -0
- data/lib/better_controller/parameter_validation.rb +86 -0
- data/lib/better_controller/params_helpers.rb +109 -0
- data/lib/better_controller/railtie.rb +18 -0
- data/lib/better_controller/resources_controller.rb +263 -0
- data/lib/better_controller/response_helpers.rb +74 -0
- data/lib/better_controller/serializer.rb +85 -0
- data/lib/better_controller/service.rb +94 -0
- data/lib/better_controller/version.rb +5 -0
- data/lib/better_controller.rb +55 -0
- data/lib/generators/better_controller/controller_generator.rb +65 -0
- data/lib/generators/better_controller/install_generator.rb +22 -0
- data/lib/generators/better_controller/templates/README +87 -0
- data/lib/generators/better_controller/templates/controller.rb +78 -0
- data/lib/generators/better_controller/templates/initializer.rb +31 -0
- data/lib/generators/better_controller/templates/serializer.rb +32 -0
- data/lib/generators/better_controller/templates/service.rb +57 -0
- data/lib/tasks/better_controller_tasks.rake +61 -0
- data/sig/better_controller.rbs +4 -0
- metadata +226 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Example of a controller using BetterController's standard_actions
|
4
|
+
class ProductsController < ApplicationController
|
5
|
+
include BetterController
|
6
|
+
|
7
|
+
# Use the standard_actions helper to generate CRUD actions
|
8
|
+
standard_actions Product, paginate: true
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# Strong parameters for Product
|
13
|
+
def product_params
|
14
|
+
params.require(:product).permit(:name, :description, :price, :category_id)
|
15
|
+
end
|
16
|
+
|
17
|
+
# You can still override any of the standard actions if needed
|
18
|
+
# For example:
|
19
|
+
# def index
|
20
|
+
# execute_action do
|
21
|
+
# products = Product.where(category_id: params[:category_id])
|
22
|
+
# respond_with_pagination(products)
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Example of a serializer for User resources
|
4
|
+
class UserSerializer
|
5
|
+
include BetterController::Serializer
|
6
|
+
|
7
|
+
# Define the attributes to include in the serialized output
|
8
|
+
attributes :id, :name, :email, :role, :created_at, :updated_at
|
9
|
+
|
10
|
+
# Define methods to include in the serialized output
|
11
|
+
methods :full_name, :active_status
|
12
|
+
|
13
|
+
# Define associations to include in the serialized output
|
14
|
+
# associations posts: PostSerializer, comments: CommentSerializer
|
15
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Example of a service class for User resources
|
4
|
+
class UserService < BetterController::Service
|
5
|
+
protected
|
6
|
+
|
7
|
+
# Define the resource class for this service
|
8
|
+
def resource_class
|
9
|
+
User
|
10
|
+
end
|
11
|
+
|
12
|
+
# Customize the resource scope if needed
|
13
|
+
def resource_scope(ancestry_params = {})
|
14
|
+
scope = super
|
15
|
+
|
16
|
+
# Example of adding custom scoping
|
17
|
+
scope = scope.active if ancestry_params[:active_only]
|
18
|
+
|
19
|
+
scope
|
20
|
+
end
|
21
|
+
|
22
|
+
# Customize attribute preparation if needed
|
23
|
+
def prepare_attributes(attributes, ancestry_params = {})
|
24
|
+
prepared = super
|
25
|
+
|
26
|
+
# Example of adding custom attribute preparation
|
27
|
+
prepared[:created_by] = ancestry_params[:current_user_id] if ancestry_params[:current_user_id]
|
28
|
+
|
29
|
+
prepared
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'user_service'
|
4
|
+
require_relative 'user_serializer'
|
5
|
+
|
6
|
+
# Example of a Rails controller using BetterController::ResourcesController
|
7
|
+
class UsersController < ApplicationController
|
8
|
+
include BetterController::ResourcesController
|
9
|
+
|
10
|
+
# Define required parameters for actions
|
11
|
+
requires_params :create, :name, :email
|
12
|
+
requires_params :update, :id
|
13
|
+
|
14
|
+
# Define parameter schema for validation
|
15
|
+
param_schema :create, {
|
16
|
+
name: { required: true, type: String },
|
17
|
+
email: { required: true, format: /\A[^@\s]+@[^@\s]+\z/ },
|
18
|
+
role: { in: %w[admin user guest] },
|
19
|
+
}
|
20
|
+
|
21
|
+
# You can override any of the ResourcesController methods if needed
|
22
|
+
# For example, to customize the index action:
|
23
|
+
def index
|
24
|
+
execute_action do
|
25
|
+
@resource_collection = resource_collection_resolver
|
26
|
+
|
27
|
+
# Add pagination metadata
|
28
|
+
if @resource_collection.respond_to?(:page)
|
29
|
+
paginated = @resource_collection.page(params[:page] || 1).per(params[:per_page] || 25)
|
30
|
+
add_meta(:pagination, {
|
31
|
+
current_page: paginated.current_page,
|
32
|
+
total_pages: paginated.total_pages,
|
33
|
+
total_count: paginated.total_count,
|
34
|
+
})
|
35
|
+
@resource_collection = paginated
|
36
|
+
end
|
37
|
+
|
38
|
+
respond_with_success(@resource_collection, options: { meta: meta })
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# You can also add custom actions
|
43
|
+
def activate
|
44
|
+
execute_action do
|
45
|
+
@resource = resource_finder
|
46
|
+
@resource.update(active: true)
|
47
|
+
|
48
|
+
respond_with_success(@resource, options: {
|
49
|
+
message: "User #{@resource.name} has been activated",
|
50
|
+
})
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
# Define the resource service class
|
57
|
+
def resource_service_class
|
58
|
+
UserService
|
59
|
+
end
|
60
|
+
|
61
|
+
# Define the root key for resource parameters
|
62
|
+
def resource_params_root_key
|
63
|
+
:user
|
64
|
+
end
|
65
|
+
|
66
|
+
# Customize the ancestry parameters if needed
|
67
|
+
def ancestry_key_params
|
68
|
+
# Example: passing the current user ID to the service
|
69
|
+
{ current_user_id: current_user&.id }
|
70
|
+
end
|
71
|
+
|
72
|
+
# Customize create message
|
73
|
+
def create_message
|
74
|
+
"User #{@resource.name} has been created successfully"
|
75
|
+
end
|
76
|
+
|
77
|
+
# Customize update message
|
78
|
+
def update_message
|
79
|
+
"User #{@resource.name} has been updated successfully"
|
80
|
+
end
|
81
|
+
|
82
|
+
# Customize destroy message
|
83
|
+
def destroy_message
|
84
|
+
'User has been deleted successfully'
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterController
|
4
|
+
# Module providing helper methods for common controller actions
|
5
|
+
module ActionHelpers
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# Define standard CRUD actions for a resource
|
9
|
+
# @param resource_class [Class] The resource class (e.g., User)
|
10
|
+
# @param options [Hash] Options for customizing the actions
|
11
|
+
module ClassMethods
|
12
|
+
def standard_actions(resource_class, options = {})
|
13
|
+
resource_name = resource_class.name.underscore
|
14
|
+
resource_name.pluralize
|
15
|
+
|
16
|
+
# Define index action
|
17
|
+
define_method(:index) do
|
18
|
+
execute_action do
|
19
|
+
collection = resource_class.all
|
20
|
+
|
21
|
+
# Apply scopes if provided
|
22
|
+
collection = apply_scopes(collection) if respond_to?(:apply_scopes)
|
23
|
+
|
24
|
+
# Apply pagination if requested
|
25
|
+
if options[:paginate]
|
26
|
+
respond_with_pagination(collection, page: params[:page], per_page: params[:per_page])
|
27
|
+
else
|
28
|
+
respond_with_success(collection)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Define show action
|
34
|
+
define_method(:show) do
|
35
|
+
execute_action do
|
36
|
+
resource = resource_class.find(params[:id])
|
37
|
+
respond_with_success(resource)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Define create action
|
42
|
+
define_method(:create) do
|
43
|
+
execute_action do
|
44
|
+
resource = resource_class.new(send(:"#{resource_name}_params"))
|
45
|
+
|
46
|
+
with_transaction do
|
47
|
+
resource.save!
|
48
|
+
respond_with_success(resource, status: :created)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Define update action
|
54
|
+
define_method(:update) do
|
55
|
+
execute_action do
|
56
|
+
resource = resource_class.find(params[:id])
|
57
|
+
|
58
|
+
with_transaction do
|
59
|
+
resource.update!(send(:"#{resource_name}_params"))
|
60
|
+
respond_with_success(resource)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Define destroy action
|
66
|
+
define_method(:destroy) do
|
67
|
+
execute_action do
|
68
|
+
resource = resource_class.find(params[:id])
|
69
|
+
resource.destroy
|
70
|
+
respond_with_success(nil, status: :no_content)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterController
|
4
|
+
# Base module that provides core functionality for enhanced controllers
|
5
|
+
module Base
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
# Class methods and configurations to be included in the controller
|
10
|
+
class_attribute :better_controller_options, default: {}
|
11
|
+
|
12
|
+
# Set up rescue handlers if enabled
|
13
|
+
if defined?(rescue_from) && BetterController.config.error_handling[:detailed_errors]
|
14
|
+
rescue_from StandardError, with: :better_controller_handle_error
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Instance methods for controllers
|
19
|
+
|
20
|
+
# Execute an action with enhanced error handling and response formatting
|
21
|
+
# @param action_block [Proc] The block containing the action logic
|
22
|
+
# @return [Object] The formatted response
|
23
|
+
def execute_action(&)
|
24
|
+
log_debug("Executing action: #{action_name}") if respond_to?(:action_name)
|
25
|
+
result = instance_eval(&)
|
26
|
+
respond_with_success(result)
|
27
|
+
rescue StandardError => e
|
28
|
+
handle_exception(e)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Helper method to simplify common controller patterns
|
32
|
+
# @param options [Hash] Options for the action
|
33
|
+
# @return [Object] The result of the action
|
34
|
+
def with_transaction(options = {}, &)
|
35
|
+
ActiveRecord::Base.transaction(&)
|
36
|
+
rescue StandardError => e
|
37
|
+
handle_exception(e, options)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Handle exceptions raised by the controller
|
41
|
+
# @param exception [Exception] The exception to handle
|
42
|
+
def better_controller_handle_error(exception)
|
43
|
+
handle_exception(exception)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# Handle exceptions in a standardized way
|
49
|
+
# @param exception [Exception] The exception to handle
|
50
|
+
# @param options [Hash] Options for handling the exception
|
51
|
+
# @return [Object] The error response
|
52
|
+
def handle_exception(exception, options = {})
|
53
|
+
# Log the exception if logging is enabled
|
54
|
+
if respond_to?(:log_exception) && BetterController.config.error_handling[:log_errors]
|
55
|
+
log_exception(exception, { controller: self.class.name, action: action_name })
|
56
|
+
end
|
57
|
+
|
58
|
+
respond_with_error(exception, options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterController
|
4
|
+
# Configuration module for BetterController
|
5
|
+
module Configuration
|
6
|
+
# Default configuration options
|
7
|
+
DEFAULTS = {
|
8
|
+
pagination: {
|
9
|
+
enabled: true,
|
10
|
+
per_page: 25,
|
11
|
+
},
|
12
|
+
serialization: {
|
13
|
+
include_root: false,
|
14
|
+
camelize_keys: true,
|
15
|
+
},
|
16
|
+
error_handling: {
|
17
|
+
log_errors: true,
|
18
|
+
detailed_errors: true,
|
19
|
+
},
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
class << self
|
23
|
+
# Get the current configuration
|
24
|
+
# @return [Hash] The current configuration
|
25
|
+
def options
|
26
|
+
@options ||= DEFAULTS.deep_dup
|
27
|
+
end
|
28
|
+
|
29
|
+
# Configure BetterController
|
30
|
+
# @yield [config] The configuration block
|
31
|
+
def configure
|
32
|
+
yield(options) if block_given?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Reset the configuration to defaults
|
36
|
+
def reset!
|
37
|
+
@options = DEFAULTS.deep_dup
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get a specific configuration option
|
41
|
+
# @param key [Symbol] The configuration key
|
42
|
+
# @return [Object] The configuration value
|
43
|
+
delegate :[], to: :options
|
44
|
+
|
45
|
+
# Set a specific configuration option
|
46
|
+
# @param key [Symbol] The configuration key
|
47
|
+
# @param value [Object] The configuration value
|
48
|
+
delegate :[]=, to: :options
|
49
|
+
end
|
50
|
+
|
51
|
+
# Get the pagination configuration
|
52
|
+
# @return [Hash] The pagination configuration
|
53
|
+
def self.pagination
|
54
|
+
options[:pagination]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Get the serialization configuration
|
58
|
+
# @return [Hash] The serialization configuration
|
59
|
+
def self.serialization
|
60
|
+
options[:serialization]
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get the error handling configuration
|
64
|
+
# @return [Hash] The error handling configuration
|
65
|
+
def self.error_handling
|
66
|
+
options[:error_handling]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterController
|
4
|
+
# Module providing logging capabilities
|
5
|
+
module Logging
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
# Set up a logger for the controller
|
10
|
+
class_attribute :better_controller_logger, default: Rails.logger if defined?(Rails)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Log a message at the info level
|
14
|
+
# @param message [String] The message to log
|
15
|
+
# @param tags [Hash] Additional tags for the log
|
16
|
+
def log_info(message, tags = {})
|
17
|
+
log(:info, message, tags)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Log a message at the debug level
|
21
|
+
# @param message [String] The message to log
|
22
|
+
# @param tags [Hash] Additional tags for the log
|
23
|
+
def log_debug(message, tags = {})
|
24
|
+
log(:debug, message, tags)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Log a message at the warn level
|
28
|
+
# @param message [String] The message to log
|
29
|
+
# @param tags [Hash] Additional tags for the log
|
30
|
+
def log_warn(message, tags = {})
|
31
|
+
log(:warn, message, tags)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Log a message at the error level
|
35
|
+
# @param message [String] The message to log
|
36
|
+
# @param tags [Hash] Additional tags for the log
|
37
|
+
def log_error(message, tags = {})
|
38
|
+
log(:error, message, tags)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Log a message at the fatal level
|
42
|
+
# @param message [String] The message to log
|
43
|
+
# @param tags [Hash] Additional tags for the log
|
44
|
+
def log_fatal(message, tags = {})
|
45
|
+
log(:fatal, message, tags)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Log an exception
|
49
|
+
# @param exception [Exception] The exception to log
|
50
|
+
# @param tags [Hash] Additional tags for the log
|
51
|
+
def log_exception(exception, tags = {})
|
52
|
+
return unless BetterController.config.error_handling[:log_errors]
|
53
|
+
|
54
|
+
tags = tags.merge(
|
55
|
+
exception_class: exception.class.name,
|
56
|
+
backtrace: exception.backtrace&.join("\n")
|
57
|
+
)
|
58
|
+
|
59
|
+
log_error(exception.message, tags)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Log a message at the specified level
|
65
|
+
# @param level [Symbol] The log level
|
66
|
+
# @param message [String] The message to log
|
67
|
+
# @param tags [Hash] Additional tags for the log
|
68
|
+
def log(level, message, tags = {})
|
69
|
+
return unless logger
|
70
|
+
|
71
|
+
tags = tags.merge(controller: self.class.name, action: action_name) if respond_to?(:action_name)
|
72
|
+
|
73
|
+
if tags.empty?
|
74
|
+
logger.send(level, message)
|
75
|
+
else
|
76
|
+
logger.send(level) { "[BetterController] #{message} #{tags.inspect}" }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Get the logger
|
81
|
+
# @return [Logger] The logger
|
82
|
+
def logger
|
83
|
+
self.class.better_controller_logger
|
84
|
+
end
|
85
|
+
|
86
|
+
# Module providing class methods for logging
|
87
|
+
module ClassMethods
|
88
|
+
# Set the logger for the controller
|
89
|
+
# @param logger [Logger] The logger to use
|
90
|
+
def logger=(logger)
|
91
|
+
self.better_controller_logger = logger
|
92
|
+
end
|
93
|
+
|
94
|
+
# Get the logger for the controller
|
95
|
+
# @return [Logger] The logger
|
96
|
+
def logger
|
97
|
+
better_controller_logger
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterController
|
4
|
+
# Error raised when a required method is not overridden
|
5
|
+
class MethodNotOverriddenError < StandardError
|
6
|
+
# Initialize a new error
|
7
|
+
# @param method_name [Symbol, String] The method that should be overridden
|
8
|
+
# @param instance [Object] The instance where the method should be overridden
|
9
|
+
def initialize(method_name, instance)
|
10
|
+
super("Method '#{method_name}' must be overridden in #{instance.class.name}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterController
|
4
|
+
# Module providing pagination functionality for ActiveRecord collections
|
5
|
+
module Pagination
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# Paginate a collection
|
9
|
+
# @param collection [ActiveRecord::Relation] The collection to paginate
|
10
|
+
# @param page [Integer] The page number
|
11
|
+
# @param per_page [Integer] The number of items per page
|
12
|
+
# @return [ActiveRecord::Relation] The paginated collection
|
13
|
+
def paginate(collection, page: nil, per_page: nil)
|
14
|
+
page = (page || params[:page] || 1).to_i
|
15
|
+
per_page = (per_page || params[:per_page] || 25).to_i
|
16
|
+
|
17
|
+
paginated = collection.page(page).per(per_page)
|
18
|
+
|
19
|
+
# Add pagination metadata
|
20
|
+
if respond_to?(:add_meta)
|
21
|
+
add_meta(:pagination, {
|
22
|
+
current_page: paginated.current_page,
|
23
|
+
total_pages: paginated.total_pages,
|
24
|
+
total_count: paginated.total_count,
|
25
|
+
per_page: per_page,
|
26
|
+
})
|
27
|
+
end
|
28
|
+
|
29
|
+
paginated
|
30
|
+
end
|
31
|
+
|
32
|
+
# Module providing class methods for pagination
|
33
|
+
module ClassMethods
|
34
|
+
# Configure pagination defaults
|
35
|
+
# @param options [Hash] Pagination options
|
36
|
+
def configure_pagination(options = {})
|
37
|
+
class_attribute :pagination_options, default: {
|
38
|
+
enabled: true,
|
39
|
+
per_page: 25,
|
40
|
+
}.merge(options)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterController
|
4
|
+
# Module providing parameter validation helpers for controllers
|
5
|
+
module ParameterValidation
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# Class methods for parameter validation
|
9
|
+
module ClassMethods
|
10
|
+
# Define required parameters for an action
|
11
|
+
# @param action_name [Symbol] The name of the action
|
12
|
+
# @param params [Array<Symbol>] The required parameters
|
13
|
+
def requires_params(action_name, *params)
|
14
|
+
before_action only: action_name do
|
15
|
+
validate_required_params(*params)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Define parameter schema for an action
|
20
|
+
# @param action_name [Symbol] The name of the action
|
21
|
+
# @param schema [Hash] The parameter schema
|
22
|
+
def param_schema(action_name, schema)
|
23
|
+
before_action only: action_name do
|
24
|
+
validate_param_schema(schema)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Instance methods for parameter validation
|
30
|
+
|
31
|
+
# Validate that required parameters are present
|
32
|
+
# @param params [Array<Symbol>] The required parameters
|
33
|
+
# @raise [BetterController::Error] If a required parameter is missing
|
34
|
+
def validate_required_params(*params)
|
35
|
+
missing_params = []
|
36
|
+
|
37
|
+
params.each do |param|
|
38
|
+
missing_params << param unless parameter_present?(param)
|
39
|
+
end
|
40
|
+
|
41
|
+
unless missing_params.empty?
|
42
|
+
error_message = "Missing required parameters: #{missing_params.join(', ')}"
|
43
|
+
raise BetterController::Error, error_message
|
44
|
+
end
|
45
|
+
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
# Validate parameters against a schema
|
50
|
+
# @param schema [Hash] The parameter schema
|
51
|
+
# @raise [BetterController::Error] If parameters don't match the schema
|
52
|
+
def validate_param_schema(schema)
|
53
|
+
errors = []
|
54
|
+
|
55
|
+
schema.each do |param, rules|
|
56
|
+
value = params[param]
|
57
|
+
|
58
|
+
if rules[:required] && value.nil?
|
59
|
+
errors << "#{param} is required"
|
60
|
+
next
|
61
|
+
end
|
62
|
+
|
63
|
+
next if value.nil?
|
64
|
+
|
65
|
+
errors << "#{param} must be a #{rules[:type]}" if rules[:type] && !value.is_a?(rules[:type])
|
66
|
+
|
67
|
+
errors << "#{param} must be one of: #{rules[:in].join(', ')}" if rules[:in]&.exclude?(value)
|
68
|
+
|
69
|
+
errors << "#{param} has invalid format" if rules[:format] && rules[:format] !~ value.to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
raise BetterController::Error, errors.join(', ') unless errors.empty?
|
73
|
+
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# Check if a parameter is present
|
80
|
+
# @param param [Symbol] The parameter to check
|
81
|
+
# @return [Boolean] Whether the parameter is present
|
82
|
+
def parameter_present?(param)
|
83
|
+
params.key?(param.to_s) || params.key?(param.to_sym)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|