better_model 2.0.0 → 2.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 +4 -4
- data/README.md +179 -196
- data/lib/better_model/archivable.rb +0 -1
- data/lib/better_model/predicable.rb +39 -79
- data/lib/better_model/searchable.rb +31 -4
- data/lib/better_model/stateable/configurator.rb +46 -46
- data/lib/better_model/stateable/errors.rb +8 -5
- data/lib/better_model/stateable/guard.rb +17 -17
- data/lib/better_model/stateable/transition.rb +14 -14
- data/lib/better_model/stateable.rb +11 -11
- data/lib/better_model/validatable/configurator.rb +12 -12
- data/lib/better_model/version.rb +1 -1
- data/lib/better_model.rb +0 -4
- metadata +3 -9
- data/lib/better_model/schedulable/occurrence_calculator.rb +0 -1034
- data/lib/better_model/schedulable/schedule_builder.rb +0 -269
- data/lib/better_model/schedulable.rb +0 -356
- data/lib/generators/better_model/taggable/taggable_generator.rb +0 -129
- data/lib/generators/better_model/taggable/templates/README.tt +0 -62
- data/lib/generators/better_model/taggable/templates/migration.rb.tt +0 -21
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
# Article.title_i_cont("rails") # WHERE LOWER(title) LIKE '%rails%'
|
|
18
18
|
# Article.view_count_gt(100) # WHERE view_count > 100
|
|
19
19
|
# Article.published_at_lteq(Date.today) # WHERE published_at <= '2025-10-29'
|
|
20
|
-
# Article.
|
|
20
|
+
# Article.featured_true # WHERE featured = TRUE
|
|
21
21
|
# Article.status_in(["draft", "published"]) # WHERE status IN ('draft', 'published')
|
|
22
22
|
#
|
|
23
23
|
module BetterModel
|
|
@@ -42,10 +42,12 @@ module BetterModel
|
|
|
42
42
|
# DSL per definire campi predicable
|
|
43
43
|
#
|
|
44
44
|
# Genera automaticamente scope di filtro basati sul tipo di colonna:
|
|
45
|
-
# - String: _eq, _not_eq, _matches, _start, _end, _cont, _not_cont, _i_cont, _not_i_cont, _in, _not_in, _present, _blank, _null
|
|
46
|
-
# - Numeric: _eq, _not_eq, _lt, _lteq, _gt, _gteq, _in, _not_in, _present
|
|
47
|
-
# - Boolean: _eq, _not_eq, _present
|
|
48
|
-
# - Date: _eq, _not_eq, _lt, _lteq, _gt, _gteq, _in, _not_in,
|
|
45
|
+
# - String: _eq, _not_eq, _matches, _start, _end, _cont, _not_cont, _i_cont, _not_i_cont, _in, _not_in, _present(bool), _blank(bool), _null(bool)
|
|
46
|
+
# - Numeric: _eq, _not_eq, _lt, _lteq, _gt, _gteq, _between, _not_between, _in, _not_in, _present(bool)
|
|
47
|
+
# - Boolean: _eq, _not_eq, _present(bool)
|
|
48
|
+
# - Date: _eq, _not_eq, _lt, _lteq, _gt, _gteq, _between, _not_between, _in, _not_in, _within(duration), _blank(bool), _null(bool)
|
|
49
|
+
#
|
|
50
|
+
# Nota: Tutti i predicati richiedono parametri espliciti. Use _eq(true)/_eq(false) per booleani.
|
|
49
51
|
#
|
|
50
52
|
# Esempio:
|
|
51
53
|
# predicates :title, :view_count, :published_at, :featured
|
|
@@ -151,16 +153,11 @@ module BetterModel
|
|
|
151
153
|
scope :"#{field_name}_not_eq", ->(value) { where(field.not_eq(value)) }
|
|
152
154
|
|
|
153
155
|
# Presence - skip for string/text types as they get specialized version
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
# - false: returns null records (NULL)
|
|
156
|
+
# String types need to check for both nil and empty string
|
|
157
|
+
# Requires boolean parameter: true for present, false for absent
|
|
157
158
|
unless [ :string, :text ].include?(column_type)
|
|
158
|
-
scope :"#{field_name}_present", ->(
|
|
159
|
-
|
|
160
|
-
where(field.not_eq(nil))
|
|
161
|
-
else
|
|
162
|
-
where(field.eq(nil))
|
|
163
|
-
end
|
|
159
|
+
scope :"#{field_name}_present", ->(value) {
|
|
160
|
+
value ? where(field.not_eq(nil)) : where(field.eq(nil))
|
|
164
161
|
}
|
|
165
162
|
end
|
|
166
163
|
|
|
@@ -170,23 +167,17 @@ module BetterModel
|
|
|
170
167
|
register_predicable_scopes(*scopes_to_register)
|
|
171
168
|
end
|
|
172
169
|
|
|
173
|
-
# Genera predicati per campi stringa (
|
|
170
|
+
# Genera predicati per campi stringa (14 scope)
|
|
174
171
|
# Base predicates (_eq, _not_eq) are defined separately
|
|
175
|
-
# _present
|
|
172
|
+
# _present(bool), _blank(bool), _null(bool) handle presence with parameters
|
|
176
173
|
def define_string_predicates(field_name)
|
|
177
174
|
table = arel_table
|
|
178
175
|
field = table[field_name]
|
|
179
176
|
|
|
180
177
|
# String-specific presence check (checks both nil and empty string)
|
|
181
|
-
#
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
scope :"#{field_name}_present", ->(condition) {
|
|
185
|
-
if condition
|
|
186
|
-
where(field.not_eq(nil).and(field.not_eq("")))
|
|
187
|
-
else
|
|
188
|
-
where(field.eq(nil).or(field.eq("")))
|
|
189
|
-
end
|
|
178
|
+
# Requires boolean parameter: true for present, false for blank
|
|
179
|
+
scope :"#{field_name}_present", ->(value) {
|
|
180
|
+
value ? where(field.not_eq(nil).and(field.not_eq(""))) : where(field.eq(nil).or(field.eq("")))
|
|
190
181
|
}
|
|
191
182
|
|
|
192
183
|
# Pattern matching (4)
|
|
@@ -222,23 +213,13 @@ module BetterModel
|
|
|
222
213
|
scope :"#{field_name}_in", ->(values) { where(field.in(Array(values))) }
|
|
223
214
|
scope :"#{field_name}_not_in", ->(values) { where.not(field.in(Array(values))) }
|
|
224
215
|
|
|
225
|
-
# Presence (
|
|
226
|
-
#
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
scope :"#{field_name}_blank", ->(condition) {
|
|
230
|
-
if condition
|
|
231
|
-
where(field.eq(nil).or(field.eq("")))
|
|
232
|
-
else
|
|
233
|
-
where(field.not_eq(nil).and(field.not_eq("")))
|
|
234
|
-
end
|
|
216
|
+
# Presence predicates (3) - _present is overridden above for string-specific behavior
|
|
217
|
+
# All require boolean parameter: true for condition, false for negation
|
|
218
|
+
scope :"#{field_name}_blank", ->(value) {
|
|
219
|
+
value ? where(field.eq(nil).or(field.eq(""))) : where(field.not_eq(nil).and(field.not_eq("")))
|
|
235
220
|
}
|
|
236
|
-
scope :"#{field_name}_null", ->(
|
|
237
|
-
|
|
238
|
-
where(field.eq(nil))
|
|
239
|
-
else
|
|
240
|
-
where(field.not_eq(nil))
|
|
241
|
-
end
|
|
221
|
+
scope :"#{field_name}_null", ->(value) {
|
|
222
|
+
value ? where(field.eq(nil)) : where(field.not_eq(nil))
|
|
242
223
|
}
|
|
243
224
|
|
|
244
225
|
register_predicable_scopes(
|
|
@@ -257,8 +238,8 @@ module BetterModel
|
|
|
257
238
|
)
|
|
258
239
|
end
|
|
259
240
|
|
|
260
|
-
# Genera predicati per campi numerici (
|
|
261
|
-
# Base predicates (_eq, _not_eq, _present) are defined separately
|
|
241
|
+
# Genera predicati per campi numerici (11 scope)
|
|
242
|
+
# Base predicates (_eq, _not_eq, _present(bool)) are defined separately
|
|
262
243
|
def define_numeric_predicates(field_name)
|
|
263
244
|
table = arel_table
|
|
264
245
|
field = table[field_name]
|
|
@@ -290,16 +271,12 @@ module BetterModel
|
|
|
290
271
|
)
|
|
291
272
|
end
|
|
292
273
|
|
|
293
|
-
# Genera predicati per campi booleani
|
|
274
|
+
# Genera predicati per campi booleani (0 scope)
|
|
294
275
|
# Base predicates (_eq, _not_eq, _present) are defined separately
|
|
295
|
-
#
|
|
296
|
-
# Note: In v2.0.0, we removed _true and _false predicates as they were
|
|
297
|
-
# redundant with the base _eq predicate. Use these instead:
|
|
298
|
-
# field_eq(true) # instead of field_true
|
|
299
|
-
# field_eq(false) # instead of field_false
|
|
276
|
+
# Use _eq(true) or _eq(false) for boolean filtering
|
|
300
277
|
def define_boolean_predicates(field_name)
|
|
301
|
-
#
|
|
302
|
-
#
|
|
278
|
+
# No additional scopes needed for boolean fields
|
|
279
|
+
# Use field_eq(true) or field_eq(false) instead
|
|
303
280
|
end
|
|
304
281
|
|
|
305
282
|
# Genera predicati per campi array PostgreSQL (3 scope)
|
|
@@ -411,8 +388,9 @@ module BetterModel
|
|
|
411
388
|
)
|
|
412
389
|
end
|
|
413
390
|
|
|
414
|
-
# Genera predicati per campi data/datetime (
|
|
415
|
-
# Base predicates (_eq, _not_eq, _present) are defined separately
|
|
391
|
+
# Genera predicati per campi data/datetime (11 scope)
|
|
392
|
+
# Base predicates (_eq, _not_eq, _present(bool)) are defined separately
|
|
393
|
+
# Date convenience shortcuts removed except _within(duration)
|
|
416
394
|
def define_date_predicates(field_name)
|
|
417
395
|
table = arel_table
|
|
418
396
|
field = table[field_name]
|
|
@@ -431,37 +409,20 @@ module BetterModel
|
|
|
431
409
|
scope :"#{field_name}_in", ->(values) { where(field.in(Array(values))) }
|
|
432
410
|
scope :"#{field_name}_not_in", ->(values) { where.not(field.in(Array(values))) }
|
|
433
411
|
|
|
434
|
-
# Date
|
|
412
|
+
# Date convenience - only _within with explicit parameter
|
|
435
413
|
scope :"#{field_name}_within", ->(duration) {
|
|
436
414
|
# Auto-detect: ActiveSupport::Duration or numeric (days)
|
|
437
415
|
time_ago = duration.respond_to?(:ago) ? duration.ago : duration.to_i.days.ago
|
|
438
416
|
where(field.gteq(time_ago))
|
|
439
417
|
}
|
|
440
418
|
|
|
441
|
-
# Presence (
|
|
442
|
-
#
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
scope :"#{field_name}_blank", ->(condition) {
|
|
446
|
-
if condition
|
|
447
|
-
where(field.eq(nil))
|
|
448
|
-
else
|
|
449
|
-
where(field.not_eq(nil))
|
|
450
|
-
end
|
|
419
|
+
# Presence predicates (2) - _present(bool) is defined in base predicates
|
|
420
|
+
# _blank and _null require boolean parameter: true for condition, false for negation
|
|
421
|
+
scope :"#{field_name}_blank", ->(value) {
|
|
422
|
+
value ? where(field.eq(nil)) : where(field.not_eq(nil))
|
|
451
423
|
}
|
|
452
|
-
scope :"#{field_name}_null", ->(
|
|
453
|
-
|
|
454
|
-
where(field.eq(nil))
|
|
455
|
-
else
|
|
456
|
-
where(field.not_eq(nil))
|
|
457
|
-
end
|
|
458
|
-
}
|
|
459
|
-
scope :"#{field_name}_not_null", ->(condition) {
|
|
460
|
-
if condition
|
|
461
|
-
where(field.not_eq(nil))
|
|
462
|
-
else
|
|
463
|
-
where(field.eq(nil))
|
|
464
|
-
end
|
|
424
|
+
scope :"#{field_name}_null", ->(value) {
|
|
425
|
+
value ? where(field.eq(nil)) : where(field.not_eq(nil))
|
|
465
426
|
}
|
|
466
427
|
|
|
467
428
|
register_predicable_scopes(
|
|
@@ -475,8 +436,7 @@ module BetterModel
|
|
|
475
436
|
:"#{field_name}_not_in",
|
|
476
437
|
:"#{field_name}_within",
|
|
477
438
|
:"#{field_name}_blank",
|
|
478
|
-
:"#{field_name}_null"
|
|
479
|
-
:"#{field_name}_not_null"
|
|
439
|
+
:"#{field_name}_null"
|
|
480
440
|
)
|
|
481
441
|
end
|
|
482
442
|
|
|
@@ -90,6 +90,9 @@ module BetterModel
|
|
|
90
90
|
pagination = options.delete(:pagination)
|
|
91
91
|
orders = options.delete(:orders)
|
|
92
92
|
security = options.delete(:security)
|
|
93
|
+
includes_param = options.delete(:includes)
|
|
94
|
+
preload_param = options.delete(:preload)
|
|
95
|
+
eager_load_param = options.delete(:eager_load)
|
|
93
96
|
|
|
94
97
|
# If there are remaining unknown options, they might be misplaced predicates
|
|
95
98
|
if options.any?
|
|
@@ -132,6 +135,11 @@ module BetterModel
|
|
|
132
135
|
# Apply pagination
|
|
133
136
|
scope = apply_pagination(scope, pagination) if pagination.present?
|
|
134
137
|
|
|
138
|
+
# Apply eager loading associations
|
|
139
|
+
scope = apply_includes(scope, includes_param) if includes_param.present?
|
|
140
|
+
scope = apply_preload(scope, preload_param) if preload_param.present?
|
|
141
|
+
scope = apply_eager_load(scope, eager_load_param) if eager_load_param.present?
|
|
142
|
+
|
|
135
143
|
scope
|
|
136
144
|
end
|
|
137
145
|
|
|
@@ -227,13 +235,14 @@ module BetterModel
|
|
|
227
235
|
next if value.nil?
|
|
228
236
|
next if value.respond_to?(:empty?) && value.empty?
|
|
229
237
|
|
|
230
|
-
#
|
|
231
|
-
|
|
232
|
-
|
|
238
|
+
# Apply scope
|
|
239
|
+
scope = if value == true || value == "true"
|
|
240
|
+
# All predicates require parameters, pass true
|
|
241
|
+
scope.public_send(predicate_scope, true)
|
|
242
|
+
elsif value.is_a?(Array)
|
|
233
243
|
# Splat array values for predicates like _between that expect multiple args
|
|
234
244
|
scope.public_send(predicate_scope, *value)
|
|
235
245
|
else
|
|
236
|
-
# Pass value as parameter (all predicates require at least one parameter)
|
|
237
246
|
scope.public_send(predicate_scope, value)
|
|
238
247
|
end
|
|
239
248
|
end
|
|
@@ -319,6 +328,24 @@ module BetterModel
|
|
|
319
328
|
scope.limit(per_page).offset(offset)
|
|
320
329
|
end
|
|
321
330
|
|
|
331
|
+
# Applica includes per eager loading delle associazioni (usa LEFT OUTER JOIN)
|
|
332
|
+
def apply_includes(scope, includes_param)
|
|
333
|
+
return scope if includes_param.blank?
|
|
334
|
+
scope.includes(includes_param)
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Applica preload per eager loading delle associazioni (carica con query separate)
|
|
338
|
+
def apply_preload(scope, preload_param)
|
|
339
|
+
return scope if preload_param.blank?
|
|
340
|
+
scope.preload(preload_param)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# Applica eager_load per eager loading delle associazioni (forza LEFT OUTER JOIN)
|
|
344
|
+
def apply_eager_load(scope, eager_load_param)
|
|
345
|
+
return scope if eager_load_param.blank?
|
|
346
|
+
scope.eager_load(eager_load_param)
|
|
347
|
+
end
|
|
348
|
+
|
|
322
349
|
# Valida che i predicati obbligatori della security siano presenti con valori validi
|
|
323
350
|
def validate_security(security_name, predicates)
|
|
324
351
|
# Converti security_name a symbol
|
|
@@ -16,14 +16,14 @@ module BetterModel
|
|
|
16
16
|
#
|
|
17
17
|
# # Definisci transizioni
|
|
18
18
|
# transition :confirm, from: :pending, to: :confirmed do
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
19
|
+
# check { items.any? }
|
|
20
|
+
# check :customer_valid?
|
|
21
|
+
# check if: :is_ready?
|
|
22
22
|
#
|
|
23
23
|
# validate { errors.add(:base, "Invalid") unless valid_for_confirmation? }
|
|
24
24
|
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
25
|
+
# before_transition { prepare }
|
|
26
|
+
# after_transition { notify }
|
|
27
27
|
# end
|
|
28
28
|
# end
|
|
29
29
|
#
|
|
@@ -71,12 +71,12 @@ module BetterModel
|
|
|
71
71
|
# @example Transizione semplice
|
|
72
72
|
# transition :publish, from: :draft, to: :published
|
|
73
73
|
#
|
|
74
|
-
# @example Con
|
|
74
|
+
# @example Con checks e callbacks
|
|
75
75
|
# transition :confirm, from: :pending, to: :confirmed do
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
#
|
|
76
|
+
# check { valid? }
|
|
77
|
+
# check :ready_to_confirm?
|
|
78
|
+
# before_transition { prepare_confirmation }
|
|
79
|
+
# after_transition { send_email }
|
|
80
80
|
# end
|
|
81
81
|
#
|
|
82
82
|
# @example Da multipli stati
|
|
@@ -119,30 +119,30 @@ module BetterModel
|
|
|
119
119
|
end
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
-
# Definisce un
|
|
122
|
+
# Definisce un check per la transizione corrente
|
|
123
123
|
#
|
|
124
|
-
# I
|
|
124
|
+
# I checks sono precondizioni che devono essere vere per permettere la transizione.
|
|
125
125
|
#
|
|
126
|
-
# @overload
|
|
127
|
-
#
|
|
126
|
+
# @overload check(&block)
|
|
127
|
+
# Check con lambda/proc
|
|
128
128
|
# @yield Blocco da valutare nel contesto dell'istanza
|
|
129
129
|
# @example
|
|
130
|
-
#
|
|
130
|
+
# check { items.any? && customer.present? }
|
|
131
131
|
#
|
|
132
|
-
# @overload
|
|
133
|
-
#
|
|
132
|
+
# @overload check(method_name)
|
|
133
|
+
# Check con metodo
|
|
134
134
|
# @param method_name [Symbol] Nome del metodo da chiamare
|
|
135
135
|
# @example
|
|
136
|
-
#
|
|
136
|
+
# check :customer_valid?
|
|
137
137
|
#
|
|
138
|
-
# @overload
|
|
139
|
-
#
|
|
138
|
+
# @overload check(if: predicate)
|
|
139
|
+
# Check con Statusable predicate
|
|
140
140
|
# @param if [Symbol] Nome del predicate (integrazione Statusable)
|
|
141
141
|
# @example
|
|
142
|
-
#
|
|
142
|
+
# check if: :is_ready_for_publishing?
|
|
143
143
|
#
|
|
144
|
-
def
|
|
145
|
-
raise StateableError, "
|
|
144
|
+
def check(method_name = nil, if: nil, &block)
|
|
145
|
+
raise StateableError, "check can only be called inside a transition block" unless @current_transition
|
|
146
146
|
|
|
147
147
|
if block_given?
|
|
148
148
|
@current_transition[:guards] << { type: :block, block: block }
|
|
@@ -151,7 +151,7 @@ module BetterModel
|
|
|
151
151
|
elsif binding.local_variable_get(:if)
|
|
152
152
|
@current_transition[:guards] << { type: :predicate, predicate: binding.local_variable_get(:if) }
|
|
153
153
|
else
|
|
154
|
-
raise ArgumentError, "
|
|
154
|
+
raise ArgumentError, "check requires either a block, method name, or if: option"
|
|
155
155
|
end
|
|
156
156
|
end
|
|
157
157
|
|
|
@@ -175,59 +175,59 @@ module BetterModel
|
|
|
175
175
|
@current_transition[:validations] << block
|
|
176
176
|
end
|
|
177
177
|
|
|
178
|
-
# Definisce un callback
|
|
178
|
+
# Definisce un callback before_transition per la transizione corrente
|
|
179
179
|
#
|
|
180
|
-
# I
|
|
180
|
+
# I before_transition callbacks sono eseguiti prima della transizione di stato.
|
|
181
181
|
#
|
|
182
|
-
# @overload
|
|
183
|
-
#
|
|
182
|
+
# @overload before_transition(&block)
|
|
183
|
+
# Before_transition callback con lambda/proc
|
|
184
184
|
# @yield Blocco da eseguire
|
|
185
185
|
# @example
|
|
186
|
-
#
|
|
186
|
+
# before_transition { calculate_total }
|
|
187
187
|
#
|
|
188
|
-
# @overload
|
|
189
|
-
#
|
|
188
|
+
# @overload before_transition(method_name)
|
|
189
|
+
# Before_transition callback con metodo
|
|
190
190
|
# @param method_name [Symbol] Nome del metodo da chiamare
|
|
191
191
|
# @example
|
|
192
|
-
#
|
|
192
|
+
# before_transition :calculate_total
|
|
193
193
|
#
|
|
194
|
-
def
|
|
195
|
-
raise StateableError, "
|
|
194
|
+
def before_transition(method_name = nil, &block)
|
|
195
|
+
raise StateableError, "before_transition can only be called inside a transition block" unless @current_transition
|
|
196
196
|
|
|
197
197
|
if block_given?
|
|
198
198
|
@current_transition[:before_callbacks] << { type: :block, block: block }
|
|
199
199
|
elsif method_name
|
|
200
200
|
@current_transition[:before_callbacks] << { type: :method, method: method_name }
|
|
201
201
|
else
|
|
202
|
-
raise ArgumentError, "
|
|
202
|
+
raise ArgumentError, "before_transition requires either a block or method name"
|
|
203
203
|
end
|
|
204
204
|
end
|
|
205
205
|
|
|
206
|
-
# Definisce un callback
|
|
206
|
+
# Definisce un callback after_transition per la transizione corrente
|
|
207
207
|
#
|
|
208
|
-
# Gli
|
|
208
|
+
# Gli after_transition callbacks sono eseguiti dopo la transizione di stato.
|
|
209
209
|
#
|
|
210
|
-
# @overload
|
|
211
|
-
#
|
|
210
|
+
# @overload after_transition(&block)
|
|
211
|
+
# After_transition callback con lambda/proc
|
|
212
212
|
# @yield Blocco da eseguire
|
|
213
213
|
# @example
|
|
214
|
-
#
|
|
214
|
+
# after_transition { send_notification }
|
|
215
215
|
#
|
|
216
|
-
# @overload
|
|
217
|
-
#
|
|
216
|
+
# @overload after_transition(method_name)
|
|
217
|
+
# After_transition callback con metodo
|
|
218
218
|
# @param method_name [Symbol] Nome del metodo da chiamare
|
|
219
219
|
# @example
|
|
220
|
-
#
|
|
220
|
+
# after_transition :send_notification
|
|
221
221
|
#
|
|
222
|
-
def
|
|
223
|
-
raise StateableError, "
|
|
222
|
+
def after_transition(method_name = nil, &block)
|
|
223
|
+
raise StateableError, "after_transition can only be called inside a transition block" unless @current_transition
|
|
224
224
|
|
|
225
225
|
if block_given?
|
|
226
226
|
@current_transition[:after_callbacks] << { type: :block, block: block }
|
|
227
227
|
elsif method_name
|
|
228
228
|
@current_transition[:after_callbacks] << { type: :method, method: method_name }
|
|
229
229
|
else
|
|
230
|
-
raise ArgumentError, "
|
|
230
|
+
raise ArgumentError, "after_transition requires either a block or method name"
|
|
231
231
|
end
|
|
232
232
|
end
|
|
233
233
|
|
|
@@ -26,15 +26,18 @@ module BetterModel
|
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
# Raised when a
|
|
30
|
-
class
|
|
31
|
-
def initialize(event,
|
|
32
|
-
msg = "
|
|
33
|
-
msg += ": #{
|
|
29
|
+
# Raised when a check condition fails
|
|
30
|
+
class CheckFailedError < StateableError
|
|
31
|
+
def initialize(event, check_description = nil)
|
|
32
|
+
msg = "Check failed for transition #{event.inspect}"
|
|
33
|
+
msg += ": #{check_description}" if check_description
|
|
34
34
|
super(msg)
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
# Alias for backwards compatibility
|
|
39
|
+
GuardFailedError = CheckFailedError
|
|
40
|
+
|
|
38
41
|
# Raised when a transition validation fails
|
|
39
42
|
class ValidationFailedError < StateableError
|
|
40
43
|
def initialize(event, errors)
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module BetterModel
|
|
4
4
|
module Stateable
|
|
5
|
-
#
|
|
5
|
+
# Check evaluator per Stateable (internal class name remains Guard for compatibility)
|
|
6
6
|
#
|
|
7
|
-
# Valuta le
|
|
8
|
-
# Supporta tre tipi di
|
|
7
|
+
# Valuta le check conditions per determinare se una transizione è permessa.
|
|
8
|
+
# Supporta tre tipi di checks:
|
|
9
9
|
# - Block: lambda/proc valutato nel contesto dell'istanza
|
|
10
10
|
# - Method: metodo chiamato sull'istanza
|
|
11
11
|
# - Predicate: integrazione con Statusable (is_ready?, etc.)
|
|
@@ -16,10 +16,10 @@ module BetterModel
|
|
|
16
16
|
@guard_config = guard_config
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
# Valuta il
|
|
19
|
+
# Valuta il check
|
|
20
20
|
#
|
|
21
|
-
# @return [Boolean] true se il
|
|
22
|
-
# @raise [
|
|
21
|
+
# @return [Boolean] true se il check passa
|
|
22
|
+
# @raise [CheckFailedError] Se il check fallisce (opzionale, dipende dal contesto)
|
|
23
23
|
#
|
|
24
24
|
def evaluate
|
|
25
25
|
case @guard_config[:type]
|
|
@@ -30,53 +30,53 @@ module BetterModel
|
|
|
30
30
|
when :predicate
|
|
31
31
|
evaluate_predicate
|
|
32
32
|
else
|
|
33
|
-
raise StateableError, "Unknown
|
|
33
|
+
raise StateableError, "Unknown check type: #{@guard_config[:type]}"
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
# Descrizione del
|
|
37
|
+
# Descrizione del check per messaggi di errore
|
|
38
38
|
#
|
|
39
39
|
# @return [String] Descrizione human-readable
|
|
40
40
|
#
|
|
41
41
|
def description
|
|
42
42
|
case @guard_config[:type]
|
|
43
43
|
when :block
|
|
44
|
-
"block
|
|
44
|
+
"block check"
|
|
45
45
|
when :method
|
|
46
|
-
"method
|
|
46
|
+
"method check: #{@guard_config[:method]}"
|
|
47
47
|
when :predicate
|
|
48
|
-
"predicate
|
|
48
|
+
"predicate check: #{@guard_config[:predicate]}"
|
|
49
49
|
else
|
|
50
|
-
"unknown
|
|
50
|
+
"unknown check"
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
private
|
|
55
55
|
|
|
56
|
-
# Valuta un
|
|
56
|
+
# Valuta un check block
|
|
57
57
|
def evaluate_block
|
|
58
58
|
block = @guard_config[:block]
|
|
59
59
|
@instance.instance_exec(&block)
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
# Valuta un
|
|
62
|
+
# Valuta un check method
|
|
63
63
|
def evaluate_method
|
|
64
64
|
method_name = @guard_config[:method]
|
|
65
65
|
|
|
66
66
|
unless @instance.respond_to?(method_name, true)
|
|
67
|
-
raise NoMethodError, "
|
|
67
|
+
raise NoMethodError, "Check method '#{method_name}' not found in #{@instance.class.name}. " \
|
|
68
68
|
"Define it in your model: def #{method_name}; ...; end"
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
@instance.send(method_name)
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
# Valuta un
|
|
74
|
+
# Valuta un check predicate (integrazione Statusable)
|
|
75
75
|
def evaluate_predicate
|
|
76
76
|
predicate_name = @guard_config[:predicate]
|
|
77
77
|
|
|
78
78
|
unless @instance.respond_to?(predicate_name)
|
|
79
|
-
raise NoMethodError, "
|
|
79
|
+
raise NoMethodError, "Check predicate '#{predicate_name}' not found in #{@instance.class.name}. " \
|
|
80
80
|
"Make sure Statusable is enabled and the predicate is defined: is :ready, -> { ... }"
|
|
81
81
|
end
|
|
82
82
|
|
|
@@ -5,9 +5,9 @@ module BetterModel
|
|
|
5
5
|
# Transition executor per Stateable
|
|
6
6
|
#
|
|
7
7
|
# Gestisce l'esecuzione di una transizione di stato, includendo:
|
|
8
|
-
# - Valutazione
|
|
8
|
+
# - Valutazione checks
|
|
9
9
|
# - Esecuzione validazioni
|
|
10
|
-
# - Esecuzione callbacks (
|
|
10
|
+
# - Esecuzione callbacks (before_transition/after_transition/around)
|
|
11
11
|
# - Aggiornamento stato nel database
|
|
12
12
|
# - Creazione record StateTransition per storico
|
|
13
13
|
#
|
|
@@ -23,14 +23,14 @@ module BetterModel
|
|
|
23
23
|
|
|
24
24
|
# Esegue la transizione
|
|
25
25
|
#
|
|
26
|
-
# @raise [
|
|
26
|
+
# @raise [CheckFailedError] Se un check fallisce
|
|
27
27
|
# @raise [ValidationFailedError] Se una validazione fallisce
|
|
28
28
|
# @raise [ActiveRecord::RecordInvalid] Se il save! fallisce
|
|
29
29
|
# @return [Boolean] true se la transizione ha successo
|
|
30
30
|
#
|
|
31
31
|
def execute!
|
|
32
|
-
# 1. Valuta
|
|
33
|
-
|
|
32
|
+
# 1. Valuta checks
|
|
33
|
+
evaluate_checks!
|
|
34
34
|
|
|
35
35
|
# 2. Esegui validazioni
|
|
36
36
|
execute_validations!
|
|
@@ -52,15 +52,15 @@ module BetterModel
|
|
|
52
52
|
|
|
53
53
|
private
|
|
54
54
|
|
|
55
|
-
# Valuta tutti i
|
|
56
|
-
def
|
|
57
|
-
|
|
55
|
+
# Valuta tutti i checks
|
|
56
|
+
def evaluate_checks!
|
|
57
|
+
checks = @config[:guards] || [] # Manteniamo :guards per compatibilità interna
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
checks.each do |check_config|
|
|
60
|
+
check = Guard.new(@instance, check_config) # Guard class handles the logic
|
|
61
61
|
|
|
62
|
-
unless
|
|
63
|
-
raise
|
|
62
|
+
unless check.evaluate
|
|
63
|
+
raise CheckFailedError.new(@event, check.description)
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
end
|
|
@@ -101,7 +101,7 @@ module BetterModel
|
|
|
101
101
|
|
|
102
102
|
# Esegue la transizione effettiva
|
|
103
103
|
def perform_transition!
|
|
104
|
-
# 1. Esegui
|
|
104
|
+
# 1. Esegui before_transition callbacks
|
|
105
105
|
execute_callbacks(@config[:before_callbacks] || [])
|
|
106
106
|
|
|
107
107
|
# 2. Aggiorna stato
|
|
@@ -113,7 +113,7 @@ module BetterModel
|
|
|
113
113
|
# 4. Crea record StateTransition
|
|
114
114
|
create_state_transition_record
|
|
115
115
|
|
|
116
|
-
# 5. Esegui
|
|
116
|
+
# 5. Esegui after_transition callbacks
|
|
117
117
|
execute_callbacks(@config[:after_callbacks] || [])
|
|
118
118
|
end
|
|
119
119
|
|