better_model 1.3.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 +304 -167
- data/lib/better_model/archivable.rb +2 -2
- data/lib/better_model/predicable.rb +43 -62
- data/lib/better_model/searchable.rb +28 -1
- 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
- metadata +3 -3
|
@@ -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,
|
|
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
|
|
@@ -152,8 +154,11 @@ module BetterModel
|
|
|
152
154
|
|
|
153
155
|
# Presence - skip for string/text types as they get specialized version
|
|
154
156
|
# String types need to check for both nil and empty string
|
|
157
|
+
# Requires boolean parameter: true for present, false for absent
|
|
155
158
|
unless [ :string, :text ].include?(column_type)
|
|
156
|
-
scope :"#{field_name}_present", ->
|
|
159
|
+
scope :"#{field_name}_present", ->(value) {
|
|
160
|
+
value ? where(field.not_eq(nil)) : where(field.eq(nil))
|
|
161
|
+
}
|
|
157
162
|
end
|
|
158
163
|
|
|
159
164
|
scopes_to_register = [ :"#{field_name}_eq", :"#{field_name}_not_eq" ]
|
|
@@ -162,15 +167,18 @@ module BetterModel
|
|
|
162
167
|
register_predicable_scopes(*scopes_to_register)
|
|
163
168
|
end
|
|
164
169
|
|
|
165
|
-
# Genera predicati per campi stringa (
|
|
170
|
+
# Genera predicati per campi stringa (14 scope)
|
|
166
171
|
# Base predicates (_eq, _not_eq) are defined separately
|
|
167
|
-
# _present
|
|
172
|
+
# _present(bool), _blank(bool), _null(bool) handle presence with parameters
|
|
168
173
|
def define_string_predicates(field_name)
|
|
169
174
|
table = arel_table
|
|
170
175
|
field = table[field_name]
|
|
171
176
|
|
|
172
177
|
# String-specific presence check (checks both nil and empty string)
|
|
173
|
-
|
|
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("")))
|
|
181
|
+
}
|
|
174
182
|
|
|
175
183
|
# Pattern matching (4)
|
|
176
184
|
scope :"#{field_name}_matches", ->(pattern) { where(field.matches(pattern)) }
|
|
@@ -205,9 +213,14 @@ module BetterModel
|
|
|
205
213
|
scope :"#{field_name}_in", ->(values) { where(field.in(Array(values))) }
|
|
206
214
|
scope :"#{field_name}_not_in", ->(values) { where.not(field.in(Array(values))) }
|
|
207
215
|
|
|
208
|
-
# Presence (
|
|
209
|
-
|
|
210
|
-
scope :"#{field_name}
|
|
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("")))
|
|
220
|
+
}
|
|
221
|
+
scope :"#{field_name}_null", ->(value) {
|
|
222
|
+
value ? where(field.eq(nil)) : where(field.not_eq(nil))
|
|
223
|
+
}
|
|
211
224
|
|
|
212
225
|
register_predicable_scopes(
|
|
213
226
|
:"#{field_name}_matches",
|
|
@@ -225,8 +238,8 @@ module BetterModel
|
|
|
225
238
|
)
|
|
226
239
|
end
|
|
227
240
|
|
|
228
|
-
# Genera predicati per campi numerici (
|
|
229
|
-
# 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
|
|
230
243
|
def define_numeric_predicates(field_name)
|
|
231
244
|
table = arel_table
|
|
232
245
|
field = table[field_name]
|
|
@@ -258,20 +271,12 @@ module BetterModel
|
|
|
258
271
|
)
|
|
259
272
|
end
|
|
260
273
|
|
|
261
|
-
# Genera predicati per campi booleani (
|
|
274
|
+
# Genera predicati per campi booleani (0 scope)
|
|
262
275
|
# Base predicates (_eq, _not_eq, _present) are defined separately
|
|
276
|
+
# Use _eq(true) or _eq(false) for boolean filtering
|
|
263
277
|
def define_boolean_predicates(field_name)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
# Boolean shortcuts (2)
|
|
268
|
-
scope :"#{field_name}_true", -> { where(field.eq(true)) }
|
|
269
|
-
scope :"#{field_name}_false", -> { where(field.eq(false)) }
|
|
270
|
-
|
|
271
|
-
register_predicable_scopes(
|
|
272
|
-
:"#{field_name}_true",
|
|
273
|
-
:"#{field_name}_false"
|
|
274
|
-
)
|
|
278
|
+
# No additional scopes needed for boolean fields
|
|
279
|
+
# Use field_eq(true) or field_eq(false) instead
|
|
275
280
|
end
|
|
276
281
|
|
|
277
282
|
# Genera predicati per campi array PostgreSQL (3 scope)
|
|
@@ -383,8 +388,9 @@ module BetterModel
|
|
|
383
388
|
)
|
|
384
389
|
end
|
|
385
390
|
|
|
386
|
-
# Genera predicati per campi data/datetime (
|
|
387
|
-
# 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)
|
|
388
394
|
def define_date_predicates(field_name)
|
|
389
395
|
table = arel_table
|
|
390
396
|
field = table[field_name]
|
|
@@ -403,38 +409,21 @@ module BetterModel
|
|
|
403
409
|
scope :"#{field_name}_in", ->(values) { where(field.in(Array(values))) }
|
|
404
410
|
scope :"#{field_name}_not_in", ->(values) { where.not(field.in(Array(values))) }
|
|
405
411
|
|
|
406
|
-
# Date convenience
|
|
407
|
-
scope :"#{field_name}_today", -> {
|
|
408
|
-
where(field.between(Date.current.beginning_of_day..Date.current.end_of_day))
|
|
409
|
-
}
|
|
410
|
-
scope :"#{field_name}_yesterday", -> {
|
|
411
|
-
where(field.between(1.day.ago.beginning_of_day..1.day.ago.end_of_day))
|
|
412
|
-
}
|
|
413
|
-
scope :"#{field_name}_this_week", -> {
|
|
414
|
-
where(field.gteq(Date.current.beginning_of_week))
|
|
415
|
-
}
|
|
416
|
-
scope :"#{field_name}_this_month", -> {
|
|
417
|
-
where(field.gteq(Date.current.beginning_of_month))
|
|
418
|
-
}
|
|
419
|
-
scope :"#{field_name}_this_year", -> {
|
|
420
|
-
where(field.gteq(Date.current.beginning_of_year))
|
|
421
|
-
}
|
|
422
|
-
scope :"#{field_name}_past", -> {
|
|
423
|
-
where(field.lt(Time.current))
|
|
424
|
-
}
|
|
425
|
-
scope :"#{field_name}_future", -> {
|
|
426
|
-
where(field.gt(Time.current))
|
|
427
|
-
}
|
|
412
|
+
# Date convenience - only _within with explicit parameter
|
|
428
413
|
scope :"#{field_name}_within", ->(duration) {
|
|
429
414
|
# Auto-detect: ActiveSupport::Duration or numeric (days)
|
|
430
415
|
time_ago = duration.respond_to?(:ago) ? duration.ago : duration.to_i.days.ago
|
|
431
416
|
where(field.gteq(time_ago))
|
|
432
417
|
}
|
|
433
418
|
|
|
434
|
-
# Presence (
|
|
435
|
-
|
|
436
|
-
scope :"#{field_name}
|
|
437
|
-
|
|
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))
|
|
423
|
+
}
|
|
424
|
+
scope :"#{field_name}_null", ->(value) {
|
|
425
|
+
value ? where(field.eq(nil)) : where(field.not_eq(nil))
|
|
426
|
+
}
|
|
438
427
|
|
|
439
428
|
register_predicable_scopes(
|
|
440
429
|
:"#{field_name}_lt",
|
|
@@ -445,17 +434,9 @@ module BetterModel
|
|
|
445
434
|
:"#{field_name}_not_between",
|
|
446
435
|
:"#{field_name}_in",
|
|
447
436
|
:"#{field_name}_not_in",
|
|
448
|
-
:"#{field_name}_today",
|
|
449
|
-
:"#{field_name}_yesterday",
|
|
450
|
-
:"#{field_name}_this_week",
|
|
451
|
-
:"#{field_name}_this_month",
|
|
452
|
-
:"#{field_name}_this_year",
|
|
453
|
-
:"#{field_name}_past",
|
|
454
|
-
:"#{field_name}_future",
|
|
455
437
|
:"#{field_name}_within",
|
|
456
438
|
:"#{field_name}_blank",
|
|
457
|
-
:"#{field_name}_null"
|
|
458
|
-
:"#{field_name}_not_null"
|
|
439
|
+
:"#{field_name}_null"
|
|
459
440
|
)
|
|
460
441
|
end
|
|
461
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
|
|
|
@@ -229,7 +237,8 @@ module BetterModel
|
|
|
229
237
|
|
|
230
238
|
# Apply scope
|
|
231
239
|
scope = if value == true || value == "true"
|
|
232
|
-
|
|
240
|
+
# All predicates require parameters, pass true
|
|
241
|
+
scope.public_send(predicate_scope, true)
|
|
233
242
|
elsif value.is_a?(Array)
|
|
234
243
|
# Splat array values for predicates like _between that expect multiple args
|
|
235
244
|
scope.public_send(predicate_scope, *value)
|
|
@@ -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
|
|
|
@@ -32,18 +32,18 @@
|
|
|
32
32
|
#
|
|
33
33
|
# # Transizioni
|
|
34
34
|
# transition :confirm, from: :pending, to: :confirmed do
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
35
|
+
# check { items.any? }
|
|
36
|
+
# check :customer_valid?
|
|
37
|
+
# check if: :is_payable? # Statusable integration
|
|
38
38
|
#
|
|
39
39
|
# validate { errors.add(:base, "Stock unavailable") unless stock_available? }
|
|
40
40
|
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
41
|
+
# before_transition { calculate_total }
|
|
42
|
+
# after_transition { send_confirmation_email }
|
|
43
43
|
# end
|
|
44
44
|
#
|
|
45
45
|
# transition :pay, from: :confirmed, to: :paid do
|
|
46
|
-
#
|
|
46
|
+
# before_transition { charge_payment }
|
|
47
47
|
# end
|
|
48
48
|
#
|
|
49
49
|
# transition :cancel, from: [:pending, :confirmed], to: :cancelled
|
|
@@ -119,15 +119,15 @@ module BetterModel
|
|
|
119
119
|
# transition :publish, from: :draft, to: :published
|
|
120
120
|
# end
|
|
121
121
|
#
|
|
122
|
-
# @example Con
|
|
122
|
+
# @example Con checks e callbacks
|
|
123
123
|
# stateable do
|
|
124
124
|
# state :pending, initial: true
|
|
125
125
|
# state :confirmed
|
|
126
126
|
#
|
|
127
127
|
# transition :confirm, from: :pending, to: :confirmed do
|
|
128
|
-
#
|
|
129
|
-
#
|
|
130
|
-
#
|
|
128
|
+
# check { valid? }
|
|
129
|
+
# before_transition { prepare_confirmation }
|
|
130
|
+
# after_transition { send_notification }
|
|
131
131
|
# end
|
|
132
132
|
# end
|
|
133
133
|
#
|
|
@@ -270,7 +270,7 @@ module BetterModel
|
|
|
270
270
|
# @param event [Symbol] Nome della transizione
|
|
271
271
|
# @param metadata [Hash] Metadata opzionale da salvare nella StateTransition
|
|
272
272
|
# @raise [InvalidTransitionError] Se la transizione non è valida
|
|
273
|
-
# @raise [
|
|
273
|
+
# @raise [CheckFailedError] Se un check fallisce
|
|
274
274
|
# @raise [ValidationFailedError] Se una validazione fallisce
|
|
275
275
|
# @return [Boolean] true se la transizione ha successo
|
|
276
276
|
#
|