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.
- checksums.yaml +4 -4
- data/README.md +96 -13
- data/lib/better_model/archivable.rb +203 -91
- data/lib/better_model/errors/archivable/already_archived_error.rb +11 -0
- data/lib/better_model/errors/archivable/archivable_error.rb +13 -0
- data/lib/better_model/errors/archivable/configuration_error.rb +10 -0
- data/lib/better_model/errors/archivable/not_archived_error.rb +11 -0
- data/lib/better_model/errors/archivable/not_enabled_error.rb +11 -0
- data/lib/better_model/errors/better_model_error.rb +9 -0
- data/lib/better_model/errors/permissible/configuration_error.rb +9 -0
- data/lib/better_model/errors/permissible/permissible_error.rb +13 -0
- data/lib/better_model/errors/predicable/configuration_error.rb +9 -0
- data/lib/better_model/errors/predicable/predicable_error.rb +13 -0
- data/lib/better_model/errors/searchable/configuration_error.rb +9 -0
- data/lib/better_model/errors/searchable/invalid_order_error.rb +11 -0
- data/lib/better_model/errors/searchable/invalid_pagination_error.rb +11 -0
- data/lib/better_model/errors/searchable/invalid_predicate_error.rb +11 -0
- data/lib/better_model/errors/searchable/invalid_security_error.rb +11 -0
- data/lib/better_model/errors/searchable/searchable_error.rb +13 -0
- data/lib/better_model/errors/sortable/configuration_error.rb +10 -0
- data/lib/better_model/errors/sortable/sortable_error.rb +13 -0
- data/lib/better_model/errors/stateable/check_failed_error.rb +14 -0
- data/lib/better_model/errors/stateable/configuration_error.rb +10 -0
- data/lib/better_model/errors/stateable/invalid_state_error.rb +11 -0
- data/lib/better_model/errors/stateable/invalid_transition_error.rb +11 -0
- data/lib/better_model/errors/stateable/not_enabled_error.rb +11 -0
- data/lib/better_model/errors/stateable/stateable_error.rb +13 -0
- data/lib/better_model/errors/stateable/validation_failed_error.rb +11 -0
- data/lib/better_model/errors/statusable/configuration_error.rb +9 -0
- data/lib/better_model/errors/statusable/statusable_error.rb +13 -0
- data/lib/better_model/errors/taggable/configuration_error.rb +10 -0
- data/lib/better_model/errors/taggable/taggable_error.rb +13 -0
- data/lib/better_model/errors/traceable/configuration_error.rb +10 -0
- data/lib/better_model/errors/traceable/not_enabled_error.rb +11 -0
- data/lib/better_model/errors/traceable/traceable_error.rb +13 -0
- data/lib/better_model/errors/validatable/configuration_error.rb +10 -0
- data/lib/better_model/errors/validatable/not_enabled_error.rb +11 -0
- data/lib/better_model/errors/validatable/validatable_error.rb +13 -0
- data/lib/better_model/models/state_transition.rb +122 -0
- data/lib/better_model/models/version.rb +68 -0
- data/lib/better_model/permissible.rb +103 -52
- data/lib/better_model/predicable.rb +114 -63
- data/lib/better_model/repositable/base_repository.rb +232 -0
- data/lib/better_model/repositable.rb +32 -0
- data/lib/better_model/searchable.rb +92 -92
- data/lib/better_model/sortable.rb +137 -41
- data/lib/better_model/stateable/configurator.rb +71 -53
- data/lib/better_model/stateable/guard.rb +35 -15
- data/lib/better_model/stateable/transition.rb +59 -30
- data/lib/better_model/stateable.rb +33 -15
- data/lib/better_model/statusable.rb +84 -52
- data/lib/better_model/taggable.rb +120 -75
- data/lib/better_model/traceable.rb +56 -48
- data/lib/better_model/validatable/configurator.rb +49 -172
- data/lib/better_model/validatable.rb +88 -113
- data/lib/better_model/version.rb +1 -1
- data/lib/better_model.rb +42 -5
- data/lib/generators/better_model/repository/repository_generator.rb +141 -0
- data/lib/generators/better_model/repository/templates/application_repository.rb.tt +21 -0
- data/lib/generators/better_model/repository/templates/repository.rb.tt +71 -0
- data/lib/generators/better_model/stateable/templates/README +1 -1
- metadata +44 -7
- data/lib/better_model/state_transition.rb +0 -106
- data/lib/better_model/stateable/errors.rb +0 -48
- data/lib/better_model/validatable/business_rule_validator.rb +0 -47
- data/lib/better_model/validatable/order_validator.rb +0 -77
- data/lib/better_model/version_record.rb +0 -66
|
@@ -2,56 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
module BetterModel
|
|
4
4
|
module Validatable
|
|
5
|
-
# Configurator
|
|
5
|
+
# Configurator for Validatable DSL.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
7
|
+
# This configurator enables defining validations declaratively
|
|
8
|
+
# within the `validatable do...end` block.
|
|
9
9
|
#
|
|
10
|
-
#
|
|
10
|
+
# @example
|
|
11
11
|
# validatable do
|
|
12
|
-
# #
|
|
12
|
+
# # Basic validations
|
|
13
13
|
# check :title, :content, presence: true
|
|
14
14
|
# check :email, format: { with: URI::MailTo::EMAIL_REGEXP }
|
|
15
15
|
#
|
|
16
|
-
# #
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# check :author_id, presence: true
|
|
20
|
-
# end
|
|
16
|
+
# # Complex validations
|
|
17
|
+
# check_complex :valid_pricing
|
|
18
|
+
# check_complex :stock_check
|
|
21
19
|
#
|
|
22
|
-
# #
|
|
23
|
-
# validate_unless :is_draft? do
|
|
24
|
-
# check :reviewer_id, presence: true
|
|
25
|
-
# end
|
|
26
|
-
#
|
|
27
|
-
# # Cross-field validations
|
|
28
|
-
# validate_order :starts_at, :before, :ends_at
|
|
29
|
-
# validate_order :min_price, :lteq, :max_price
|
|
30
|
-
#
|
|
31
|
-
# # Business rules
|
|
32
|
-
# validate_business_rule :valid_category
|
|
33
|
-
# validate_business_rule :author_has_permission, on: :create
|
|
34
|
-
#
|
|
35
|
-
# # Gruppi di validazioni
|
|
20
|
+
# # Validation groups
|
|
36
21
|
# validation_group :step1, [:email, :password]
|
|
37
22
|
# validation_group :step2, [:first_name, :last_name]
|
|
38
23
|
# end
|
|
39
24
|
#
|
|
25
|
+
# @api private
|
|
40
26
|
class Configurator
|
|
41
27
|
attr_reader :groups
|
|
42
28
|
|
|
29
|
+
# Initialize a new Configurator.
|
|
30
|
+
#
|
|
31
|
+
# @param model_class [Class] Model class being configured
|
|
43
32
|
def initialize(model_class)
|
|
44
33
|
@model_class = model_class
|
|
45
|
-
@
|
|
46
|
-
@order_validations = []
|
|
47
|
-
@business_rules = []
|
|
34
|
+
@complex_validations = []
|
|
48
35
|
@groups = {}
|
|
49
36
|
end
|
|
50
37
|
|
|
51
|
-
#
|
|
38
|
+
# Define standard validations on fields.
|
|
39
|
+
#
|
|
40
|
+
# Delegates to ActiveRecord's validates method.
|
|
52
41
|
#
|
|
53
|
-
# @param fields [Array<Symbol>]
|
|
54
|
-
# @param options [Hash]
|
|
42
|
+
# @param fields [Array<Symbol>] Field names
|
|
43
|
+
# @param options [Hash] Validation options (presence, format, etc.)
|
|
55
44
|
#
|
|
56
45
|
# @example
|
|
57
46
|
# check :title, :content, presence: true
|
|
@@ -59,164 +48,54 @@ module BetterModel
|
|
|
59
48
|
# check :age, numericality: { greater_than: 0 }
|
|
60
49
|
#
|
|
61
50
|
def check(*fields, **options)
|
|
62
|
-
|
|
63
|
-
if @current_conditional
|
|
64
|
-
@current_conditional[:validations] << {
|
|
65
|
-
fields: fields,
|
|
66
|
-
options: options
|
|
67
|
-
}
|
|
68
|
-
else
|
|
69
|
-
# Altrimenti applica direttamente alla classe
|
|
70
|
-
# Questo viene fatto subito, non in apply_validatable_config
|
|
71
|
-
@model_class.validates(*fields, **options)
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Validazioni condizionali (se condizione è vera)
|
|
76
|
-
#
|
|
77
|
-
# @param condition [Symbol, Proc] Condizione da verificare
|
|
78
|
-
# @yield Blocco con validazioni da applicare se condizione è vera
|
|
79
|
-
#
|
|
80
|
-
# @example Con simbolo (metodo)
|
|
81
|
-
# validate_if :is_published? do
|
|
82
|
-
# check :published_at, presence: true
|
|
83
|
-
# end
|
|
84
|
-
#
|
|
85
|
-
# @example Con lambda
|
|
86
|
-
# validate_if -> { status == "published" } do
|
|
87
|
-
# check :published_at, presence: true
|
|
88
|
-
# end
|
|
89
|
-
#
|
|
90
|
-
def validate_if(condition, &block)
|
|
91
|
-
raise ArgumentError, "validate_if requires a block" unless block_given?
|
|
92
|
-
|
|
93
|
-
conditional = {
|
|
94
|
-
condition: condition,
|
|
95
|
-
negate: false,
|
|
96
|
-
validations: []
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
# Set current conditional per catturare le validate dentro il blocco
|
|
100
|
-
@current_conditional = conditional
|
|
101
|
-
instance_eval(&block)
|
|
102
|
-
@current_conditional = nil
|
|
103
|
-
|
|
104
|
-
@conditional_validations << conditional
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# Validazioni condizionali negate (se condizione è falsa)
|
|
108
|
-
#
|
|
109
|
-
# @param condition [Symbol, Proc] Condizione da verificare
|
|
110
|
-
# @yield Blocco con validazioni da applicare se condizione è falsa
|
|
111
|
-
#
|
|
112
|
-
# @example
|
|
113
|
-
# validate_unless :is_draft? do
|
|
114
|
-
# check :reviewer_id, presence: true
|
|
115
|
-
# end
|
|
116
|
-
#
|
|
117
|
-
def validate_unless(condition, &block)
|
|
118
|
-
raise ArgumentError, "validate_unless requires a block" unless block_given?
|
|
119
|
-
|
|
120
|
-
conditional = {
|
|
121
|
-
condition: condition,
|
|
122
|
-
negate: true,
|
|
123
|
-
validations: []
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
@current_conditional = conditional
|
|
127
|
-
instance_eval(&block)
|
|
128
|
-
@current_conditional = nil
|
|
129
|
-
|
|
130
|
-
@conditional_validations << conditional
|
|
51
|
+
@model_class.validates(*fields, **options)
|
|
131
52
|
end
|
|
132
53
|
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
# Verifica che un campo sia in una relazione d'ordine rispetto ad un altro.
|
|
136
|
-
#
|
|
137
|
-
# @param first_field [Symbol] Primo campo
|
|
138
|
-
# @param comparator [Symbol] Comparatore (:before, :after, :lteq, :gteq)
|
|
139
|
-
# @param second_field [Symbol] Secondo campo
|
|
140
|
-
# @param options [Hash] Opzioni aggiuntive (on, if, unless, message)
|
|
141
|
-
#
|
|
142
|
-
# @example Date validation
|
|
143
|
-
# validate_order :starts_at, :before, :ends_at
|
|
144
|
-
# validate_order :starts_at, :before, :ends_at, message: "must be before end date"
|
|
145
|
-
#
|
|
146
|
-
# @example Numeric validation
|
|
147
|
-
# validate_order :min_price, :lteq, :max_price
|
|
148
|
-
# validate_order :discount, :lteq, :price, on: :create
|
|
149
|
-
#
|
|
150
|
-
# Comparatori supportati:
|
|
151
|
-
# - :before - first < second (date/time)
|
|
152
|
-
# - :after - first > second (date/time)
|
|
153
|
-
# - :lteq - first <= second (numeric)
|
|
154
|
-
# - :gteq - first >= second (numeric)
|
|
155
|
-
# - :lt - first < second (numeric)
|
|
156
|
-
# - :gt - first > second (numeric)
|
|
157
|
-
#
|
|
158
|
-
def validate_order(first_field, comparator, second_field, **options)
|
|
159
|
-
valid_comparators = %i[before after lteq gteq lt gt]
|
|
160
|
-
unless valid_comparators.include?(comparator)
|
|
161
|
-
raise ArgumentError, "Invalid comparator: #{comparator}. Valid: #{valid_comparators.join(', ')}"
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
@order_validations << {
|
|
165
|
-
first_field: first_field,
|
|
166
|
-
comparator: comparator,
|
|
167
|
-
second_field: second_field,
|
|
168
|
-
options: options
|
|
169
|
-
}
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
# Definisce una business rule custom
|
|
54
|
+
# Use a registered complex validation.
|
|
173
55
|
#
|
|
174
|
-
#
|
|
175
|
-
#
|
|
56
|
+
# Complex validations must be registered first using
|
|
57
|
+
# register_complex_validation in the model.
|
|
176
58
|
#
|
|
177
|
-
# @param
|
|
178
|
-
# @
|
|
59
|
+
# @param name [Symbol] Name of registered complex validation
|
|
60
|
+
# @raise [ArgumentError] If validation is not registered
|
|
179
61
|
#
|
|
180
62
|
# @example
|
|
181
|
-
# #
|
|
182
|
-
#
|
|
183
|
-
#
|
|
184
|
-
#
|
|
185
|
-
# # Nel modello (implementazione):
|
|
186
|
-
# def valid_category
|
|
187
|
-
# unless Category.exists?(id: category_id)
|
|
188
|
-
# errors.add(:category_id, "must be a valid category")
|
|
63
|
+
# # In model (registration):
|
|
64
|
+
# register_complex_validation :valid_pricing do
|
|
65
|
+
# if sale_price.present? && sale_price >= price
|
|
66
|
+
# errors.add(:sale_price, "must be less than regular price")
|
|
189
67
|
# end
|
|
190
68
|
# end
|
|
191
69
|
#
|
|
192
|
-
#
|
|
193
|
-
#
|
|
194
|
-
#
|
|
195
|
-
# end
|
|
70
|
+
# # In configurator (usage):
|
|
71
|
+
# validatable do
|
|
72
|
+
# check_complex :valid_pricing
|
|
196
73
|
# end
|
|
197
74
|
#
|
|
198
|
-
def
|
|
199
|
-
@
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
75
|
+
def check_complex(name)
|
|
76
|
+
unless @model_class.complex_validation?(name)
|
|
77
|
+
raise ArgumentError, "Unknown complex validation: #{name}. Use register_complex_validation to define it first."
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
@complex_validations << name.to_sym
|
|
203
81
|
end
|
|
204
82
|
|
|
205
|
-
#
|
|
83
|
+
# Define a validation group.
|
|
206
84
|
#
|
|
207
|
-
#
|
|
208
|
-
#
|
|
85
|
+
# Groups allow validating only a subset of fields,
|
|
86
|
+
# useful for multi-step forms or partial validations.
|
|
209
87
|
#
|
|
210
|
-
# @param group_name [Symbol]
|
|
211
|
-
# @param fields [Array<Symbol>]
|
|
88
|
+
# @param group_name [Symbol] Group name
|
|
89
|
+
# @param fields [Array<Symbol>] Fields included in group
|
|
90
|
+
# @raise [ArgumentError] If group name is invalid, fields not array, or group already defined
|
|
212
91
|
#
|
|
213
92
|
# @example
|
|
214
93
|
# validation_group :step1, [:email, :password]
|
|
215
94
|
# validation_group :step2, [:first_name, :last_name]
|
|
216
95
|
# validation_group :step3, [:address, :city, :zip_code]
|
|
217
96
|
#
|
|
218
|
-
#
|
|
219
|
-
# user.valid?(:step1) #
|
|
97
|
+
# @example Usage
|
|
98
|
+
# user.valid?(:step1) # Validate only email and password
|
|
220
99
|
# user.errors_for_group(:step1)
|
|
221
100
|
#
|
|
222
101
|
def validation_group(group_name, fields)
|
|
@@ -230,14 +109,12 @@ module BetterModel
|
|
|
230
109
|
}
|
|
231
110
|
end
|
|
232
111
|
|
|
233
|
-
#
|
|
112
|
+
# Return complete configuration.
|
|
234
113
|
#
|
|
235
|
-
# @return [Hash]
|
|
114
|
+
# @return [Hash] Configuration with all validations
|
|
236
115
|
def to_h
|
|
237
116
|
{
|
|
238
|
-
|
|
239
|
-
order_validations: @order_validations,
|
|
240
|
-
business_rules: @business_rules
|
|
117
|
+
complex_validations: @complex_validations
|
|
241
118
|
}
|
|
242
119
|
end
|
|
243
120
|
end
|
|
@@ -1,56 +1,62 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative "errors/validatable/validatable_error"
|
|
4
|
+
require_relative "errors/validatable/not_enabled_error"
|
|
5
|
+
require_relative "errors/validatable/configuration_error"
|
|
6
|
+
|
|
7
|
+
# Validatable - Declarative validation system for Rails models.
|
|
4
8
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
9
|
+
# This concern enables defining validations declaratively and readably,
|
|
10
|
+
# with support for conditional validations, groups, cross-field, and business rules.
|
|
7
11
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
12
|
+
# @note OPT-IN APPROACH
|
|
13
|
+
# Declarative validations are not enabled automatically.
|
|
14
|
+
# You must explicitly call `validatable do...end` in your model.
|
|
10
15
|
#
|
|
11
|
-
#
|
|
16
|
+
# @example Basic Usage
|
|
12
17
|
# class Article < ApplicationRecord
|
|
13
18
|
# include BetterModel
|
|
14
19
|
#
|
|
15
|
-
# # Status (
|
|
20
|
+
# # Status (from Statusable)
|
|
16
21
|
# is :draft, -> { status == "draft" }
|
|
17
22
|
# is :published, -> { status == "published" }
|
|
18
23
|
#
|
|
19
|
-
# #
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
24
|
+
# # Register complex validations
|
|
25
|
+
# register_complex_validation :valid_date_range do
|
|
26
|
+
# return if starts_at.blank? || ends_at.blank?
|
|
27
|
+
# errors.add(:starts_at, "must be before end date") if starts_at >= ends_at
|
|
28
|
+
# end
|
|
23
29
|
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# end
|
|
30
|
+
# # Enable validatable (opt-in)
|
|
31
|
+
# validatable do
|
|
32
|
+
# # Basic validations
|
|
33
|
+
# check :title, :content, presence: true
|
|
29
34
|
#
|
|
30
|
-
# #
|
|
31
|
-
#
|
|
35
|
+
# # Conditional validations (using Rails options)
|
|
36
|
+
# check :published_at, presence: true, if: -> { status == "published" }
|
|
37
|
+
# check :author_id, presence: true, if: :is_published?
|
|
32
38
|
#
|
|
33
|
-
# #
|
|
34
|
-
#
|
|
39
|
+
# # Complex validations for cross-field and business logic
|
|
40
|
+
# check_complex :valid_date_range
|
|
35
41
|
#
|
|
36
|
-
# #
|
|
42
|
+
# # Validation groups
|
|
37
43
|
# validation_group :step1, [:email, :password]
|
|
38
44
|
# validation_group :step2, [:first_name, :last_name]
|
|
39
45
|
# end
|
|
40
46
|
# end
|
|
41
47
|
#
|
|
42
|
-
#
|
|
43
|
-
# article.valid? #
|
|
44
|
-
# article.valid?(:step1) #
|
|
48
|
+
# @example Validation Usage
|
|
49
|
+
# article.valid? # All validations
|
|
50
|
+
# article.valid?(:step1) # Only step1 group
|
|
45
51
|
#
|
|
46
52
|
module BetterModel
|
|
47
53
|
module Validatable
|
|
48
54
|
extend ActiveSupport::Concern
|
|
49
55
|
|
|
50
56
|
included do
|
|
51
|
-
#
|
|
57
|
+
# Validate ActiveRecord inheritance
|
|
52
58
|
unless ancestors.include?(ActiveRecord::Base)
|
|
53
|
-
raise
|
|
59
|
+
raise BetterModel::Errors::Validatable::ConfigurationError, "Invalid configuration"
|
|
54
60
|
end
|
|
55
61
|
|
|
56
62
|
# Configurazione validatable (opt-in)
|
|
@@ -58,6 +64,8 @@ module BetterModel
|
|
|
58
64
|
class_attribute :validatable_config, default: {}.freeze
|
|
59
65
|
class_attribute :validatable_groups, default: {}.freeze
|
|
60
66
|
class_attribute :_validatable_setup_done, default: false
|
|
67
|
+
# Registry dei complex validations custom
|
|
68
|
+
class_attribute :complex_validations_registry, default: {}.freeze
|
|
61
69
|
end
|
|
62
70
|
|
|
63
71
|
class_methods do
|
|
@@ -65,14 +73,12 @@ module BetterModel
|
|
|
65
73
|
#
|
|
66
74
|
# @example Attivazione base
|
|
67
75
|
# validatable do
|
|
68
|
-
#
|
|
76
|
+
# check :title, presence: true
|
|
69
77
|
# end
|
|
70
78
|
#
|
|
71
79
|
# @example Con validazioni condizionali
|
|
72
80
|
# validatable do
|
|
73
|
-
#
|
|
74
|
-
# validate :published_at, presence: true
|
|
75
|
-
# end
|
|
81
|
+
# check :published_at, presence: true, if: :is_published?
|
|
76
82
|
# end
|
|
77
83
|
#
|
|
78
84
|
def validatable(&block)
|
|
@@ -99,98 +105,71 @@ module BetterModel
|
|
|
99
105
|
# Verifica se validatable è attivo
|
|
100
106
|
#
|
|
101
107
|
# @return [Boolean]
|
|
102
|
-
def validatable_enabled?
|
|
103
|
-
|
|
108
|
+
def validatable_enabled? = validatable_enabled == true
|
|
109
|
+
|
|
110
|
+
# Registra una validazione complessa custom
|
|
111
|
+
#
|
|
112
|
+
# Permette di definire validazioni complesse riutilizzabili che possono combinare
|
|
113
|
+
# più campi o utilizzare logica custom non coperta dalle validazioni standard.
|
|
114
|
+
#
|
|
115
|
+
# @param name [Symbol] il nome della validazione
|
|
116
|
+
# @param block [Proc] il blocco di validazione che verrà eseguito nel contesto dell'istanza
|
|
117
|
+
#
|
|
118
|
+
# @example Validazione complessa base
|
|
119
|
+
# register_complex_validation :valid_pricing do
|
|
120
|
+
# if sale_price.present? && sale_price >= price
|
|
121
|
+
# errors.add(:sale_price, "must be less than regular price")
|
|
122
|
+
# end
|
|
123
|
+
# end
|
|
124
|
+
#
|
|
125
|
+
# @example Con logica multi-campo
|
|
126
|
+
# register_complex_validation :valid_dates do
|
|
127
|
+
# if starts_at.present? && ends_at.present? && starts_at >= ends_at
|
|
128
|
+
# errors.add(:ends_at, "must be after start date")
|
|
129
|
+
# end
|
|
130
|
+
# end
|
|
131
|
+
#
|
|
132
|
+
def register_complex_validation(name, &block)
|
|
133
|
+
unless block_given?
|
|
134
|
+
raise BetterModel::Errors::Validatable::ConfigurationError, "Invalid configuration"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Registra nel registry
|
|
138
|
+
self.complex_validations_registry = complex_validations_registry.merge(name.to_sym => block).freeze
|
|
104
139
|
end
|
|
105
140
|
|
|
141
|
+
# Verifica se una validazione complessa è stata registrata
|
|
142
|
+
#
|
|
143
|
+
# @param name [Symbol] il nome della validazione
|
|
144
|
+
# @return [Boolean]
|
|
145
|
+
def complex_validation?(name) = complex_validations_registry.key?(name.to_sym)
|
|
146
|
+
|
|
106
147
|
private
|
|
107
148
|
|
|
108
149
|
# Applica le configurazioni di validazione al modello
|
|
109
150
|
def apply_validatable_config
|
|
110
151
|
return unless validatable_config.present?
|
|
111
152
|
|
|
112
|
-
# Apply
|
|
113
|
-
validatable_config[:
|
|
114
|
-
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# Apply order validations
|
|
118
|
-
validatable_config[:order_validations]&.each do |order_val|
|
|
119
|
-
apply_order_validation(order_val)
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
# Apply business rules
|
|
123
|
-
validatable_config[:business_rules]&.each do |rule|
|
|
124
|
-
apply_business_rule(rule)
|
|
153
|
+
# Apply complex validations
|
|
154
|
+
validatable_config[:complex_validations]&.each do |name|
|
|
155
|
+
apply_complex_validation(name)
|
|
125
156
|
end
|
|
126
157
|
end
|
|
127
158
|
|
|
128
|
-
# Applica una validazione
|
|
129
|
-
def
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
validations = conditional[:validations]
|
|
159
|
+
# Applica una validazione complessa
|
|
160
|
+
def apply_complex_validation(name)
|
|
161
|
+
block = complex_validations_registry[name]
|
|
162
|
+
return unless block
|
|
133
163
|
|
|
134
|
-
#
|
|
164
|
+
# Crea un validator custom per questa validazione complessa
|
|
135
165
|
validate do
|
|
136
|
-
|
|
137
|
-
send(condition)
|
|
138
|
-
elsif condition.is_a?(Proc)
|
|
139
|
-
instance_exec(&condition)
|
|
140
|
-
else
|
|
141
|
-
raise ArgumentError, "Condition must be a Symbol or Proc"
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
condition_met = !condition_met if negate
|
|
145
|
-
|
|
146
|
-
if condition_met
|
|
147
|
-
validations.each do |validation|
|
|
148
|
-
apply_validation_in_context(validation)
|
|
149
|
-
end
|
|
150
|
-
end
|
|
166
|
+
instance_eval(&block)
|
|
151
167
|
end
|
|
152
168
|
end
|
|
153
|
-
|
|
154
|
-
# Applica una validazione order (cross-field)
|
|
155
|
-
def apply_order_validation(order_val)
|
|
156
|
-
validates_with BetterModel::Validatable::OrderValidator,
|
|
157
|
-
attributes: [ order_val[:first_field] ],
|
|
158
|
-
second_field: order_val[:second_field],
|
|
159
|
-
comparator: order_val[:comparator],
|
|
160
|
-
**order_val[:options]
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
# Applica una business rule
|
|
164
|
-
def apply_business_rule(rule)
|
|
165
|
-
validates_with BetterModel::Validatable::BusinessRuleValidator,
|
|
166
|
-
rule_name: rule[:name],
|
|
167
|
-
**rule[:options]
|
|
168
|
-
end
|
|
169
169
|
end
|
|
170
170
|
|
|
171
171
|
# Metodi di istanza
|
|
172
172
|
|
|
173
|
-
# Apply a validation in the context of the current instance
|
|
174
|
-
def apply_validation_in_context(validation)
|
|
175
|
-
fields = validation[:fields]
|
|
176
|
-
options = validation[:options]
|
|
177
|
-
|
|
178
|
-
fields.each do |field|
|
|
179
|
-
options.each do |validator_type, validator_options|
|
|
180
|
-
# Prepare validator options
|
|
181
|
-
# If validator_options is true, convert to empty hash
|
|
182
|
-
# If it's a hash, use as-is
|
|
183
|
-
opts = validator_options.is_a?(Hash) ? validator_options : {}
|
|
184
|
-
|
|
185
|
-
validator = ActiveModel::Validations.const_get("#{validator_type.to_s.camelize}Validator").new(
|
|
186
|
-
attributes: [ field ],
|
|
187
|
-
**opts
|
|
188
|
-
)
|
|
189
|
-
validator.validate(self)
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
|
|
194
173
|
# Override valid? per supportare validation groups
|
|
195
174
|
#
|
|
196
175
|
# @param context [Symbol, nil] Context o gruppo di validazione
|
|
@@ -210,7 +189,9 @@ module BetterModel
|
|
|
210
189
|
# @param group_name [Symbol] Nome del gruppo
|
|
211
190
|
# @return [Boolean]
|
|
212
191
|
def validate_group(group_name)
|
|
213
|
-
|
|
192
|
+
unless self.class.validatable_enabled?
|
|
193
|
+
raise BetterModel::Errors::Validatable::NotEnabledError, "Module is not enabled"
|
|
194
|
+
end
|
|
214
195
|
|
|
215
196
|
group = self.class.validatable_groups[group_name]
|
|
216
197
|
return false unless group
|
|
@@ -231,7 +212,9 @@ module BetterModel
|
|
|
231
212
|
# @param group_name [Symbol] Nome del gruppo
|
|
232
213
|
# @return [ActiveModel::Errors]
|
|
233
214
|
def errors_for_group(group_name)
|
|
234
|
-
|
|
215
|
+
unless self.class.validatable_enabled?
|
|
216
|
+
raise BetterModel::Errors::Validatable::NotEnabledError, "Module is not enabled"
|
|
217
|
+
end
|
|
235
218
|
|
|
236
219
|
group = self.class.validatable_groups[group_name]
|
|
237
220
|
return errors unless group
|
|
@@ -259,12 +242,4 @@ module BetterModel
|
|
|
259
242
|
end
|
|
260
243
|
end
|
|
261
244
|
|
|
262
|
-
# Errori custom
|
|
263
|
-
class ValidatableError < StandardError; end
|
|
264
|
-
|
|
265
|
-
class ValidatableNotEnabledError < ValidatableError
|
|
266
|
-
def initialize(msg = nil)
|
|
267
|
-
super(msg || "Validatable is not enabled. Add 'validatable do...end' to your model.")
|
|
268
|
-
end
|
|
269
|
-
end
|
|
270
245
|
end
|
data/lib/better_model/version.rb
CHANGED
data/lib/better_model.rb
CHANGED
|
@@ -1,24 +1,61 @@
|
|
|
1
1
|
require "better_model/version"
|
|
2
2
|
require "better_model/railtie"
|
|
3
|
+
|
|
4
|
+
# Load all error classes first
|
|
5
|
+
require "better_model/errors/better_model_error"
|
|
6
|
+
require "better_model/errors/archivable/archivable_error"
|
|
7
|
+
require "better_model/errors/archivable/already_archived_error"
|
|
8
|
+
require "better_model/errors/archivable/not_archived_error"
|
|
9
|
+
require "better_model/errors/archivable/not_enabled_error"
|
|
10
|
+
require "better_model/errors/validatable/validatable_error"
|
|
11
|
+
require "better_model/errors/validatable/not_enabled_error"
|
|
12
|
+
require "better_model/errors/traceable/traceable_error"
|
|
13
|
+
require "better_model/errors/traceable/not_enabled_error"
|
|
14
|
+
require "better_model/errors/searchable/searchable_error"
|
|
15
|
+
require "better_model/errors/searchable/invalid_predicate_error"
|
|
16
|
+
require "better_model/errors/searchable/invalid_order_error"
|
|
17
|
+
require "better_model/errors/searchable/invalid_pagination_error"
|
|
18
|
+
require "better_model/errors/searchable/invalid_security_error"
|
|
19
|
+
require "better_model/errors/stateable/stateable_error"
|
|
20
|
+
require "better_model/errors/stateable/not_enabled_error"
|
|
21
|
+
require "better_model/errors/stateable/invalid_state_error"
|
|
22
|
+
require "better_model/errors/stateable/invalid_transition_error"
|
|
23
|
+
require "better_model/errors/stateable/check_failed_error"
|
|
24
|
+
require "better_model/errors/stateable/validation_failed_error"
|
|
25
|
+
require "better_model/errors/stateable/configuration_error"
|
|
26
|
+
require "better_model/errors/archivable/configuration_error"
|
|
27
|
+
require "better_model/errors/validatable/configuration_error"
|
|
28
|
+
require "better_model/errors/traceable/configuration_error"
|
|
29
|
+
require "better_model/errors/searchable/configuration_error"
|
|
30
|
+
require "better_model/errors/predicable/predicable_error"
|
|
31
|
+
require "better_model/errors/predicable/configuration_error"
|
|
32
|
+
require "better_model/errors/sortable/sortable_error"
|
|
33
|
+
require "better_model/errors/sortable/configuration_error"
|
|
34
|
+
require "better_model/errors/statusable/statusable_error"
|
|
35
|
+
require "better_model/errors/statusable/configuration_error"
|
|
36
|
+
require "better_model/errors/permissible/permissible_error"
|
|
37
|
+
require "better_model/errors/permissible/configuration_error"
|
|
38
|
+
require "better_model/errors/taggable/taggable_error"
|
|
39
|
+
require "better_model/errors/taggable/configuration_error"
|
|
40
|
+
|
|
41
|
+
# Load modules
|
|
3
42
|
require "better_model/statusable"
|
|
4
43
|
require "better_model/permissible"
|
|
5
44
|
require "better_model/sortable"
|
|
6
45
|
require "better_model/predicable"
|
|
7
46
|
require "better_model/searchable"
|
|
8
47
|
require "better_model/archivable"
|
|
9
|
-
require "better_model/
|
|
48
|
+
require "better_model/models/version"
|
|
10
49
|
require "better_model/traceable"
|
|
11
50
|
require "better_model/validatable"
|
|
12
51
|
require "better_model/validatable/configurator"
|
|
13
|
-
require "better_model/
|
|
14
|
-
require "better_model/validatable/business_rule_validator"
|
|
15
|
-
require "better_model/state_transition"
|
|
52
|
+
require "better_model/models/state_transition"
|
|
16
53
|
require "better_model/stateable"
|
|
17
54
|
require "better_model/stateable/configurator"
|
|
18
|
-
require "better_model/stateable/errors"
|
|
19
55
|
require "better_model/stateable/guard"
|
|
20
56
|
require "better_model/stateable/transition"
|
|
21
57
|
require "better_model/taggable"
|
|
58
|
+
require "better_model/repositable"
|
|
22
59
|
|
|
23
60
|
module BetterModel
|
|
24
61
|
extend ActiveSupport::Concern
|