securial 1.0.0 → 1.0.2

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +4 -0
  3. data/README.md +14 -9
  4. data/app/controllers/concerns/securial/identity.rb +91 -2
  5. data/app/controllers/securial/accounts_controller.rb +68 -5
  6. data/app/controllers/securial/application_controller.rb +34 -2
  7. data/app/controllers/securial/passwords_controller.rb +44 -4
  8. data/app/controllers/securial/role_assignments_controller.rb +55 -4
  9. data/app/controllers/securial/roles_controller.rb +54 -0
  10. data/app/controllers/securial/sessions_controller.rb +77 -3
  11. data/app/controllers/securial/status_controller.rb +24 -0
  12. data/app/controllers/securial/users_controller.rb +54 -0
  13. data/app/jobs/securial/application_job.rb +9 -0
  14. data/app/mailers/securial/application_mailer.rb +12 -0
  15. data/app/mailers/securial/securial_mailer.rb +30 -0
  16. data/app/models/concerns/securial/password_resettable.rb +70 -0
  17. data/app/models/securial/application_record.rb +19 -0
  18. data/app/models/securial/current.rb +13 -0
  19. data/app/models/securial/role.rb +17 -0
  20. data/app/models/securial/role_assignment.rb +16 -0
  21. data/app/models/securial/session.rb +79 -1
  22. data/app/models/securial/user.rb +34 -0
  23. data/bin/securial +6 -23
  24. data/lib/generators/factory_bot/model/model_generator.rb +1 -0
  25. data/lib/generators/securial/install/install_generator.rb +2 -2
  26. data/lib/generators/securial/install/views_generator.rb +2 -1
  27. data/lib/generators/securial/jbuilder/jbuilder_generator.rb +2 -0
  28. data/lib/generators/securial/scaffold/scaffold_generator.rb +2 -0
  29. data/lib/securial/auth/auth_encoder.rb +3 -3
  30. data/lib/securial/auth/session_creator.rb +1 -1
  31. data/lib/securial/auth/token_generator.rb +13 -13
  32. data/lib/securial/auth.rb +44 -6
  33. data/lib/securial/cli.rb +282 -0
  34. data/lib/securial/config/signature.rb +1 -1
  35. data/lib/securial/config/validation.rb +44 -45
  36. data/lib/securial/config.rb +63 -17
  37. data/lib/securial/engine.rb +41 -0
  38. data/lib/securial/error/auth.rb +52 -0
  39. data/lib/securial/error/base_securial_error.rb +56 -3
  40. data/lib/securial/error/config.rb +33 -0
  41. data/lib/securial/error.rb +33 -3
  42. data/lib/securial/helpers/key_transformer.rb +1 -1
  43. data/lib/securial/helpers/normalizing_helper.rb +1 -1
  44. data/lib/securial/helpers/regex_helper.rb +6 -7
  45. data/lib/securial/helpers/roles_helper.rb +6 -7
  46. data/lib/securial/helpers.rb +48 -4
  47. data/lib/securial/logger/broadcaster.rb +89 -1
  48. data/lib/securial/logger/builder.rb +54 -1
  49. data/lib/securial/logger/formatter.rb +73 -0
  50. data/lib/securial/logger.rb +48 -8
  51. data/lib/securial/middleware.rb +40 -9
  52. data/lib/securial/security/request_rate_limiter.rb +48 -2
  53. data/lib/securial/security.rb +37 -6
  54. data/lib/securial/version.rb +8 -1
  55. data/lib/securial.rb +40 -4
  56. metadata +15 -11
  57. data/lib/securial/cli/run.rb +0 -11
  58. data/lib/securial/cli/securial_new.rb +0 -53
  59. data/lib/securial/cli/show_help.rb +0 -26
  60. data/lib/securial/cli/show_version.rb +0 -9
@@ -1,28 +1,74 @@
1
+ # @title Securial Configuration System
2
+ #
3
+ # Configuration management for the Securial framework.
4
+ #
5
+ # This file defines the configuration system for Securial, providing mechanisms
6
+ # to set, retrieve, and validate configuration options. It uses a conventional
7
+ # Ruby configuration block pattern to allow applications to customize Securial's
8
+ # behavior through an easy-to-use interface.
9
+ #
10
+ # @example Basic configuration
11
+ # # In config/initializers/securial.rb
12
+ # Securial.configure do |config|
13
+ # config.jwt_secret = ENV["JWT_SECRET"]
14
+ # config.token_expiry = 2.hours
15
+ # config.refresh_token_expiry = 7.days
16
+ # config.session_timeout = 1.hour
17
+ # end
18
+ #
19
+ # @example Accessing configuration values
20
+ # # Get the configured token expiry
21
+ # expiry = Securial.configuration.token_expiry
22
+ #
1
23
  require "securial/config/configuration"
2
24
  require "securial/config/signature"
3
25
  require "securial/config/validation"
4
26
 
5
-
6
27
  module Securial
7
- class << self
8
- attr_accessor :configuration
9
-
10
- def configuration
11
- @configuration ||= Config::Configuration.new
12
- end
28
+ extend self
13
29
 
14
- def configuration=(config)
15
- if config.is_a?(Config::Configuration)
16
- @configuration = config
17
- Securial::Config::Validation.validate_all!(configuration)
18
- else
19
- raise ArgumentError, "Expected an instance of Securial::Config::Configuration"
20
- end
21
- end
30
+ # Gets the current configuration instance.
31
+ #
32
+ # Returns the existing configuration or creates a new default configuration
33
+ # if one hasn't been set yet.
34
+ #
35
+ # @return [Securial::Config::Configuration] the current configuration instance
36
+ def configuration
37
+ @configuration ||= Config::Configuration.new
38
+ end
22
39
 
23
- def configure
24
- yield(configuration) if block_given?
40
+ # Sets the configuration instance.
41
+ #
42
+ # Validates the provided configuration to ensure all required settings
43
+ # are present and have valid values.
44
+ #
45
+ # @param config [Securial::Config::Configuration] the configuration instance to use
46
+ # @raise [ArgumentError] if the provided object is not a Configuration instance
47
+ # @raise [Securial::Error::Config::ValidationError] if the configuration is invalid
48
+ # @return [Securial::Config::Configuration] the newly set configuration
49
+ #
50
+ def configuration=(config)
51
+ if config.is_a?(Config::Configuration)
52
+ @configuration = config
25
53
  Securial::Config::Validation.validate_all!(configuration)
54
+ else
55
+ raise ArgumentError, "Expected an instance of Securial::Config::Configuration"
26
56
  end
27
57
  end
58
+
59
+ # Configures the Securial framework using a block.
60
+ #
61
+ # This method provides the conventional Ruby configuration block pattern,
62
+ # yielding the current configuration instance to the provided block for
63
+ # modification. After the block executes, the configuration is validated.
64
+ #
65
+ # @yield [config] Yields the configuration instance to the block
66
+ # @yieldparam config [Securial::Config::Configuration] the configuration instance
67
+ # @raise [Securial::Error::Config::ValidationError] if the configuration is invalid
68
+ # @return [Securial::Config::Configuration] the updated configuration
69
+ def configure
70
+ yield(configuration) if block_given?
71
+ Securial::Config::Validation.validate_all!(configuration)
72
+ configuration
73
+ end
28
74
  end
@@ -1,3 +1,25 @@
1
+ # @title Securial Rails Engine
2
+ #
3
+ # Rails engine configuration for the Securial framework.
4
+ #
5
+ # This file defines the core Rails engine that powers Securial, setting up
6
+ # namespace isolation, generator defaults, and loading all required components.
7
+ # The engine configuration establishes how Securial integrates with host Rails
8
+ # applications and defines the conventions used throughout the codebase.
9
+ #
10
+ # @example Mounting the engine in a Rails application
11
+ # # In config/routes.rb
12
+ # Rails.application.routes.draw do
13
+ # mount Securial::Engine => '/securial'
14
+ # end
15
+ #
16
+ # @example Accessing engine routes in views or controllers
17
+ # # Generate path to Securial login
18
+ # securial.login_path
19
+ #
20
+ # # Generate URL to Securial user profile
21
+ # securial.user_url(@user)
22
+ #
1
23
  require "securial/auth"
2
24
  require "securial/config"
3
25
  require "securial/error"
@@ -7,11 +29,29 @@ require "securial/middleware"
7
29
  require "securial/security"
8
30
 
9
31
  module Securial
32
+ # Rails engine implementation for the Securial framework.
33
+ #
34
+ # This class defines the core engine that powers Securial's integration with
35
+ # Rails applications. It isolates all Securial components within their own
36
+ # namespace to prevent conflicts with host application code and configures
37
+ # generator defaults to ensure consistent code generation.
38
+ #
39
+ # The engine handles:
40
+ # - Namespace isolation to prevent conflicts
41
+ # - Configuration of generators for consistent code generation
42
+ # - Setting up Securial's API-only mode for JSON responses
43
+ # - Loading initializers for various framework components
44
+ #
45
+ # @see https://guides.rubyonrails.org/engines.html Rails Engines Guide
46
+ #
10
47
  class Engine < ::Rails::Engine
48
+ # Isolates the Securial namespace to prevent conflicts with host applications
11
49
  isolate_namespace Securial
12
50
 
51
+ # Configures the engine for API-only mode by default
13
52
  config.generators.api_only = true
14
53
 
54
+ # Sets up standard generator configurations for consistent code generation
15
55
  config.generators do |g|
16
56
  g.orm :active_record, primary_key_type: :string
17
57
  g.test_framework :rspec,
@@ -28,4 +68,5 @@ module Securial
28
68
  end
29
69
  end
30
70
 
71
+ # Load engine initializers that configure various aspects of the framework
31
72
  require_relative "engine_initializers"
@@ -1,18 +1,70 @@
1
+ # @title Securial Authentication Errors
2
+ #
3
+ # Authentication-related errors for the Securial framework.
4
+ #
5
+ # This file defines error classes for authentication-related issues in the
6
+ # Securial framework, such as token encoding/decoding failures, expired tokens,
7
+ # and revoked sessions. These errors help identify and troubleshoot problems
8
+ # with the authentication system during runtime.
9
+ #
10
+ # @example Handling authentication errors
11
+ # begin
12
+ # decoded_token = Securial::Auth::AuthEncoder.decode(token)
13
+ # rescue Securial::Error::Auth::TokenDecodeError => e
14
+ # # Handle invalid token format
15
+ # logger.warn("Invalid token format: #{e.message}")
16
+ # rescue Securial::Error::Auth::TokenExpiredError => e
17
+ # # Handle expired token
18
+ # render json: { error: "Your session has expired. Please log in again." }, status: :unauthorized
19
+ # rescue Securial::Error::Auth::TokenRevokedError => e
20
+ # # Handle revoked token
21
+ # render json: { error: "Your session has been revoked." }, status: :unauthorized
22
+ # end
23
+ #
1
24
  module Securial
2
25
  module Error
26
+ # Namespace for authentication-related errors in the Securial framework.
27
+ #
28
+ # These errors are raised during authentication operations such as
29
+ # token encoding/decoding, session validation, and access control checks.
30
+ #
3
31
  module Auth
32
+ # Error raised when a JWT token cannot be encoded properly.
33
+ #
34
+ # This may occur due to invalid payload data, missing required claims,
35
+ # or issues with the signing key.
36
+ #
37
+ # @see Securial::Auth::AuthEncoder#encode
38
+ #
4
39
  class TokenEncodeError < BaseError
5
40
  default_message "Error while encoding session token"
6
41
  end
7
42
 
43
+ # Error raised when a JWT token cannot be decoded or validated.
44
+ #
45
+ # This may occur due to malformed tokens, invalid signatures,
46
+ # or tokens that don't match the expected format.
47
+ #
48
+ # @see Securial::Auth::AuthEncoder#decode
49
+ #
8
50
  class TokenDecodeError < BaseError
9
51
  default_message "Error while decoding session token"
10
52
  end
11
53
 
54
+ # Error raised when attempting to use a token from a revoked session.
55
+ #
56
+ # This occurs when a token references a session that has been
57
+ # explicitly revoked, such as after a user logout or account suspension.
58
+ #
12
59
  class TokenRevokedError < BaseError
13
60
  default_message "Session token is revoked"
14
61
  end
15
62
 
63
+ # Error raised when attempting to use an expired token.
64
+ #
65
+ # This occurs when a token's expiration time (exp claim) has passed,
66
+ # indicating that the token is no longer valid for authentication.
67
+ #
16
68
  class TokenExpiredError < BaseError
17
69
  default_message "Session token is expired"
18
70
  end
@@ -1,15 +1,68 @@
1
+ # @title Securial Base Error
2
+ #
3
+ # Base error class for the Securial framework.
4
+ #
5
+ # This file defines the base error class that all other specialized Securial
6
+ # errors inherit from. It provides functionality for default error messages
7
+ # and customized backtrace handling.
8
+ #
1
9
  module Securial
2
10
  module Error
11
+ # Base error class for all Securial-specific exceptions.
12
+ #
13
+ # This class serves as the foundation for Securial's error hierarchy, providing
14
+ # common functionality like default messages and backtrace customization.
15
+ # All specialized error types in Securial should inherit from this class.
16
+ #
17
+ # @example Defining a custom error class
18
+ # module Securial
19
+ # module Error
20
+ # class AuthError < BaseError
21
+ # default_message "Authentication failed"
22
+ # end
23
+ # end
24
+ # end
25
+ #
26
+ # @example Raising a custom error
27
+ # raise Securial::Error::AuthError
28
+ # # => Authentication failed (Securial::Error::AuthError)
29
+ #
30
+ # raise Securial::Error::AuthError, "Invalid token provided"
31
+ # # => Invalid token provided (Securial::Error::AuthError)
32
+ #
3
33
  class BaseError < StandardError
34
+ class_attribute :_default_message, instance_writer: false
35
+
36
+ # Sets or gets the default message for this error class.
37
+ #
38
+ # This class method allows error classes to define a standard message
39
+ # that will be used when no explicit message is provided at instantiation.
40
+ #
41
+ # @param message [String, nil] The default message to set for this error class
42
+ # @return [String, nil] The current default message for this error class
43
+ #
4
44
  def self.default_message(message = nil)
5
- @default_message = message if message
6
- @default_message
45
+ self._default_message = message if message
46
+ self._default_message
7
47
  end
8
48
 
49
+ # Initializes a new error instance with the provided message or default.
50
+ #
51
+ # @param message [String, nil] The error message or nil to use default
52
+ # @return [BaseError] New error instance
53
+ #
9
54
  def initialize(message = nil)
10
- super(message || self.class.default_message || "An error occurred in Securial")
55
+ super(message || self.class._default_message || "An error occurred in Securial")
11
56
  end
12
57
 
58
+ # Returns an empty backtrace.
59
+ #
60
+ # This method overrides the standard backtrace behavior to return an empty array,
61
+ # which helps keep error responses clean without showing internal implementation
62
+ # details in production environments.
63
+ #
64
+ # @return [Array] An empty array
65
+ #
13
66
  def backtrace; []; end
14
67
  end
15
68
  end
@@ -1,6 +1,39 @@
1
+ # @title Securial Configuration Errors
2
+ #
3
+ # Configuration-related errors for the Securial framework.
4
+ #
5
+ # This file defines error classes for configuration-related issues in the
6
+ # Securial framework, such as missing or invalid configuration options.
7
+ # These errors help identify and troubleshoot problems with application setup.
8
+ #
1
9
  module Securial
2
10
  module Error
11
+ # Namespace for configuration-related errors in the Securial framework.
12
+ #
13
+ # These errors are raised when the Securial configuration is invalid,
14
+ # missing required values, or contains values that don't meet validation
15
+ # requirements.
16
+ #
17
+ # @example Handling configuration errors
18
+ # begin
19
+ # Securial.configure do |config|
20
+ # # Configure Securial settings
21
+ # end
22
+ # rescue Securial::Error::Config::InvalidConfigurationError => e
23
+ # Rails.logger.error("Securial configuration error: #{e.message}")
24
+ # # Handle configuration error
25
+ # end
26
+ #
3
27
  module Config
28
+ # Error raised when Securial configuration is invalid.
29
+ #
30
+ # This error is typically raised during application initialization when
31
+ # the provided configuration doesn't meet requirements, such as missing
32
+ # required settings or having values that fail validation.
33
+ #
34
+ # @example Raising a configuration error
35
+ # raise InvalidConfigurationError, "JWT secret is missing"
36
+ #
4
37
  class InvalidConfigurationError < Securial::Error::BaseError
5
38
  default_message "Invalid configuration for Securial"
6
39
  end
@@ -1,9 +1,39 @@
1
+ # Requires all error classes used in the Securial framework.
2
+ #
3
+ # This file serves as the central error management system for Securial,
4
+ # loading all specialized error types and establishing the Error namespace.
5
+ # Each error type extends from BaseSecurialError and provides specific
6
+ # error handling for different parts of the framework.
7
+ #
8
+ # @example Accessing a specific error type
9
+ # begin
10
+ # # Some operation that might fail
11
+ # rescue Securial::Error::Auth::TokenDecodeError => e
12
+ # # Handle token decoding errors
13
+ # rescue Securial::Error::Config::ValidationError => e
14
+ # # Handle configuration validation errors
15
+ # rescue Securial::Error::BaseError => e
16
+ # # Handle any other Securial errors
17
+ # end
18
+ #
1
19
  require "securial/error/base_securial_error"
2
20
  require "securial/error/config"
3
21
  require "securial/error/auth"
4
22
 
5
23
  module Securial
6
- module Error
7
- # This serves as a namespace for all Securial errors.
8
- end
24
+ # Namespace for all Securial-specific errors and exceptions.
25
+ #
26
+ # The Error module contains specialized error classes for different
27
+ # components of the Securial framework, organized into submodules
28
+ # for better categorization:
29
+ #
30
+ # - {BaseError} - Base class for all Securial errors
31
+ # - {Config} - Configuration-related errors
32
+ # - {Auth} - Authentication and authorization errors
33
+ #
34
+ # @see Securial::Error::BaseError
35
+ # @see Securial::Error::Config
36
+ # @see Securial::Error::Auth
37
+ #
38
+ module Error; end
9
39
  end
@@ -1,7 +1,7 @@
1
1
  module Securial
2
2
  module Helpers
3
3
  module KeyTransformer
4
- module_function
4
+ extend self
5
5
 
6
6
  def camelize(str, format)
7
7
  case format
@@ -1,7 +1,7 @@
1
1
  module Securial
2
2
  module Helpers
3
3
  module NormalizingHelper
4
- module_function
4
+ extend self
5
5
 
6
6
  def normalize_email_address(email)
7
7
  return "" if email.empty?
@@ -5,14 +5,13 @@ module Securial
5
5
  USERNAME_REGEX = /\A(?![0-9])[a-zA-Z](?:[a-zA-Z0-9]|[._](?![._]))*[a-zA-Z0-9]\z/
6
6
  PASSWORD_REGEX = %r{\A(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9])[a-zA-Z].*\z}
7
7
 
8
- class << self
9
- def valid_email?(email)
10
- email.match?(EMAIL_REGEX)
11
- end
8
+ extend self
9
+ def valid_email?(email)
10
+ email.match?(EMAIL_REGEX)
11
+ end
12
12
 
13
- def valid_username?(username)
14
- username.match?(USERNAME_REGEX)
15
- end
13
+ def valid_username?(username)
14
+ username.match?(USERNAME_REGEX)
16
15
  end
17
16
  end
18
17
  end
@@ -3,14 +3,13 @@ module Securial
3
3
  module RolesHelper
4
4
  # This module provides helper methods related to roles.
5
5
  # It can be extended with additional role-related functionality as needed.
6
- class << self
7
- def protected_namespace
8
- Securial.configuration.admin_role.to_s.strip.underscore.pluralize
9
- end
6
+ extend self
7
+ def protected_namespace
8
+ Securial.configuration.admin_role.to_s.strip.underscore.pluralize
9
+ end
10
10
 
11
- def titleized_admin_role
12
- Securial.configuration.admin_role.to_s.strip.titleize
13
- end
11
+ def titleized_admin_role
12
+ Securial.configuration.admin_role.to_s.strip.titleize
14
13
  end
15
14
  end
16
15
  end
@@ -1,11 +1,55 @@
1
+ # @title Securial Helper Utilities
2
+ #
3
+ # Centralized collection of helper modules for the Securial framework.
4
+ #
5
+ # This file serves as the entry point for all helper functionality in Securial,
6
+ # loading specialized utility modules that provide common functions used
7
+ # throughout the framework. These helpers handle tasks such as normalization,
8
+ # pattern matching, role management, and data transformation, making common
9
+ # operations consistent across the codebase.
10
+ #
11
+ # @example Normalizing user input
12
+ # # For normalizing email addresses (converts to lowercase)
13
+ # email = Securial::Helpers::NormalizingHelper.normalize_email("USER@EXAMPLE.COM")
14
+ # # => "user@example.com"
15
+ #
16
+ # @example Validating input format
17
+ # # For validating email format
18
+ # valid = Securial::Helpers::RegexHelper.valid_email?("user@example.com")
19
+ # # => true
20
+ #
21
+ # invalid = Securial::Helpers::RegexHelper.valid_email?("not-an-email")
22
+ # # => false
23
+ #
24
+ # @example Transforming data structure keys
25
+ # # For transforming keys in response data (snake_case to camelCase)
26
+ # data = { user_id: 1, first_name: "John", last_name: "Doe" }
27
+ # json = Securial::Helpers::KeyTransformer.transform(data, :lower_camel_case)
28
+ # # => { userId: 1, firstName: "John", lastName: "Doe" }
29
+ #
1
30
  require "securial/helpers/normalizing_helper"
2
31
  require "securial/helpers/regex_helper"
3
32
  require "securial/helpers/roles_helper"
4
33
  require "securial/helpers/key_transformer"
5
34
 
6
35
  module Securial
7
- module Helpers
8
- # This module acts as a namespace for helper modules.
9
- # It requires all helper modules to make them available for consumers.
10
- end
36
+ # Namespace containing utility modules for common operations.
37
+ #
38
+ # The Helpers module serves as a container for specialized utility modules
39
+ # that provide reusable functionality across the Securial framework:
40
+ #
41
+ # - {NormalizingHelper} - Methods for normalizing user input (emails, usernames, etc.)
42
+ # - {RegexHelper} - Regular expressions and validation methods for input validation
43
+ # - {RolesHelper} - Functions for working with user roles and permissions management
44
+ # - {KeyTransformer} - Tools for transforming data structure keys between formats (snake_case, camelCase)
45
+ #
46
+ # These helpers ensure consistent behavior across the application and reduce
47
+ # code duplication by centralizing common operations.
48
+ #
49
+ # @see Securial::Helpers::NormalizingHelper
50
+ # @see Securial::Helpers::RegexHelper
51
+ # @see Securial::Helpers::RolesHelper
52
+ # @see Securial::Helpers::KeyTransformer
53
+ #
54
+ module Helpers; end
11
55
  end
@@ -1,36 +1,109 @@
1
+ # @title Securial Logger Broadcaster
2
+ #
3
+ # Logger broadcasting utility for the Securial framework.
4
+ #
5
+ # This file defines a broadcaster class that sends logging messages to multiple
6
+ # destination loggers simultaneously. This enables unified logging across different
7
+ # outputs (like console and file) while maintaining appropriate formatting for each.
8
+ #
9
+ # @example Creating a broadcaster with multiple loggers
10
+ # # Create individual loggers
11
+ # file_logger = Logger.new('application.log')
12
+ # stdout_logger = Logger.new(STDOUT)
13
+ #
14
+ # # Combine them into a broadcaster
15
+ # broadcaster = Securial::Logger::Broadcaster.new([file_logger, stdout_logger])
16
+ #
17
+ # # Log messages go to both destinations
18
+ # broadcaster.info("Processing authentication request")
19
+ #
1
20
  module Securial
2
21
  module Logger
22
+ # Broadcasts log messages to multiple logger instances simultaneously.
23
+ #
24
+ # This class implements the Logger interface and forwards all logging calls
25
+ # to a collection of underlying loggers. This allows unified logging to
26
+ # multiple destinations (e.g., file and stdout) while maintaining specific
27
+ # formatting for each destination.
28
+ #
29
+ # @example Using a broadcaster with two loggers
30
+ # file_logger = Logger.new('app.log')
31
+ # console_logger = Logger.new(STDOUT)
32
+ # broadcaster = Broadcaster.new([file_logger, console_logger])
33
+ #
34
+ # # This logs to both destinations
35
+ # broadcaster.info("User logged in")
36
+ #
3
37
  class Broadcaster
38
+ # Initializes a new broadcaster with the provided loggers.
39
+ #
40
+ # @param loggers [Array<Logger>] Collection of logger instances
41
+ # @return [Broadcaster] New broadcaster instance
4
42
  def initialize(loggers)
5
43
  @loggers = loggers
6
44
  end
7
45
 
46
+ # Dynamically define logging methods for each severity level
47
+ # (debug, info, warn, error, fatal, unknown)
8
48
  ::Logger::Severity.constants.each do |severity|
9
49
  define_method(severity.downcase) do |*args, &block|
10
50
  @loggers.each { |logger| logger.public_send(severity.downcase, *args, &block) }
11
51
  end
12
52
  end
13
53
 
54
+ # Writes a message to each underlying logger.
55
+ #
56
+ # @param msg [String] Message to be written
57
+ # @return [Array] Results from each logger's << method
14
58
  def <<(msg)
15
59
  @loggers.each { |logger| logger << msg }
16
60
  end
17
61
 
62
+ # Closes all underlying loggers.
63
+ #
64
+ # @return [void]
18
65
  def close
19
66
  @loggers.each(&:close)
20
67
  end
21
68
 
69
+ # No-op method to satisfy the Logger interface.
70
+ #
71
+ # Since each underlying logger has its own formatter, setting a
72
+ # formatter on the broadcaster is not supported.
73
+ #
74
+ # @param _formatter [Object] Ignored formatter object
75
+ # @return [void]
22
76
  def formatter=(_formatter)
23
- # Do nothing
77
+ # Do nothing - each logger maintains its own formatter
24
78
  end
25
79
 
80
+ # Returns nil to satisfy the Logger interface.
81
+ #
82
+ # Since each underlying logger has its own formatter, there is
83
+ # no single formatter for the broadcaster.
84
+ #
85
+ # @return [nil] Always returns nil
26
86
  def formatter
27
87
  nil
28
88
  end
29
89
 
90
+ # Returns the collection of managed loggers.
91
+ #
92
+ # @return [Array<Logger>] All loggers managed by this broadcaster
93
+ #
30
94
  def loggers
31
95
  @loggers
32
96
  end
33
97
 
98
+ # Executes a block with the specified tags added to the log.
99
+ #
100
+ # Supports ActiveSupport::TaggedLogging by forwarding tagged blocks
101
+ # to all underlying loggers that support tagging.
102
+ #
103
+ # @param tags [Array<String>] Tags to apply to log messages
104
+ # @yield Block to be executed with the tags applied
105
+ # @return [Object] Result of the block
106
+ #
34
107
  def tagged(*tags, &block)
35
108
  # If all loggers support tagged, nest the calls, otherwise just yield.
36
109
  taggable_loggers = @loggers.select { |logger| logger.respond_to?(:tagged) }
@@ -44,10 +117,25 @@ module Securial
44
117
  end
45
118
  end
46
119
 
120
+ # Checks if the broadcaster responds to the given method.
121
+ #
122
+ # @param method [Symbol] Method name to check
123
+ # @param include_private [Boolean] Whether to include private methods
124
+ # @return [Boolean] True if any of the underlying loggers respond to the method
47
125
  def respond_to_missing?(method, include_private = false)
48
126
  @loggers.any? { |logger| logger.respond_to?(method, include_private) } || super
49
127
  end
50
128
 
129
+ # Delegates missing methods to all underlying loggers.
130
+ #
131
+ # If all loggers respond to the method, it will be called on each logger.
132
+ # Otherwise, raises a NoMethodError.
133
+ #
134
+ # @param method [Symbol] Method name to call
135
+ # @param args [Array] Arguments to pass to the method
136
+ # @param block [Proc] Block to pass to the method
137
+ # @return [Array] Results from each logger's method call
138
+ # @raise [NoMethodError] If any logger doesn't respond to the method
51
139
  def method_missing(method, *args, &block)
52
140
  if @loggers.all? { |logger| logger.respond_to?(method) }
53
141
  @loggers.map { |logger| logger.send(method, *args, &block) }