Module: PricingPlans::Callbacks
- Defined in:
- lib/pricing_plans/callbacks.rb
Overview
Centralized callback dispatch module with error isolation. Callbacks should never break the main operation - errors are logged but not raised.
Class Method Summary collapse
-
.check_and_emit_limit_exceeded!(plan_owner, limit_key, current_usage, limit_config) ⇒ Object
Check if limit is exceeded and handle grace state (for grace_then_block policy).
-
.check_and_emit_warnings!(plan_owner, limit_key, current_usage, limit_amount) ⇒ Object
Check warning thresholds and emit warning event if a new threshold is crossed.
-
.dispatch(event_type, limit_key, *args) ⇒ Object
Dispatch a callback event with error isolation.
-
.execute_safely(handler, event_type, limit_key, args) ⇒ Object
Execute callback with error isolation and arity handling.
- .log_debug(message) ⇒ Object
-
.log_error(message) ⇒ Object
Safe logging that works with or without Rails.
- .log_warn(message) ⇒ Object
Class Method Details
.check_and_emit_limit_exceeded!(plan_owner, limit_key, current_usage, limit_config) ⇒ Object
Check if limit is exceeded and handle grace state (for grace_then_block policy). This is called after a successful model creation, so it only handles:
- :just_warn - emit additional warning
- :grace_then_block - start grace period if exceeded and not already in grace
NOTE: Block events are NOT emitted here because this runs after successful creation. Block events are emitted in Limitable validation when creation is actually blocked.
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/pricing_plans/callbacks.rb', line 104 def check_and_emit_limit_exceeded!(plan_owner, limit_key, current_usage, limit_config) return unless limit_config return if limit_config[:to] == :unlimited limit_amount = limit_config[:to].to_i after_limit = limit_config[:after_limit] case after_limit when :just_warn return unless current_usage >= limit_amount # Just emit warning, don't track grace/block check_and_emit_warnings!(plan_owner, limit_key, current_usage, limit_amount) when :block_usage return unless current_usage >= limit_amount # Do NOT mark as blocked here - this callback runs after SUCCESSFUL creation. # Block events are emitted from validation when creation is actually blocked. nil when :grace_then_block # Grace semantics are for over-limit usage, not exact-at-limit. return unless current_usage > limit_amount # Start grace period if not already in grace/blocked unless GraceManager.grace_active?(plan_owner, limit_key) || GraceManager.should_block?(plan_owner, limit_key) GraceManager.mark_exceeded!(plan_owner, limit_key, grace_period: limit_config[:grace]) end end end |
.check_and_emit_warnings!(plan_owner, limit_key, current_usage, limit_amount) ⇒ Object
Check warning thresholds and emit warning event if a new threshold is crossed. This is the main entry point for automatic warning detection.
78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/pricing_plans/callbacks.rb', line 78 def check_and_emit_warnings!(plan_owner, limit_key, current_usage, limit_amount) return if limit_amount == :unlimited || limit_amount.to_i.zero? percent_used = (current_usage.to_f / limit_amount) * 100 thresholds = LimitChecker.warning_thresholds(plan_owner, limit_key) # Find the highest threshold that has been crossed crossed_threshold = thresholds.select { |t| percent_used >= (t * 100) }.max return unless crossed_threshold # Emit warning if this is a new higher threshold GraceManager.maybe_emit_warning!(plan_owner, limit_key, crossed_threshold) end |
.dispatch(event_type, limit_key, *args) ⇒ Object
Dispatch a callback event with error isolation. Fires specific handler first (if exists), then wildcard handler (if exists). Callbacks should never break the main operation.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/pricing_plans/callbacks.rb', line 16 def dispatch(event_type, limit_key, *args) handlers = Registry.event_handlers[event_type] || {} # Build full args with limit_key injected after plan_owner # Input args: [plan_owner, ...event_specific_args] # Output: [plan_owner, limit_key, ...event_specific_args] plan_owner = args.first event_args = args.drop(1) full_args = [plan_owner, limit_key, *event_args] # Fire specific handler first specific_handler = handlers[limit_key] execute_safely(specific_handler, event_type, limit_key, full_args) if specific_handler.is_a?(Proc) # Fire wildcard handler second wildcard_handler = handlers[:_all] execute_safely(wildcard_handler, event_type, limit_key, full_args) if wildcard_handler.is_a?(Proc) end |
.execute_safely(handler, event_type, limit_key, args) ⇒ Object
Execute callback with error isolation and arity handling. Supports callbacks with varying argument counts for backwards compatibility.
Backward compatibility:
- Arity 2 (old style): receives (plan_owner, last_arg) - skips limit_key
- Arity 3+ (new style): receives (plan_owner, limit_key, ...rest)
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/pricing_plans/callbacks.rb', line 46 def execute_safely(handler, event_type, limit_key, args) case handler.arity when 0 handler.call when 1 handler.call(args[0]) when 2 # Backward compatibility: old callbacks expect (plan_owner, event_arg) # where event_arg is threshold for warnings, grace_ends_at for grace_start. # Skip limit_key (args[1]) and pass plan_owner + last arg. # For on_block (args = [plan_owner, limit_key]), this passes (plan_owner, limit_key). handler.call(args[0], args.last) when 3 handler.call(args[0], args[1], args[2]) when -1, -2, -3 # Variable arity (splat args) handler.call(*args) else handler.call(*args.first(handler.arity.abs)) end rescue StandardError => e # Log but don't re-raise - callbacks should never break model creation log_error("[PricingPlans] Callback error for #{event_type}:#{limit_key}: #{e.class}: #{e.}") log_debug(e.backtrace&.join("\n")) end |
.log_debug(message) ⇒ Object
151 152 153 154 155 156 157 |
# File 'lib/pricing_plans/callbacks.rb', line 151 def log_debug() return unless if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger&.debug? Rails.logger.debug() end end |
.log_error(message) ⇒ Object
Safe logging that works with or without Rails
135 136 137 138 139 140 141 |
# File 'lib/pricing_plans/callbacks.rb', line 135 def log_error() if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger Rails.logger.error() elsif PricingPlans.configuration&.debug warn end end |
.log_warn(message) ⇒ Object
143 144 145 146 147 148 149 |
# File 'lib/pricing_plans/callbacks.rb', line 143 def log_warn() if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger Rails.logger.warn() elsif PricingPlans.configuration&.debug warn end end |