better_model 2.1.0 → 3.0.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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +96 -13
  3. data/lib/better_model/archivable.rb +203 -91
  4. data/lib/better_model/errors/archivable/already_archived_error.rb +11 -0
  5. data/lib/better_model/errors/archivable/archivable_error.rb +13 -0
  6. data/lib/better_model/errors/archivable/configuration_error.rb +10 -0
  7. data/lib/better_model/errors/archivable/not_archived_error.rb +11 -0
  8. data/lib/better_model/errors/archivable/not_enabled_error.rb +11 -0
  9. data/lib/better_model/errors/better_model_error.rb +9 -0
  10. data/lib/better_model/errors/permissible/configuration_error.rb +9 -0
  11. data/lib/better_model/errors/permissible/permissible_error.rb +13 -0
  12. data/lib/better_model/errors/predicable/configuration_error.rb +9 -0
  13. data/lib/better_model/errors/predicable/predicable_error.rb +13 -0
  14. data/lib/better_model/errors/searchable/configuration_error.rb +9 -0
  15. data/lib/better_model/errors/searchable/invalid_order_error.rb +11 -0
  16. data/lib/better_model/errors/searchable/invalid_pagination_error.rb +11 -0
  17. data/lib/better_model/errors/searchable/invalid_predicate_error.rb +11 -0
  18. data/lib/better_model/errors/searchable/invalid_security_error.rb +11 -0
  19. data/lib/better_model/errors/searchable/searchable_error.rb +13 -0
  20. data/lib/better_model/errors/sortable/configuration_error.rb +10 -0
  21. data/lib/better_model/errors/sortable/sortable_error.rb +13 -0
  22. data/lib/better_model/errors/stateable/check_failed_error.rb +14 -0
  23. data/lib/better_model/errors/stateable/configuration_error.rb +10 -0
  24. data/lib/better_model/errors/stateable/invalid_state_error.rb +11 -0
  25. data/lib/better_model/errors/stateable/invalid_transition_error.rb +11 -0
  26. data/lib/better_model/errors/stateable/not_enabled_error.rb +11 -0
  27. data/lib/better_model/errors/stateable/stateable_error.rb +13 -0
  28. data/lib/better_model/errors/stateable/validation_failed_error.rb +11 -0
  29. data/lib/better_model/errors/statusable/configuration_error.rb +9 -0
  30. data/lib/better_model/errors/statusable/statusable_error.rb +13 -0
  31. data/lib/better_model/errors/taggable/configuration_error.rb +10 -0
  32. data/lib/better_model/errors/taggable/taggable_error.rb +13 -0
  33. data/lib/better_model/errors/traceable/configuration_error.rb +10 -0
  34. data/lib/better_model/errors/traceable/not_enabled_error.rb +11 -0
  35. data/lib/better_model/errors/traceable/traceable_error.rb +13 -0
  36. data/lib/better_model/errors/validatable/configuration_error.rb +10 -0
  37. data/lib/better_model/errors/validatable/not_enabled_error.rb +11 -0
  38. data/lib/better_model/errors/validatable/validatable_error.rb +13 -0
  39. data/lib/better_model/models/state_transition.rb +122 -0
  40. data/lib/better_model/models/version.rb +68 -0
  41. data/lib/better_model/permissible.rb +103 -52
  42. data/lib/better_model/predicable.rb +114 -63
  43. data/lib/better_model/repositable/base_repository.rb +232 -0
  44. data/lib/better_model/repositable.rb +32 -0
  45. data/lib/better_model/searchable.rb +92 -92
  46. data/lib/better_model/sortable.rb +137 -41
  47. data/lib/better_model/stateable/configurator.rb +71 -53
  48. data/lib/better_model/stateable/guard.rb +35 -15
  49. data/lib/better_model/stateable/transition.rb +59 -30
  50. data/lib/better_model/stateable.rb +33 -15
  51. data/lib/better_model/statusable.rb +84 -52
  52. data/lib/better_model/taggable.rb +120 -75
  53. data/lib/better_model/traceable.rb +56 -48
  54. data/lib/better_model/validatable/configurator.rb +49 -172
  55. data/lib/better_model/validatable.rb +88 -113
  56. data/lib/better_model/version.rb +1 -1
  57. data/lib/better_model.rb +42 -5
  58. data/lib/generators/better_model/repository/repository_generator.rb +141 -0
  59. data/lib/generators/better_model/repository/templates/application_repository.rb.tt +21 -0
  60. data/lib/generators/better_model/repository/templates/repository.rb.tt +71 -0
  61. data/lib/generators/better_model/stateable/templates/README +1 -1
  62. metadata +44 -7
  63. data/lib/better_model/state_transition.rb +0 -106
  64. data/lib/better_model/stateable/errors.rb +0 -48
  65. data/lib/better_model/validatable/business_rule_validator.rb +0 -47
  66. data/lib/better_model/validatable/order_validator.rb +0 -77
  67. data/lib/better_model/version_record.rb +0 -66
@@ -2,25 +2,32 @@
2
2
 
3
3
  module BetterModel
4
4
  module Stateable
5
- # Check evaluator per Stateable (internal class name remains Guard for compatibility)
5
+ # Check evaluator for Stateable transitions.
6
6
  #
7
- # Valuta le check conditions per determinare se una transizione è permessa.
8
- # Supporta tre tipi di checks:
9
- # - Block: lambda/proc valutato nel contesto dell'istanza
10
- # - Method: metodo chiamato sull'istanza
11
- # - Predicate: integrazione con Statusable (is_ready?, etc.)
7
+ # Evaluates check conditions to determine if a transition is allowed.
8
+ # Supports three types of checks:
9
+ # - Block: lambda/proc evaluated in instance context
10
+ # - Method: method called on instance
11
+ # - Predicate: integration with Statusable (is_ready?, etc.)
12
12
  #
13
+ # @api private
13
14
  class Guard
15
+ # Initialize a new Guard.
16
+ #
17
+ # @param instance [Object] Model instance
18
+ # @param guard_config [Hash] Guard configuration hash
14
19
  def initialize(instance, guard_config)
15
20
  @instance = instance
16
21
  @guard_config = guard_config
17
22
  end
18
23
 
19
- # Valuta il check
24
+ # Evaluate the check.
20
25
  #
21
- # @return [Boolean] true se il check passa
22
- # @raise [CheckFailedError] Se il check fallisce (opzionale, dipende dal contesto)
26
+ # @return [Boolean] true if check passes
27
+ # @raise [BetterModel::Errors::Stateable::CheckFailedError] If check fails (optional, context-dependent)
23
28
  #
29
+ # @example
30
+ # guard.evaluate # => true
24
31
  def evaluate
25
32
  case @guard_config[:type]
26
33
  when :block
@@ -30,14 +37,16 @@ module BetterModel
30
37
  when :predicate
31
38
  evaluate_predicate
32
39
  else
33
- raise StateableError, "Unknown check type: #{@guard_config[:type]}"
40
+ raise BetterModel::Errors::Stateable::StateableError, "Unknown check type: #{@guard_config[:type]}"
34
41
  end
35
42
  end
36
43
 
37
- # Descrizione del check per messaggi di errore
44
+ # Description of check for error messages.
38
45
  #
39
- # @return [String] Descrizione human-readable
46
+ # @return [String] Human-readable description
40
47
  #
48
+ # @example
49
+ # guard.description # => "method check: customer_valid?"
41
50
  def description
42
51
  case @guard_config[:type]
43
52
  when :block
@@ -53,13 +62,20 @@ module BetterModel
53
62
 
54
63
  private
55
64
 
56
- # Valuta un check block
65
+ # Evaluate a block check.
66
+ #
67
+ # @return [Boolean] Result of block evaluation
68
+ # @api private
57
69
  def evaluate_block
58
70
  block = @guard_config[:block]
59
71
  @instance.instance_exec(&block)
60
72
  end
61
73
 
62
- # Valuta un check method
74
+ # Evaluate a method check.
75
+ #
76
+ # @return [Boolean] Result of method call
77
+ # @raise [NoMethodError] If method not found
78
+ # @api private
63
79
  def evaluate_method
64
80
  method_name = @guard_config[:method]
65
81
 
@@ -71,7 +87,11 @@ module BetterModel
71
87
  @instance.send(method_name)
72
88
  end
73
89
 
74
- # Valuta un check predicate (integrazione Statusable)
90
+ # Evaluate a predicate check (Statusable integration).
91
+ #
92
+ # @return [Boolean] Result of predicate call
93
+ # @raise [NoMethodError] If predicate not found
94
+ # @api private
75
95
  def evaluate_predicate
76
96
  predicate_name = @guard_config[:predicate]
77
97
 
@@ -1,17 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../errors/stateable/check_failed_error"
4
+ require_relative "../errors/stateable/validation_failed_error"
5
+
3
6
  module BetterModel
4
7
  module Stateable
5
- # Transition executor per Stateable
8
+ # Transition executor for Stateable.
6
9
  #
7
- # Gestisce l'esecuzione di una transizione di stato, includendo:
8
- # - Valutazione checks
9
- # - Esecuzione validazioni
10
- # - Esecuzione callbacks (before_transition/after_transition/around)
11
- # - Aggiornamento stato nel database
12
- # - Creazione record StateTransition per storico
10
+ # Handles the execution of a state transition, including:
11
+ # - Check evaluation
12
+ # - Validation execution
13
+ # - Callback execution (before_transition/after_transition/around)
14
+ # - State update in database
15
+ # - StateTransition record creation for history
13
16
  #
17
+ # @api private
14
18
  class Transition
19
+ # Initialize a new Transition.
20
+ #
21
+ # @param instance [Object] Model instance
22
+ # @param event [Symbol] Transition event name
23
+ # @param config [Hash] Transition configuration
24
+ # @param metadata [Hash] Additional metadata for transition
15
25
  def initialize(instance, event, config, metadata = {})
16
26
  @instance = instance
17
27
  @event = event
@@ -21,23 +31,25 @@ module BetterModel
21
31
  @to_state = config[:to]
22
32
  end
23
33
 
24
- # Esegue la transizione
34
+ # Execute the transition.
25
35
  #
26
- # @raise [CheckFailedError] Se un check fallisce
27
- # @raise [ValidationFailedError] Se una validazione fallisce
28
- # @raise [ActiveRecord::RecordInvalid] Se il save! fallisce
29
- # @return [Boolean] true se la transizione ha successo
36
+ # @raise [BetterModel::Errors::Stateable::CheckFailedError] If a check fails
37
+ # @raise [BetterModel::Errors::Stateable::ValidationFailedError] If a validation fails
38
+ # @raise [ActiveRecord::RecordInvalid] If save! fails
39
+ # @return [Boolean] true if transition succeeds
30
40
  #
41
+ # @example
42
+ # transition.execute! # => true
31
43
  def execute!
32
- # 1. Valuta checks
44
+ # 1. Evaluate checks
33
45
  evaluate_checks!
34
46
 
35
- # 2. Esegui validazioni
47
+ # 2. Execute validations
36
48
  execute_validations!
37
49
 
38
50
  # 3. Wrap in transaction
39
51
  @instance.class.transaction do
40
- # 4. Esegui callbacks around (se presenti)
52
+ # 4. Execute around callbacks (if present)
41
53
  if @config[:around_callbacks].any?
42
54
  execute_around_callbacks do
43
55
  perform_transition!
@@ -52,25 +64,31 @@ module BetterModel
52
64
 
53
65
  private
54
66
 
55
- # Valuta tutti i checks
67
+ # Evaluate all checks.
68
+ #
69
+ # @raise [BetterModel::Errors::Stateable::CheckFailedError] If any check fails
70
+ # @api private
56
71
  def evaluate_checks!
57
- checks = @config[:guards] || [] # Manteniamo :guards per compatibilità interna
72
+ checks = @config[:guards] || [] # Keep :guards for internal compatibility
58
73
 
59
74
  checks.each do |check_config|
60
75
  check = Guard.new(@instance, check_config) # Guard class handles the logic
61
76
 
62
77
  unless check.evaluate
63
- raise CheckFailedError.new(@event, check.description)
78
+ raise BetterModel::Errors::Stateable::CheckFailedError, "Check failed for transition #{@event}"
64
79
  end
65
80
  end
66
81
  end
67
82
 
68
- # Esegue tutte le validazioni
83
+ # Execute all validations.
84
+ #
85
+ # @raise [BetterModel::Errors::Stateable::ValidationFailedError] If validations fail
86
+ # @api private
69
87
  def execute_validations!
70
88
  validations = @config[:validations] || []
71
89
  return if validations.empty?
72
90
 
73
- # Clear existing errors per questa transizione
91
+ # Clear existing errors for this transition
74
92
  @instance.errors.clear
75
93
 
76
94
  validations.each do |validation_block|
@@ -78,11 +96,15 @@ module BetterModel
78
96
  end
79
97
 
80
98
  if @instance.errors.any?
81
- raise ValidationFailedError.new(@event, @instance.errors)
99
+ error_messages = @instance.errors.full_messages.join(", ")
100
+ raise BetterModel::Errors::Stateable::ValidationFailedError, "Validation failed for transition #{@event}: #{error_messages}"
82
101
  end
83
102
  end
84
103
 
85
- # Esegue i callback around
104
+ # Execute around callbacks.
105
+ #
106
+ # @yield Block to wrap with around callbacks
107
+ # @api private
86
108
  def execute_around_callbacks(&block)
87
109
  around_callbacks = @config[:around_callbacks] || []
88
110
 
@@ -99,25 +121,30 @@ module BetterModel
99
121
  chain.call
100
122
  end
101
123
 
102
- # Esegue la transizione effettiva
124
+ # Perform the actual transition.
125
+ #
126
+ # @api private
103
127
  def perform_transition!
104
- # 1. Esegui before_transition callbacks
128
+ # 1. Execute before_transition callbacks
105
129
  execute_callbacks(@config[:before_callbacks] || [])
106
130
 
107
- # 2. Aggiorna stato
131
+ # 2. Update state
108
132
  @instance.state = @to_state.to_s
109
133
 
110
- # 3. Salva il record (valida il modello)
134
+ # 3. Save record (validates model)
111
135
  @instance.save!
112
136
 
113
- # 4. Crea record StateTransition
137
+ # 4. Create StateTransition record
114
138
  create_state_transition_record
115
139
 
116
- # 5. Esegui after_transition callbacks
140
+ # 5. Execute after_transition callbacks
117
141
  execute_callbacks(@config[:after_callbacks] || [])
118
142
  end
119
143
 
120
- # Esegue una lista di callbacks
144
+ # Execute a list of callbacks.
145
+ #
146
+ # @param callbacks [Array<Hash>] Callback configurations
147
+ # @api private
121
148
  def execute_callbacks(callbacks)
122
149
  callbacks.each do |callback_config|
123
150
  case callback_config[:type]
@@ -129,7 +156,9 @@ module BetterModel
129
156
  end
130
157
  end
131
158
 
132
- # Crea il record StateTransition per lo storico
159
+ # Create StateTransition record for history.
160
+ #
161
+ # @api private
133
162
  def create_state_transition_record
134
163
  @instance.state_transitions.create!(
135
164
  event: @event.to_s,
@@ -1,5 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "errors/stateable/stateable_error"
4
+ require_relative "errors/stateable/not_enabled_error"
5
+ require_relative "errors/stateable/invalid_state_error"
6
+ require_relative "errors/stateable/invalid_transition_error"
7
+ require_relative "errors/stateable/check_failed_error"
8
+ require_relative "errors/stateable/validation_failed_error"
9
+ require_relative "errors/stateable/configuration_error"
10
+
3
11
  # Stateable - Declarative State Machine per modelli Rails
4
12
  #
5
13
  # Questo concern permette di definire state machines dichiarative con:
@@ -96,7 +104,7 @@ module BetterModel
96
104
  included do
97
105
  # Validazione ActiveRecord
98
106
  unless ancestors.include?(ActiveRecord::Base)
99
- raise ArgumentError, "BetterModel::Stateable can only be included in ActiveRecord models"
107
+ raise BetterModel::Errors::Stateable::ConfigurationError, "Invalid configuration"
100
108
  end
101
109
 
102
110
  # Configurazione stateable (opt-in)
@@ -171,9 +179,7 @@ module BetterModel
171
179
  # Verifica se stateable è attivo
172
180
  #
173
181
  # @return [Boolean]
174
- def stateable_enabled?
175
- stateable_enabled == true
176
- end
182
+ def stateable_enabled? = stateable_enabled == true
177
183
 
178
184
  private
179
185
 
@@ -214,7 +220,7 @@ module BetterModel
214
220
  end
215
221
 
216
222
  # Create new StateTransition class dynamically
217
- transition_class = Class.new(BetterModel::StateTransition) do
223
+ transition_class = Class.new(BetterModel::Models::StateTransition) do
218
224
  self.table_name = table_name
219
225
  end
220
226
 
@@ -236,8 +242,11 @@ module BetterModel
236
242
  # Metodi per ogni transizione: confirm!, can_confirm?, etc.
237
243
  stateable_transitions.each do |event_name, transition_config|
238
244
  # event! - esegue transizione (raise se fallisce)
239
- define_method "#{event_name}!" do |**metadata|
240
- transition_to!(event_name, **metadata)
245
+ # Accepts both positional hash and keyword arguments for flexibility
246
+ define_method "#{event_name}!" do |metadata = {}, **kwargs|
247
+ # Convert positional hash to keyword args if provided
248
+ combined_metadata = metadata.merge(kwargs)
249
+ transition_to!(event_name, **combined_metadata)
241
250
  end
242
251
 
243
252
  # can_event? - controlla se transizione è possibile
@@ -269,23 +278,27 @@ module BetterModel
269
278
  #
270
279
  # @param event [Symbol] Nome della transizione
271
280
  # @param metadata [Hash] Metadata opzionale da salvare nella StateTransition
272
- # @raise [InvalidTransitionError] Se la transizione non è valida
273
- # @raise [CheckFailedError] Se un check fallisce
274
- # @raise [ValidationFailedError] Se una validazione fallisce
281
+ # @raise [BetterModel::Errors::Stateable::InvalidTransitionError] Se la transizione non è valida
282
+ # @raise [BetterModel::Errors::Stateable::CheckFailedError] Se un check fallisce
283
+ # @raise [BetterModel::Errors::Stateable::ValidationFailedError] Se una validazione fallisce
275
284
  # @return [Boolean] true se la transizione ha successo
276
285
  #
277
286
  def transition_to!(event, **metadata)
278
- raise NotEnabledError unless self.class.stateable_enabled?
287
+ unless self.class.stateable_enabled?
288
+ raise BetterModel::Errors::Stateable::NotEnabledError, "Module is not enabled"
289
+ end
279
290
 
280
291
  transition_config = self.class.stateable_transitions[event.to_sym]
281
- raise ArgumentError, "Unknown transition: #{event}" unless transition_config
292
+ unless transition_config
293
+ raise BetterModel::Errors::Stateable::ConfigurationError, "Unknown transition: #{event}"
294
+ end
282
295
 
283
296
  current_state = state.to_sym
284
297
 
285
298
  # Verifica che from_state sia valido
286
299
  from_states = Array(transition_config[:from])
287
300
  unless from_states.include?(current_state)
288
- raise InvalidTransitionError.new(event, current_state, transition_config[:to])
301
+ raise BetterModel::Errors::Stateable::InvalidTransitionError, "Cannot transition from #{current_state} to #{transition_config[:to]} via #{event}"
289
302
  end
290
303
 
291
304
  # Esegui la transizione usando Transition executor
@@ -322,7 +335,9 @@ module BetterModel
322
335
  # @return [Array<Hash>] Array di transizioni con :event, :from, :to, :at, :metadata
323
336
  #
324
337
  def transition_history
325
- raise NotEnabledError unless self.class.stateable_enabled?
338
+ unless self.class.stateable_enabled?
339
+ raise BetterModel::Errors::Stateable::NotEnabledError, "Module is not enabled"
340
+ end
326
341
 
327
342
  state_transitions.map do |transition|
328
343
  {
@@ -345,7 +360,10 @@ module BetterModel
345
360
  result = super
346
361
 
347
362
  if options[:include_transition_history] && self.class.stateable_enabled?
348
- result["transition_history"] = transition_history
363
+ # Convert symbol keys to string keys for JSON compatibility
364
+ result["transition_history"] = transition_history.map do |item|
365
+ item.transform_keys(&:to_s)
366
+ end
349
367
  end
350
368
 
351
369
  result
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Statusable - Sistema di stati dichiarativi per modelli Rails
3
+ require_relative "errors/statusable/statusable_error"
4
+ require_relative "errors/statusable/configuration_error"
5
+
6
+ # Statusable - Declarative status system for Rails models.
4
7
  #
5
- # Questo concern permette di definire stati sui modelli utilizzando un DSL
6
- # semplice e dichiarativo, simile al pattern Enrichable ma per gli stati.
8
+ # This concern enables defining statuses on models using a simple, declarative DSL
9
+ # similar to the Enrichable pattern but for statuses.
7
10
  #
8
- # Esempio di utilizzo:
11
+ # @example Basic Usage
9
12
  # class Communications::Consult < ApplicationRecord
10
13
  # include BetterModel::Statusable
11
14
  #
@@ -17,7 +20,7 @@
17
20
  # is :ready_to_start, -> { scheduled? && scheduled_at <= Time.current }
18
21
  # end
19
22
  #
20
- # Utilizzo:
23
+ # @example Checking Statuses
21
24
  # consult.is?(:pending) # => true/false
22
25
  # consult.is_pending? # => true/false
23
26
  # consult.is_active_session? # => true/false
@@ -29,58 +32,79 @@ module BetterModel
29
32
  extend ActiveSupport::Concern
30
33
 
31
34
  included do
32
- # Registry degli stati definiti per questa classe
35
+ # Registry of statuses defined for this class
33
36
  class_attribute :is_definitions
34
37
  self.is_definitions = {}
35
38
  end
36
39
 
37
40
  class_methods do
38
- # DSL per definire stati
41
+ # DSL to define statuses.
42
+ #
43
+ # Defines a status check that can be evaluated against model instances.
44
+ # Automatically creates a convenience method is_<status_name>? for each status.
39
45
  #
40
- # Parametri:
41
- # - status_name: simbolo che rappresenta lo stato (es. :pending, :active)
42
- # - condition_proc: lambda o proc che definisce la condizione
43
- # - block: blocco alternativo alla condition_proc
46
+ # @param status_name [Symbol, String] Status identifier (e.g., :pending, :active)
47
+ # @param condition_proc [Proc, nil] Lambda or proc that defines the condition
48
+ # @yield Alternative to condition_proc parameter
49
+ # @raise [BetterModel::Errors::Statusable::ConfigurationError] If parameters are invalid
44
50
  #
45
- # Esempi:
51
+ # @example With lambda parameter
46
52
  # is :pending, -> { status == 'initialized' }
47
- # is :expired, -> { expires_at.present? && expires_at <= Time.current }
53
+ #
54
+ # @example With block
55
+ # is :expired do
56
+ # expires_at.present? && expires_at <= Time.current
57
+ # end
58
+ #
59
+ # @example Complex condition
48
60
  # is :ready do
49
61
  # scheduled_at.present? && scheduled_at <= Time.current
50
62
  # end
51
63
  def is(status_name, condition_proc = nil, &block)
52
- # Valida i parametri prima di convertire
53
- raise ArgumentError, "Status name cannot be blank" if status_name.blank?
64
+ # Validate parameters before converting
65
+ if status_name.blank?
66
+ raise BetterModel::Errors::Statusable::ConfigurationError, "Status name cannot be blank"
67
+ end
54
68
 
55
69
  status_name = status_name.to_sym
56
70
  condition = condition_proc || block
57
- raise ArgumentError, "Condition proc or block is required" unless condition
58
- raise ArgumentError, "Condition must respond to call" unless condition.respond_to?(:call)
59
71
 
60
- # Registra lo stato nel registry
72
+ unless condition
73
+ raise BetterModel::Errors::Statusable::ConfigurationError, "Condition proc or block is required"
74
+ end
75
+
76
+ unless condition.respond_to?(:call)
77
+ raise BetterModel::Errors::Statusable::ConfigurationError, "Condition must respond to call"
78
+ end
79
+
80
+ # Register status in registry
61
81
  self.is_definitions = is_definitions.merge(status_name => condition.freeze).freeze
62
82
 
63
- # Genera il metodo dinamico is_#{status_name}?
83
+ # Generate dynamic method is_#{status_name}?
64
84
  define_is_method(status_name)
65
85
  end
66
86
 
67
- # Lista di tutti gli stati definiti per questa classe
68
- def defined_statuses
69
- is_definitions.keys
70
- end
87
+ # List all statuses defined for this class.
88
+ #
89
+ # @return [Array<Symbol>] Array of defined status names
90
+ def defined_statuses = is_definitions.keys
71
91
 
72
- # Verifica se uno stato è definito
73
- def status_defined?(status_name)
74
- is_definitions.key?(status_name.to_sym)
75
- end
92
+ # Check if a status is defined.
93
+ #
94
+ # @param status_name [Symbol, String] Status name to check
95
+ # @return [Boolean] true if status is defined
96
+ def status_defined?(status_name) = is_definitions.key?(status_name.to_sym)
76
97
 
77
98
  private
78
99
 
79
- # Genera dinamicamente il metodo is_#{status_name}? per ogni stato definito
100
+ # Generate dynamic method is_#{status_name}? for each defined status.
101
+ #
102
+ # @param status_name [Symbol] Status name
103
+ # @api private
80
104
  def define_is_method(status_name)
81
105
  method_name = "is_#{status_name}?"
82
106
 
83
- # Evita di ridefinire metodi se già esistono
107
+ # Avoid redefining methods if they already exist
84
108
  return if method_defined?(method_name)
85
109
 
86
110
  define_method(method_name) do
@@ -89,35 +113,33 @@ module BetterModel
89
113
  end
90
114
  end
91
115
 
92
- # Metodo generico per verificare se uno stato è attivo
116
+ # Generic method to check if a status is active.
93
117
  #
94
- # Parametri:
95
- # - status_name: simbolo dello stato da verificare
118
+ # Evaluates the status condition in the context of the model instance.
119
+ # Returns false if status is not defined (secure by default).
96
120
  #
97
- # Ritorna:
98
- # - true se lo stato è attivo
99
- # - false se lo stato non è attivo o non è definito
121
+ # @param status_name [Symbol, String] Status name to check
122
+ # @return [Boolean] true if status is active, false otherwise
100
123
  #
101
- # Esempio:
102
- # consult.is?(:pending)
124
+ # @example
125
+ # consult.is?(:pending) # => true
103
126
  def is?(status_name)
104
127
  status_name = status_name.to_sym
105
128
  condition = self.class.is_definitions[status_name]
106
129
 
107
- # Se lo stato non è definito, ritorna false (secure by default)
130
+ # If status is not defined, return false (secure by default)
108
131
  return false unless condition
109
132
 
110
- # Valuta la condizione nel contesto dell'istanza del modello
111
- # Gli errori si propagano naturalmente - fail fast
133
+ # Evaluate condition in context of model instance
134
+ # Errors propagate naturally - fail fast
112
135
  instance_exec(&condition)
113
136
  end
114
137
 
115
- # Ritorna tutti gli stati disponibili per questa istanza con i loro valori
138
+ # Returns all available statuses for this instance with their values.
116
139
  #
117
- # Ritorna:
118
- # - Hash con chiavi simbolo (stati) e valori booleani (attivi/inattivi)
140
+ # @return [Hash{Symbol => Boolean}] Hash with status names and their active state
119
141
  #
120
- # Esempio:
142
+ # @example
121
143
  # consult.statuses
122
144
  # # => { pending: true, active: false, expired: false, scheduled: true }
123
145
  def statuses
@@ -126,26 +148,36 @@ module BetterModel
126
148
  end
127
149
  end
128
150
 
129
- # Verifica se l'istanza ha almeno uno stato attivo
130
- def has_any_status?
131
- statuses.values.any?
132
- end
151
+ # Check if instance has at least one active status.
152
+ #
153
+ # @return [Boolean] true if any status is active
154
+ def has_any_status? = statuses.values.any?
133
155
 
134
- # Verifica se l'istanza ha tutti gli stati specificati attivi
156
+ # Check if instance has all specified statuses active.
157
+ #
158
+ # @param status_names [Array<Symbol>] Status names to check
159
+ # @return [Boolean] true if all statuses are active
135
160
  def has_all_statuses?(status_names)
136
161
  Array(status_names).all? { |status_name| is?(status_name) }
137
162
  end
138
163
 
139
- # Filtra una lista di stati restituendo solo quelli attivi
164
+ # Filter a list of statuses returning only active ones.
165
+ #
166
+ # @param status_names [Array<Symbol>] Status names to filter
167
+ # @return [Array<Symbol>] Active statuses
140
168
  def active_statuses(status_names)
141
169
  Array(status_names).select { |status_name| is?(status_name) }
142
170
  end
143
171
 
144
- # Override di as_json per includere automaticamente gli stati se richiesto
172
+ # Override as_json to automatically include statuses if requested.
173
+ #
174
+ # @param options [Hash] Options for as_json
175
+ # @option options [Boolean] :include_statuses Include statuses in JSON output
176
+ # @return [Hash] JSON representation
145
177
  def as_json(options = {})
146
178
  result = super
147
179
 
148
- # Include gli stati se esplicitamente richiesto, converting symbol keys to strings
180
+ # Include statuses if explicitly requested, converting symbol keys to strings
149
181
  result["statuses"] = statuses.transform_keys(&:to_s) if options[:include_statuses]
150
182
 
151
183
  result