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
@@ -1,18 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Sortable - Sistema di ordinamento dichiarativo per modelli Rails
3
+ require_relative "errors/sortable/sortable_error"
4
+ require_relative "errors/sortable/configuration_error"
5
+
6
+ # Sortable - Declarative sorting system for Rails models.
4
7
  #
5
- # Questo concern permette di definire ordinamenti sui modelli utilizzando un DSL
6
- # semplice e dichiarativo che genera automaticamente scope basati sul tipo di colonna.
8
+ # This concern enables defining sorts on models using a simple, declarative DSL
9
+ # that automatically generates scopes based on column type.
7
10
  #
8
- # Esempio di utilizzo:
11
+ # @example Basic Usage
9
12
  # class Article < ApplicationRecord
10
13
  # include BetterModel::Sortable
11
14
  #
12
15
  # sort :title, :view_count, :published_at
13
16
  # end
14
17
  #
15
- # Utilizzo:
18
+ # @example Generated Scopes
16
19
  # Article.sort_title_asc # ORDER BY title ASC
17
20
  # Article.sort_title_desc_i # ORDER BY LOWER(title) DESC
18
21
  # Article.sort_view_count_desc_nulls_last # ORDER BY view_count DESC NULLS LAST
@@ -23,26 +26,30 @@ module BetterModel
23
26
  extend ActiveSupport::Concern
24
27
 
25
28
  included do
26
- # Valida che sia incluso solo in modelli ActiveRecord
29
+ # Validate ActiveRecord inheritance
27
30
  unless ancestors.include?(ActiveRecord::Base)
28
- raise ArgumentError, "BetterModel::Sortable can only be included in ActiveRecord models"
31
+ raise BetterModel::Errors::Sortable::ConfigurationError, "BetterModel::Sortable can only be included in ActiveRecord models"
29
32
  end
30
33
 
31
- # Registry dei campi sortable definiti per questa classe
34
+ # Registry of sortable fields defined for this class
32
35
  class_attribute :sortable_fields, default: Set.new
33
- # Registry degli scope sortable generati
36
+ # Registry of generated sortable scopes
34
37
  class_attribute :sortable_scopes, default: Set.new
38
+ # Registry of custom complex sorts
39
+ class_attribute :complex_sorts_registry, default: {}.freeze
35
40
  end
36
41
 
37
42
  class_methods do
38
- # DSL per definire campi sortable
43
+ # DSL to define sortable fields.
39
44
  #
40
- # Genera automaticamente scope di ordinamento basati sul tipo di colonna:
45
+ # Automatically generates sorting scopes based on column type:
41
46
  # - String: _asc, _desc, _asc_i, _desc_i (case-insensitive)
42
47
  # - Numeric: _asc, _desc, _asc_nulls_last, _desc_nulls_last, etc.
43
48
  # - Date: _asc, _desc, _newest, _oldest
44
49
  #
45
- # Esempio:
50
+ # @param field_names [Array<Symbol>] Field names to make sortable
51
+ #
52
+ # @example
46
53
  # sort :title, :view_count, :published_at
47
54
  def sort(*field_names)
48
55
  field_names.each do |field_name|
@@ -67,36 +74,103 @@ module BetterModel
67
74
  end
68
75
  end
69
76
 
70
- # Verifica se un campo è stato registrato come sortable
71
- def sortable_field?(field_name)
72
- sortable_fields.include?(field_name.to_sym)
73
- end
77
+ # Register a custom complex sort.
78
+ #
79
+ # Allows defining complex sorts that combine multiple fields
80
+ # or use custom logic not covered by standard sorts.
81
+ #
82
+ # @param name [Symbol] Sort name (will be prefixed with sort_)
83
+ # @yield Sort implementation block
84
+ # @raise [BetterModel::Errors::Sortable::ConfigurationError] If block is not provided
85
+ #
86
+ # @example Multi-field sort
87
+ # register_complex_sort :by_popularity do
88
+ # order(view_count: :desc, published_at: :desc)
89
+ # end
90
+ #
91
+ # Article.sort_by_popularity
92
+ #
93
+ # @example Parametrized sort
94
+ # register_complex_sort :by_relevance do |keyword|
95
+ # order(Arel.sql("CASE WHEN title ILIKE '%#{sanitize_sql_like(keyword)}%' THEN 1 ELSE 2 END"))
96
+ # end
97
+ #
98
+ # Article.sort_by_relevance('rails')
99
+ def register_complex_sort(name, &block)
100
+ unless block_given?
101
+ raise BetterModel::Errors::Sortable::ConfigurationError, "Block required for complex sort"
102
+ end
103
+
104
+ # Register in registry
105
+ self.complex_sorts_registry = complex_sorts_registry.merge(name.to_sym => block).freeze
106
+
107
+ # Define scope
108
+ scope :"sort_#{name}", block
74
109
 
75
- # Verifica se uno scope sortable è stato generato
76
- def sortable_scope?(scope_name)
77
- sortable_scopes.include?(scope_name.to_sym)
110
+ # Register scope
111
+ register_sortable_scopes(:"sort_#{name}")
78
112
  end
79
113
 
114
+ # Check if a field has been registered as sortable.
115
+ #
116
+ # @param field_name [Symbol] Field name to check
117
+ # @return [Boolean] true if field is sortable
118
+ #
119
+ # @example
120
+ # Article.sortable_field?(:title) # => true
121
+ def sortable_field?(field_name) = sortable_fields.include?(field_name.to_sym)
122
+
123
+ # Check if a sortable scope has been generated.
124
+ #
125
+ # @param scope_name [Symbol] Scope name to check
126
+ # @return [Boolean] true if scope exists
127
+ #
128
+ # @example
129
+ # Article.sortable_scope?(:sort_title_asc) # => true
130
+ def sortable_scope?(scope_name) = sortable_scopes.include?(scope_name.to_sym)
131
+
132
+ # Check if a complex sort has been registered.
133
+ #
134
+ # @param name [Symbol] Sort name to check
135
+ # @return [Boolean] true if complex sort exists
136
+ #
137
+ # @example
138
+ # Article.complex_sort?(:by_popularity) # => true
139
+ def complex_sort?(name) = complex_sorts_registry.key?(name.to_sym)
140
+
80
141
  private
81
142
 
82
- # Valida che il campo esista nella tabella
143
+ # Validate that field exists in table.
144
+ #
145
+ # @param field_name [Symbol] Field name to validate
146
+ # @raise [BetterModel::Errors::Sortable::ConfigurationError] If field doesn't exist
147
+ # @api private
83
148
  def validate_sortable_field!(field_name)
84
149
  unless column_names.include?(field_name.to_s)
85
- raise ArgumentError, "Invalid field name: #{field_name}. Field does not exist in #{table_name}"
150
+ raise BetterModel::Errors::Sortable::ConfigurationError, "Invalid field name: #{field_name}. Field does not exist in #{table_name}"
86
151
  end
87
152
  end
88
153
 
89
- # Registra un campo nel registry sortable_fields
154
+ # Register a field in sortable_fields registry.
155
+ #
156
+ # @param field_name [Symbol] Field name to register
157
+ # @api private
90
158
  def register_sortable_field(field_name)
91
159
  self.sortable_fields = (sortable_fields + [ field_name.to_sym ]).to_set.freeze
92
160
  end
93
161
 
94
- # Registra scope nel registry sortable_scopes
162
+ # Register scopes in sortable_scopes registry.
163
+ #
164
+ # @param scope_names [Array<Symbol>] Scope names to register
165
+ # @api private
95
166
  def register_sortable_scopes(*scope_names)
96
167
  self.sortable_scopes = (sortable_scopes + scope_names.map(&:to_sym)).to_set.freeze
97
168
  end
98
169
 
99
- # Genera scope base: sort_field_asc e sort_field_desc
170
+ # Generate base sorting scopes: sort_field_asc and sort_field_desc.
171
+ #
172
+ # @param field_name [Symbol] Field name
173
+ # @api private
100
174
  def define_base_sorting(field_name)
101
175
  quoted_field = connection.quote_column_name(field_name)
102
176
 
@@ -109,7 +183,10 @@ module BetterModel
109
183
  )
110
184
  end
111
185
 
112
- # Genera scope per campi stringa (include case-insensitive)
186
+ # Generate sorting scopes for string fields (includes case-insensitive).
187
+ #
188
+ # @param field_name [Symbol] Field name
189
+ # @api private
113
190
  def define_string_sorting(field_name)
114
191
  quoted_field = connection.quote_column_name(field_name)
115
192
 
@@ -129,21 +206,24 @@ module BetterModel
129
206
  )
130
207
  end
131
208
 
132
- # Genera scope per campi numerici (include gestione NULL)
209
+ # Generate sorting scopes for numeric fields (includes NULL handling).
210
+ #
211
+ # @param field_name [Symbol] Field name
212
+ # @api private
133
213
  def define_numeric_sorting(field_name)
134
214
  quoted_field = connection.quote_column_name(field_name)
135
215
 
136
- # Scope base
216
+ # Base scopes
137
217
  scope :"sort_#{field_name}_asc", -> { order(Arel.sql("#{quoted_field} ASC")) }
138
218
  scope :"sort_#{field_name}_desc", -> { order(Arel.sql("#{quoted_field} DESC")) }
139
219
 
140
- # Pre-calcola SQL per gestione NULL (necessario perché lo scope non ha accesso ai metodi privati)
220
+ # Pre-calculate SQL for NULL handling (necessary because scope doesn't have access to private methods)
141
221
  sql_asc_nulls_last = nulls_order_sql(field_name, "ASC", "LAST")
142
222
  sql_desc_nulls_last = nulls_order_sql(field_name, "DESC", "LAST")
143
223
  sql_asc_nulls_first = nulls_order_sql(field_name, "ASC", "FIRST")
144
224
  sql_desc_nulls_first = nulls_order_sql(field_name, "DESC", "FIRST")
145
225
 
146
- # Scope con gestione NULL
226
+ # Scopes with NULL handling
147
227
  scope :"sort_#{field_name}_asc_nulls_last", -> {
148
228
  order(Arel.sql(sql_asc_nulls_last))
149
229
  }
@@ -167,15 +247,18 @@ module BetterModel
167
247
  )
168
248
  end
169
249
 
170
- # Genera scope per campi data/datetime (include shortcuts semantici)
250
+ # Generate sorting scopes for date/datetime fields (includes semantic shortcuts).
251
+ #
252
+ # @param field_name [Symbol] Field name
253
+ # @api private
171
254
  def define_date_sorting(field_name)
172
255
  quoted_field = connection.quote_column_name(field_name)
173
256
 
174
- # Scope base
257
+ # Base scopes
175
258
  scope :"sort_#{field_name}_asc", -> { order(Arel.sql("#{quoted_field} ASC")) }
176
259
  scope :"sort_#{field_name}_desc", -> { order(Arel.sql("#{quoted_field} DESC")) }
177
260
 
178
- # Shortcuts semantici
261
+ # Semantic shortcuts
179
262
  scope :"sort_#{field_name}_newest", -> { order(Arel.sql("#{quoted_field} DESC")) }
180
263
  scope :"sort_#{field_name}_oldest", -> { order(Arel.sql("#{quoted_field} ASC")) }
181
264
 
@@ -187,19 +270,24 @@ module BetterModel
187
270
  )
188
271
  end
189
272
 
190
- # Genera SQL per gestione NULL multi-database
273
+ # Generate SQL for NULL handling across different databases.
191
274
  #
192
- # NOTA: Il blocco MySQL else qui sotto non è coperto da test automatici
193
- # perché i test vengono eseguiti su SQLite. Testare manualmente su MySQL
194
- # con: rails console RAILS_ENV=test
275
+ # @param field_name [Symbol] Field name
276
+ # @param direction [String] Sort direction ('ASC' or 'DESC')
277
+ # @param nulls_position [String] NULL position ('FIRST' or 'LAST')
278
+ # @return [String] SQL ORDER BY clause
279
+ # @api private
280
+ #
281
+ # @note The MySQL/MariaDB else block is not covered by automated tests
282
+ # because tests run on SQLite. Test manually on MySQL with: rails console RAILS_ENV=test
195
283
  def nulls_order_sql(field_name, direction, nulls_position)
196
284
  quoted_field = connection.quote_column_name(field_name)
197
285
 
198
- # PostgreSQL e SQLite 3.30+ supportano NULLS LAST/FIRST nativamente
286
+ # PostgreSQL and SQLite 3.30+ support NULLS LAST/FIRST natively
199
287
  if connection.adapter_name.match?(/PostgreSQL|SQLite/)
200
288
  "#{quoted_field} #{direction} NULLS #{nulls_position}"
201
289
  else
202
- # MySQL/MariaDB: emulazione con CASE
290
+ # MySQL/MariaDB: emulate with CASE
203
291
  if nulls_position == "LAST"
204
292
  "CASE WHEN #{quoted_field} IS NULL THEN 1 ELSE 0 END, #{quoted_field} #{direction}"
205
293
  else # FIRST
@@ -209,9 +297,17 @@ module BetterModel
209
297
  end
210
298
  end
211
299
 
212
- # Metodi di istanza
213
-
214
- # Ritorna lista di attributi sortable (esclude campi sensibili)
300
+ # Instance Methods
301
+
302
+ # Returns list of sortable attributes (excludes sensitive fields).
303
+ #
304
+ # Automatically filters out password and encrypted fields for security.
305
+ #
306
+ # @return [Array<String>] Sortable attribute names
307
+ #
308
+ # @example
309
+ # article.sortable_attributes
310
+ # # => ["id", "title", "view_count", "published_at", "created_at", "updated_at"]
215
311
  def sortable_attributes
216
312
  self.class.column_names.reject do |attr|
217
313
  attr.start_with?("password", "encrypted_")
@@ -2,19 +2,19 @@
2
2
 
3
3
  module BetterModel
4
4
  module Stateable
5
- # Configurator per il DSL di Stateable
5
+ # Configurator for Stateable DSL.
6
6
  #
7
- # Questo configurator permette di definire state machines in modo dichiarativo
8
- # all'interno del blocco `stateable do...end`.
7
+ # This configurator enables defining state machines declaratively
8
+ # within the `stateable do...end` block.
9
9
  #
10
- # Esempio:
10
+ # @example
11
11
  # stateable do
12
- # # Definisci stati
12
+ # # Define states
13
13
  # state :pending, initial: true
14
14
  # state :confirmed
15
15
  # state :paid
16
16
  #
17
- # # Definisci transizioni
17
+ # # Define transitions
18
18
  # transition :confirm, from: :pending, to: :confirmed do
19
19
  # check { items.any? }
20
20
  # check :customer_valid?
@@ -27,9 +27,13 @@ module BetterModel
27
27
  # end
28
28
  # end
29
29
  #
30
+ # @api private
30
31
  class Configurator
31
32
  attr_reader :states, :transitions, :initial_state, :table_name
32
33
 
34
+ # Initialize a new Configurator.
35
+ #
36
+ # @param model_class [Class] Model class being configured
33
37
  def initialize(model_class)
34
38
  @model_class = model_class
35
39
  @states = []
@@ -39,16 +43,19 @@ module BetterModel
39
43
  @current_transition = nil
40
44
  end
41
45
 
42
- # Definisce uno stato
46
+ # Define a state.
43
47
  #
44
- # @param name [Symbol] Nome dello stato
45
- # @param initial [Boolean] Se questo è lo stato iniziale
48
+ # @param name [Symbol] State name
49
+ # @param initial [Boolean] Whether this is the initial state
50
+ # @raise [ArgumentError] If state name is invalid or already defined
46
51
  #
47
52
  # @example
48
53
  # state :draft, initial: true
49
54
  # state :published
50
55
  # state :archived
51
56
  #
57
+ # @example With initial state
58
+ # state :pending, initial: true
52
59
  def state(name, initial: false)
53
60
  raise ArgumentError, "State name must be a symbol" unless name.is_a?(Symbol)
54
61
  raise ArgumentError, "State #{name} already defined" if @states.include?(name)
@@ -61,17 +68,18 @@ module BetterModel
61
68
  end
62
69
  end
63
70
 
64
- # Definisce una transizione
71
+ # Define a transition.
65
72
  #
66
- # @param event [Symbol] Nome dell'evento/transizione
67
- # @param from [Symbol, Array<Symbol>] Stato/i di partenza
68
- # @param to [Symbol] Stato di arrivo
69
- # @yield Blocco per configurare guards, validations, callbacks
73
+ # @param event [Symbol] Event/transition name
74
+ # @param from [Symbol, Array<Symbol>] Source state(s)
75
+ # @param to [Symbol] Destination state
76
+ # @yield Block to configure guards, validations, callbacks
77
+ # @raise [ArgumentError] If event is invalid, already defined, or states don't exist
70
78
  #
71
- # @example Transizione semplice
79
+ # @example Simple transition
72
80
  # transition :publish, from: :draft, to: :published
73
81
  #
74
- # @example Con checks e callbacks
82
+ # @example With checks and callbacks
75
83
  # transition :confirm, from: :pending, to: :confirmed do
76
84
  # check { valid? }
77
85
  # check :ready_to_confirm?
@@ -79,17 +87,17 @@ module BetterModel
79
87
  # after_transition { send_email }
80
88
  # end
81
89
  #
82
- # @example Da multipli stati
90
+ # @example From multiple states
83
91
  # transition :cancel, from: [:pending, :confirmed, :paid], to: :cancelled
84
92
  #
85
93
  def transition(event, from:, to:, &block)
86
94
  raise ArgumentError, "Event name must be a symbol" unless event.is_a?(Symbol)
87
95
  raise ArgumentError, "Transition #{event} already defined" if @transitions.key?(event)
88
96
 
89
- # Normalizza from in array
97
+ # Normalize from to array
90
98
  from_states = Array(from)
91
99
 
92
- # Verifica che gli stati esistano
100
+ # Verify states exist
93
101
  from_states.each do |state_name|
94
102
  unless @states.include?(state_name)
95
103
  raise ArgumentError, "Unknown state in from: #{state_name}. Define it with 'state :#{state_name}' first."
@@ -100,7 +108,7 @@ module BetterModel
100
108
  raise ArgumentError, "Unknown state in to: #{to}. Define it with 'state :#{to}' first."
101
109
  end
102
110
 
103
- # Inizializza configurazione transizione
111
+ # Initialize transition configuration
104
112
  @transitions[event] = {
105
113
  from: from_states,
106
114
  to: to,
@@ -111,7 +119,7 @@ module BetterModel
111
119
  around_callbacks: []
112
120
  }
113
121
 
114
- # Se c'è un blocco, configuralo
122
+ # If block provided, configure it
115
123
  if block_given?
116
124
  @current_transition = @transitions[event]
117
125
  instance_eval(&block)
@@ -119,28 +127,30 @@ module BetterModel
119
127
  end
120
128
  end
121
129
 
122
- # Definisce un check per la transizione corrente
130
+ # Define a check for the current transition.
123
131
  #
124
- # I checks sono precondizioni che devono essere vere per permettere la transizione.
132
+ # Checks are preconditions that must be true to allow the transition.
125
133
  #
126
134
  # @overload check(&block)
127
- # Check con lambda/proc
128
- # @yield Blocco da valutare nel contesto dell'istanza
135
+ # Check with lambda/proc
136
+ # @yield Block to evaluate in instance context
129
137
  # @example
130
138
  # check { items.any? && customer.present? }
131
139
  #
132
140
  # @overload check(method_name)
133
- # Check con metodo
134
- # @param method_name [Symbol] Nome del metodo da chiamare
141
+ # Check with method
142
+ # @param method_name [Symbol] Method name to call
135
143
  # @example
136
144
  # check :customer_valid?
137
145
  #
138
146
  # @overload check(if: predicate)
139
- # Check con Statusable predicate
140
- # @param if [Symbol] Nome del predicate (integrazione Statusable)
147
+ # Check with Statusable predicate
148
+ # @param if [Symbol] Predicate name (Statusable integration)
141
149
  # @example
142
150
  # check if: :is_ready_for_publishing?
143
151
  #
152
+ # @raise [StateableError] If called outside transition block
153
+ # @raise [ArgumentError] If no check provided
144
154
  def check(method_name = nil, if: nil, &block)
145
155
  raise StateableError, "check can only be called inside a transition block" unless @current_transition
146
156
 
@@ -155,12 +165,14 @@ module BetterModel
155
165
  end
156
166
  end
157
167
 
158
- # Definisce una validazione per la transizione corrente
168
+ # Define a validation for the current transition.
159
169
  #
160
- # Le validazioni sono eseguite dopo i guards e prima dei callbacks.
161
- # Devono aggiungere errori all'oggetto errors se la validazione fallisce.
170
+ # Validations are executed after guards and before callbacks.
171
+ # They must add errors to the errors object if validation fails.
162
172
  #
163
- # @yield Blocco da valutare nel contesto dell'istanza
173
+ # @yield Block to evaluate in instance context
174
+ # @raise [StateableError] If called outside transition block
175
+ # @raise [ArgumentError] If no block provided
164
176
  #
165
177
  # @example
166
178
  # validate do
@@ -175,22 +187,24 @@ module BetterModel
175
187
  @current_transition[:validations] << block
176
188
  end
177
189
 
178
- # Definisce un callback before_transition per la transizione corrente
190
+ # Define a before_transition callback for the current transition.
179
191
  #
180
- # I before_transition callbacks sono eseguiti prima della transizione di stato.
192
+ # Before_transition callbacks are executed before the state transition.
181
193
  #
182
194
  # @overload before_transition(&block)
183
- # Before_transition callback con lambda/proc
184
- # @yield Blocco da eseguire
195
+ # Before_transition callback with lambda/proc
196
+ # @yield Block to execute
185
197
  # @example
186
198
  # before_transition { calculate_total }
187
199
  #
188
200
  # @overload before_transition(method_name)
189
- # Before_transition callback con metodo
190
- # @param method_name [Symbol] Nome del metodo da chiamare
201
+ # Before_transition callback with method
202
+ # @param method_name [Symbol] Method name to call
191
203
  # @example
192
204
  # before_transition :calculate_total
193
205
  #
206
+ # @raise [StateableError] If called outside transition block
207
+ # @raise [ArgumentError] If no callback provided
194
208
  def before_transition(method_name = nil, &block)
195
209
  raise StateableError, "before_transition can only be called inside a transition block" unless @current_transition
196
210
 
@@ -203,22 +217,24 @@ module BetterModel
203
217
  end
204
218
  end
205
219
 
206
- # Definisce un callback after_transition per la transizione corrente
220
+ # Define an after_transition callback for the current transition.
207
221
  #
208
- # Gli after_transition callbacks sono eseguiti dopo la transizione di stato.
222
+ # After_transition callbacks are executed after the state transition.
209
223
  #
210
224
  # @overload after_transition(&block)
211
- # After_transition callback con lambda/proc
212
- # @yield Blocco da eseguire
225
+ # After_transition callback with lambda/proc
226
+ # @yield Block to execute
213
227
  # @example
214
228
  # after_transition { send_notification }
215
229
  #
216
230
  # @overload after_transition(method_name)
217
- # After_transition callback con metodo
218
- # @param method_name [Symbol] Nome del metodo da chiamare
231
+ # After_transition callback with method
232
+ # @param method_name [Symbol] Method name to call
219
233
  # @example
220
234
  # after_transition :send_notification
221
235
  #
236
+ # @raise [StateableError] If called outside transition block
237
+ # @raise [ArgumentError] If no callback provided
222
238
  def after_transition(method_name = nil, &block)
223
239
  raise StateableError, "after_transition can only be called inside a transition block" unless @current_transition
224
240
 
@@ -231,12 +247,14 @@ module BetterModel
231
247
  end
232
248
  end
233
249
 
234
- # Definisce un callback around per la transizione corrente
250
+ # Define an around callback for the current transition.
235
251
  #
236
- # Gli around callbacks wrappano la transizione di stato.
237
- # Il blocco riceve un altro blocco che deve chiamare per eseguire la transizione.
252
+ # Around callbacks wrap the state transition.
253
+ # The block receives another block that must be called to execute the transition.
238
254
  #
239
- # @yield Blocco da eseguire, riceve un blocco da chiamare
255
+ # @yield Block to execute, receives a block to call
256
+ # @raise [StateableError] If called outside transition block
257
+ # @raise [ArgumentError] If no block provided
240
258
  #
241
259
  # @example
242
260
  # around do |transition|
@@ -252,9 +270,9 @@ module BetterModel
252
270
  @current_transition[:around_callbacks] << block
253
271
  end
254
272
 
255
- # Specifica il nome della tabella per state transitions
273
+ # Specify the table name for state transitions.
256
274
  #
257
- # @param name [String, Symbol] Nome della tabella
275
+ # @param name [String, Symbol] Table name
258
276
  #
259
277
  # @example Default (state_transitions)
260
278
  # stateable do
@@ -283,9 +301,9 @@ module BetterModel
283
301
  @table_name = name.to_s
284
302
  end
285
303
 
286
- # Restituisce la configurazione completa
304
+ # Return complete configuration.
287
305
  #
288
- # @return [Hash] Configurazione con stati e transizioni
306
+ # @return [Hash] Configuration with states and transitions
289
307
  #
290
308
  def to_h
291
309
  {