better_model 2.0.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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +274 -208
  3. data/lib/better_model/archivable.rb +203 -92
  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 +142 -131
  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 +123 -96
  46. data/lib/better_model/sortable.rb +137 -41
  47. data/lib/better_model/stateable/configurator.rb +103 -85
  48. data/lib/better_model/stateable/guard.rb +41 -21
  49. data/lib/better_model/stateable/transition.rb +64 -35
  50. data/lib/better_model/stateable.rb +43 -25
  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 +54 -177
  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 -9
  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 +45 -14
  63. data/lib/better_model/schedulable/occurrence_calculator.rb +0 -1034
  64. data/lib/better_model/schedulable/schedule_builder.rb +0 -269
  65. data/lib/better_model/schedulable.rb +0 -356
  66. data/lib/better_model/state_transition.rb +0 -106
  67. data/lib/better_model/stateable/errors.rb +0 -45
  68. data/lib/better_model/validatable/business_rule_validator.rb +0 -47
  69. data/lib/better_model/validatable/order_validator.rb +0 -77
  70. data/lib/better_model/version_record.rb +0 -66
  71. data/lib/generators/better_model/taggable/taggable_generator.rb +0 -129
  72. data/lib/generators/better_model/taggable/templates/README.tt +0 -62
  73. data/lib/generators/better_model/taggable/templates/migration.rb.tt +0 -21
@@ -1,56 +1,62 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Validatable - Sistema di validazioni dichiarativo per modelli Rails
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
- # Questo concern permette di definire validazioni in modo dichiarativo e leggibile,
6
- # con supporto per validazioni condizionali, gruppi, cross-field e business rules.
9
+ # This concern enables defining validations declaratively and readably,
10
+ # with support for conditional validations, groups, cross-field, and business rules.
7
11
  #
8
- # APPROCCIO OPT-IN: Le validazioni dichiarative non sono attive automaticamente.
9
- # Devi chiamare esplicitamente `validatable do...end` nel tuo modello.
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
- # Esempio di utilizzo:
16
+ # @example Basic Usage
12
17
  # class Article < ApplicationRecord
13
18
  # include BetterModel
14
19
  #
15
- # # Status (già esistente in Statusable)
20
+ # # Status (from Statusable)
16
21
  # is :draft, -> { status == "draft" }
17
22
  # is :published, -> { status == "published" }
18
23
  #
19
- # # Attiva validatable (opt-in)
20
- # validatable do
21
- # # Validazioni base
22
- # validate :title, :content, presence: true
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
- # # Validazioni condizionali
25
- # validate_if :is_published? do
26
- # validate :published_at, presence: true
27
- # validate :author_id, presence: true
28
- # end
30
+ # # Enable validatable (opt-in)
31
+ # validatable do
32
+ # # Basic validations
33
+ # check :title, :content, presence: true
29
34
  #
30
- # # Cross-field validations
31
- # validate_order :starts_at, :before, :ends_at
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
- # # Business rules
34
- # validate_business_rule :valid_category
39
+ # # Complex validations for cross-field and business logic
40
+ # check_complex :valid_date_range
35
41
  #
36
- # # Gruppi di validazioni
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
- # Utilizzo:
43
- # article.valid? # Tutte le validazioni
44
- # article.valid?(:step1) # Solo gruppo 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
- # Validazione ActiveRecord
57
+ # Validate ActiveRecord inheritance
52
58
  unless ancestors.include?(ActiveRecord::Base)
53
- raise ArgumentError, "BetterModel::Validatable can only be included in ActiveRecord models"
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
- # validate :title, presence: true
76
+ # check :title, presence: true
69
77
  # end
70
78
  #
71
79
  # @example Con validazioni condizionali
72
80
  # validatable do
73
- # validate_if :is_published? do
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
- validatable_enabled == true
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 conditional validations
113
- validatable_config[:conditional_validations]&.each do |conditional|
114
- apply_conditional_validation(conditional)
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 condizionale
129
- def apply_conditional_validation(conditional)
130
- condition = conditional[:condition]
131
- negate = conditional[:negate]
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
- # Create a custom validator for this conditional block
164
+ # Crea un validator custom per questa validazione complessa
135
165
  validate do
136
- condition_met = if condition.is_a?(Symbol)
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
- raise ValidatableNotEnabledError unless self.class.validatable_enabled?
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
- raise ValidatableNotEnabledError unless self.class.validatable_enabled?
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
@@ -1,3 +1,3 @@
1
1
  module BetterModel
2
- VERSION = "2.0.0"
2
+ VERSION = "3.0.0"
3
3
  end
data/lib/better_model.rb CHANGED
@@ -1,27 +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/version_record"
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/validatable/order_validator"
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"
22
- require "better_model/schedulable"
23
- require "better_model/schedulable/schedule_builder"
24
- require "better_model/schedulable/occurrence_calculator"
58
+ require "better_model/repositable"
25
59
 
26
60
  module BetterModel
27
61
  extend ActiveSupport::Concern
@@ -38,6 +72,5 @@ module BetterModel
38
72
  include BetterModel::Validatable
39
73
  include BetterModel::Stateable
40
74
  include BetterModel::Taggable
41
- include BetterModel::Schedulable
42
75
  end
43
76
  end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module BetterModel
6
+ module Generators
7
+ # Generator for creating repository classes that implement the Repository Pattern.
8
+ #
9
+ # This generator creates a repository class for a given model, integrating seamlessly
10
+ # with BetterModel's Searchable, Predicable, and Sortable concerns.
11
+ #
12
+ # @example Generate a repository for Article model
13
+ # rails generate better_model:repository Article
14
+ #
15
+ # @example Generate with custom path
16
+ # rails generate better_model:repository Article --path app/services/repositories
17
+ #
18
+ # @example Skip ApplicationRepository creation
19
+ # rails generate better_model:repository Article --skip-base
20
+ #
21
+ class RepositoryGenerator < Rails::Generators::NamedBase
22
+ source_root File.expand_path("templates", __dir__)
23
+
24
+ class_option :path, type: :string, default: "app/repositories",
25
+ desc: "Directory where the repository will be created"
26
+ class_option :skip_base, type: :boolean, default: false,
27
+ desc: "Skip creating ApplicationRepository if it doesn't exist"
28
+ class_option :namespace, type: :string, default: nil,
29
+ desc: "Namespace for the repository class"
30
+
31
+ # Create the ApplicationRepository base class if it doesn't exist
32
+ def create_application_repository
33
+ return if options[:skip_base]
34
+ return if File.exist?(File.join(destination_root, application_repository_path))
35
+
36
+ template "application_repository.rb.tt", application_repository_path
37
+ say "Created ApplicationRepository at #{application_repository_path}", :green
38
+ end
39
+
40
+ # Create the model-specific repository class
41
+ def create_repository_file
42
+ template "repository.rb.tt", repository_path
43
+ say "Created #{repository_class_name} at #{repository_path}", :green
44
+ end
45
+
46
+ # Display usage instructions
47
+ def show_instructions
48
+ say "\nRepository created successfully!", :green
49
+ say "\nUsage example:", :yellow
50
+ say " repo = #{repository_class_name}.new", :white
51
+ say " results = repo.search({ #{example_predicate} })", :white
52
+ say " record = repo.search({ id_eq: 1 }, limit: 1)", :white
53
+ say " all = repo.search({}, limit: nil)", :white
54
+ say "\nAdd custom methods to #{repository_path}", :yellow
55
+
56
+ if model_has_better_model_features?
57
+ say "\nYour model has BetterModel features enabled:", :green
58
+ display_available_features
59
+ else
60
+ say "\nTip: Include BetterModel in your #{class_name} model to unlock:", :yellow
61
+ say " - Predicable: Auto-generated filter scopes", :white
62
+ say " - Sortable: Auto-generated sort scopes", :white
63
+ say " - Searchable: Unified search interface", :white
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def repository_path
70
+ File.join(options[:path], "#{file_name}_repository.rb")
71
+ end
72
+
73
+ def application_repository_path
74
+ File.join(options[:path], "application_repository.rb")
75
+ end
76
+
77
+ def repository_class_name
78
+ if options[:namespace]
79
+ "#{options[:namespace]}::#{class_name}Repository"
80
+ else
81
+ "#{class_name}Repository"
82
+ end
83
+ end
84
+
85
+ def base_repository_class
86
+ if options[:skip_base]
87
+ "BetterModel::Repositable::BaseRepository"
88
+ else
89
+ "ApplicationRepository"
90
+ end
91
+ end
92
+
93
+ def example_predicate
94
+ if model_class_exists? && model_class.column_names.include?("name")
95
+ "name_cont: 'search'"
96
+ elsif model_class_exists? && model_class.column_names.include?("title")
97
+ "title_cont: 'search'"
98
+ elsif model_class_exists? && model_class.column_names.include?("status")
99
+ "status_eq: 'active'"
100
+ else
101
+ "id_eq: 1"
102
+ end
103
+ end
104
+
105
+ def model_class_exists?
106
+ return false unless Object.const_defined?(class_name)
107
+ klass = class_name.constantize
108
+ klass < ActiveRecord::Base && klass.table_exists?
109
+ rescue NameError, ActiveRecord::StatementInvalid, ActiveRecord::NoDatabaseError
110
+ false
111
+ end
112
+
113
+ def model_class
114
+ class_name.constantize if model_class_exists?
115
+ end
116
+
117
+ def model_has_better_model_features?
118
+ return false unless model_class_exists?
119
+ model_class.respond_to?(:predicable_fields) ||
120
+ model_class.respond_to?(:sortable_fields) ||
121
+ model_class.respond_to?(:searchable_fields)
122
+ end
123
+
124
+ def display_available_features
125
+ return unless model_class_exists?
126
+
127
+ if model_class.respond_to?(:predicable_fields) && model_class.predicable_fields.any?
128
+ say " • Predicable fields: #{model_class.predicable_fields.to_a.join(', ')}", :white
129
+ end
130
+
131
+ if model_class.respond_to?(:sortable_fields) && model_class.sortable_fields.any?
132
+ say " • Sortable fields: #{model_class.sortable_fields.to_a.join(', ')}", :white
133
+ end
134
+
135
+ if model_class.respond_to?(:searchable_fields) && model_class.searchable_fields.any?
136
+ say " • Searchable fields: #{model_class.searchable_fields.to_a.join(', ')}", :white
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Base repository class for the application.
4
+ #
5
+ # Inherits from BetterModel::Repositable::BaseRepository and can be customized with
6
+ # application-wide repository behaviors.
7
+ #
8
+ # @example Add custom methods available to all repositories
9
+ # class ApplicationRepository < BetterModel::Repositable::BaseRepository
10
+ # def find_active(id)
11
+ # search({ id_eq: id, status_eq: "active" }, limit: 1)
12
+ # end
13
+ #
14
+ # def paginated_search(filters, page: 1)
15
+ # search(filters, page: page, per_page: 25)
16
+ # end
17
+ # end
18
+ #
19
+ class ApplicationRepository < BetterModel::Repositable::BaseRepository
20
+ # Add application-wide repository methods here
21
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ <% if options[:namespace] -%>
4
+ module <%= options[:namespace] %>
5
+ class <%= class_name %>Repository < <%= base_repository_class %>
6
+ def model_class = <%= class_name %>
7
+
8
+ # Add your custom query methods here
9
+ #
10
+ # Example methods:
11
+ # def active
12
+ # search({ status_eq: "active" })
13
+ # end
14
+ #
15
+ # def recent(days: 7)
16
+ # search({ created_at_gteq: days.days.ago }, order_scope: { field: :created_at, direction: :desc })
17
+ # end
18
+ #
19
+ # def find_with_details(id)
20
+ # search({ id_eq: id }, includes: [:associated_records], limit: 1)
21
+ # end
22
+ <% if model_class_exists? && model_class.respond_to?(:predicable_fields) && model_class.predicable_fields.any? -%>
23
+
24
+ # Available predicates for <%= class_name %>:
25
+ <% model_class.predicable_fields.each do |field| -%>
26
+ # <%= field %>: <%= model_class.searchable_predicates_for(field).map { |p| ":#{p}" }.join(", ") %>
27
+ <% end -%>
28
+ <% end -%>
29
+ <% if model_class_exists? && model_class.respond_to?(:sortable_fields) && model_class.sortable_fields.any? -%>
30
+
31
+ # Available sort scopes for <%= class_name %>:
32
+ <% model_class.sortable_fields.each do |field| -%>
33
+ # <%= field %>: <%= model_class.searchable_sorts_for(field).join(", ") %>
34
+ <% end -%>
35
+ <% end -%>
36
+ end
37
+ end
38
+ <% else -%>
39
+ class <%= class_name %>Repository < <%= base_repository_class %>
40
+ def model_class = <%= class_name %>
41
+
42
+ # Add your custom query methods here
43
+ #
44
+ # Example methods:
45
+ # def active
46
+ # search({ status_eq: "active" })
47
+ # end
48
+ #
49
+ # def recent(days: 7)
50
+ # search({ created_at_gteq: days.days.ago }, order_scope: { field: :created_at, direction: :desc })
51
+ # end
52
+ #
53
+ # def find_with_details(id)
54
+ # search({ id_eq: id }, includes: [:associated_records], limit: 1)
55
+ # end
56
+ <% if model_class_exists? && model_class.respond_to?(:predicable_fields) && model_class.predicable_fields.any? -%>
57
+
58
+ # Available predicates for <%= class_name %>:
59
+ <% model_class.predicable_fields.each do |field| -%>
60
+ # <%= field %>: <%= model_class.searchable_predicates_for(field).map { |p| ":#{p}" }.join(", ") %>
61
+ <% end -%>
62
+ <% end -%>
63
+ <% if model_class_exists? && model_class.respond_to?(:sortable_fields) && model_class.sortable_fields.any? -%>
64
+
65
+ # Available sort scopes for <%= class_name %>:
66
+ <% model_class.sortable_fields.each do |field| -%>
67
+ # <%= field %>: <%= model_class.searchable_sorts_for(field).join(", ") %>
68
+ <% end -%>
69
+ <% end -%>
70
+ end
71
+ <% end -%>
@@ -20,7 +20,7 @@ Next steps:
20
20
 
21
21
  # Define transitions
22
22
  transition :confirm, from: :<%= initial_state_value %>, to: :confirmed do
23
- guard { valid? }
23
+ check { valid? }
24
24
  before { prepare_confirmation }
25
25
  after { send_notification }
26
26
  end