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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../better_model_error"
4
+
5
+ module BetterModel
6
+ module Errors
7
+ module Sortable
8
+ # Base error class for all Sortable-related errors.
9
+ class SortableError < BetterModelError
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "stateable_error"
4
+
5
+ module BetterModel
6
+ module Errors
7
+ module Stateable
8
+ class CheckFailedError < StateableError; end
9
+
10
+ # Alias for backward compatibility
11
+ GuardFailedError = CheckFailedError
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ module BetterModel
5
+ module Errors
6
+ module Stateable
7
+ class ConfigurationError < ArgumentError; end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "stateable_error"
4
+
5
+ module BetterModel
6
+ module Errors
7
+ module Stateable
8
+ class InvalidStateError < StateableError; end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "stateable_error"
4
+
5
+ module BetterModel
6
+ module Errors
7
+ module Stateable
8
+ class InvalidTransitionError < StateableError; end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "stateable_error"
4
+
5
+ module BetterModel
6
+ module Errors
7
+ module Stateable
8
+ class NotEnabledError < StateableError; end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../better_model_error"
4
+
5
+ module BetterModel
6
+ module Errors
7
+ module Stateable
8
+ # Base error class for all Stateable-related errors.
9
+ class StateableError < BetterModelError
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "stateable_error"
4
+
5
+ module BetterModel
6
+ module Errors
7
+ module Stateable
8
+ class ValidationFailedError < StateableError; end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterModel
4
+ module Errors
5
+ module Statusable
6
+ class ConfigurationError < ArgumentError; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../better_model_error"
4
+
5
+ module BetterModel
6
+ module Errors
7
+ module Statusable
8
+ # Base error class for all Statusable-related errors.
9
+ class StatusableError < BetterModelError
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ module BetterModel
5
+ module Errors
6
+ module Taggable
7
+ class ConfigurationError < ArgumentError; end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../better_model_error"
4
+
5
+ module BetterModel
6
+ module Errors
7
+ module Taggable
8
+ # Base error class for all Taggable-related errors.
9
+ class TaggableError < BetterModelError
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ module BetterModel
5
+ module Errors
6
+ module Traceable
7
+ class ConfigurationError < ArgumentError; end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "traceable_error"
4
+
5
+ module BetterModel
6
+ module Errors
7
+ module Traceable
8
+ class NotEnabledError < TraceableError; end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../better_model_error"
4
+
5
+ module BetterModel
6
+ module Errors
7
+ module Traceable
8
+ # Base error class for all Traceable-related errors.
9
+ class TraceableError < BetterModelError
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ module BetterModel
5
+ module Errors
6
+ module Validatable
7
+ class ConfigurationError < ArgumentError; end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "validatable_error"
4
+
5
+ module BetterModel
6
+ module Errors
7
+ module Validatable
8
+ class NotEnabledError < ValidatableError; end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../better_model_error"
4
+
5
+ module BetterModel
6
+ module Errors
7
+ module Validatable
8
+ # Base error class for all Validatable-related errors.
9
+ class ValidatableError < BetterModelError
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterModel
4
+ module Models
5
+ # StateTransition - Base ActiveRecord model for state transition history.
6
+ #
7
+ # This is an abstract model. Concrete classes are generated dynamically
8
+ # for each table (state_transitions, order_transitions, etc.).
9
+ #
10
+ # @note Table Schema
11
+ # t.string :transitionable_type, null: false
12
+ # t.integer :transitionable_id, null: false
13
+ # t.string :event, null: false
14
+ # t.string :from_state, null: false
15
+ # t.string :to_state, null: false
16
+ # t.json :metadata
17
+ # t.datetime :created_at, null: false
18
+ #
19
+ # @example Usage
20
+ # # All transitions for a model
21
+ # order.state_transitions
22
+ #
23
+ # @example Global queries (via dynamic classes)
24
+ # BetterModel::Models::StateTransitions.for_model(Order)
25
+ # BetterModel::Models::OrderTransitions.by_event(:confirm)
26
+ #
27
+ class StateTransition < ActiveRecord::Base
28
+ # Default table name (can be overridden by dynamic subclasses)
29
+ self.table_name = "state_transitions"
30
+
31
+ # Polymorphic association
32
+ belongs_to :transitionable, polymorphic: true
33
+
34
+ # Validations
35
+ validates :event, :from_state, :to_state, presence: true
36
+
37
+ # Scopes
38
+
39
+ # Scope for specific model.
40
+ #
41
+ # @param model_class [Class] Model class
42
+ # @return [ActiveRecord::Relation]
43
+ #
44
+ # @example
45
+ # StateTransition.for_model(Order)
46
+ scope :for_model, ->(model_class) {
47
+ where(transitionable_type: model_class.name)
48
+ }
49
+
50
+ # Scope for specific event.
51
+ #
52
+ # @param event [Symbol, String] Event name
53
+ # @return [ActiveRecord::Relation]
54
+ #
55
+ # @example
56
+ # StateTransition.by_event(:confirm)
57
+ scope :by_event, ->(event) {
58
+ where(event: event.to_s)
59
+ }
60
+
61
+ # Scope for source state.
62
+ #
63
+ # @param state [Symbol, String] Source state
64
+ # @return [ActiveRecord::Relation]
65
+ #
66
+ # @example
67
+ # StateTransition.from_state(:pending)
68
+ scope :from_state, ->(state) {
69
+ where(from_state: state.to_s)
70
+ }
71
+
72
+ # Scope for destination state.
73
+ #
74
+ # @param state [Symbol, String] Destination state
75
+ # @return [ActiveRecord::Relation]
76
+ #
77
+ # @example
78
+ # StateTransition.to_state(:confirmed)
79
+ scope :to_state, ->(state) {
80
+ where(to_state: state.to_s)
81
+ }
82
+
83
+ # Scope for recent transitions.
84
+ #
85
+ # @param duration [ActiveSupport::Duration] Time duration (e.g., 7.days)
86
+ # @return [ActiveRecord::Relation]
87
+ #
88
+ # @example
89
+ # StateTransition.recent(7.days)
90
+ scope :recent, ->(duration = 7.days) {
91
+ where("created_at >= ?", duration.ago)
92
+ }
93
+
94
+ # Scope for transitions in a time period.
95
+ #
96
+ # @param start_time [Time, Date] Period start
97
+ # @param end_time [Time, Date] Period end
98
+ # @return [ActiveRecord::Relation]
99
+ #
100
+ # @example
101
+ # StateTransition.between(1.week.ago, Time.current)
102
+ scope :between, ->(start_time, end_time) {
103
+ where(created_at: start_time..end_time)
104
+ }
105
+
106
+ # Instance Methods
107
+
108
+ # Formatted description of the transition.
109
+ #
110
+ # @return [String] Human-readable transition description
111
+ #
112
+ # @example
113
+ # transition.description # => "Order#123: pending -> confirmed (confirm)"
114
+ def description
115
+ "#{transitionable_type}##{transitionable_id}: #{from_state} -> #{to_state} (#{event})"
116
+ end
117
+
118
+ # Alias for backward compatibility
119
+ alias_method :to_s, :description
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterModel
4
+ module Models
5
+ # Version model for tracking changes
6
+ # This is the base AR model for version history
7
+ # Actual table_name is set dynamically in subclasses
8
+ class Version < ActiveRecord::Base
9
+ self.abstract_class = true
10
+
11
+ # Polymorphic association to the tracked model
12
+ belongs_to :item, polymorphic: true, optional: true
13
+
14
+ # Optional: belongs_to user who made the change
15
+ # belongs_to :updated_by, class_name: "User", optional: true
16
+
17
+ # Serialize object_changes as JSON
18
+ # Rails handles this automatically for json/jsonb columns
19
+
20
+ # Validations
21
+ validates :item_type, :event, presence: true
22
+ validates :event, inclusion: { in: %w[created updated destroyed] }
23
+
24
+ # Scopes
25
+ scope :for_item, ->(item) { where(item_type: item.class.name, item_id: item.id) }
26
+ scope :created_events, -> { where(event: "created") }
27
+ scope :updated_events, -> { where(event: "updated") }
28
+ scope :destroyed_events, -> { where(event: "destroyed") }
29
+ scope :by_user, ->(user_id) { where(updated_by_id: user_id) }
30
+ scope :between, ->(start_time, end_time) { where(created_at: start_time..end_time) }
31
+ scope :recent, ->(limit = 10) { order(created_at: :desc).limit(limit) }
32
+
33
+ # Get the change for a specific field
34
+ #
35
+ # @param field_name [Symbol, String] Field name
36
+ # @return [Hash, nil] Hash with :before and :after keys
37
+ def change_for(field_name)
38
+ return nil unless object_changes
39
+
40
+ field = field_name.to_s
41
+ return nil unless object_changes.key?(field)
42
+
43
+ {
44
+ before: object_changes[field][0],
45
+ after: object_changes[field][1]
46
+ }
47
+ end
48
+
49
+ # Check if a specific field changed in this version
50
+ # This method overrides ActiveRecord's changed? to accept a field_name parameter
51
+ #
52
+ # @param field_name [Symbol, String, nil] Field name (if nil, calls ActiveRecord's changed?)
53
+ # @return [Boolean]
54
+ def changed?(field_name = nil)
55
+ return super() if field_name.nil?
56
+
57
+ object_changes&.key?(field_name.to_s) || false
58
+ end
59
+
60
+ # Get list of changed fields
61
+ #
62
+ # @return [Array<String>]
63
+ def changed_fields
64
+ object_changes&.keys || []
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Permissible - Sistema di permessi dichiarativi per modelli Rails
3
+ require_relative "errors/permissible/permissible_error"
4
+ require_relative "errors/permissible/configuration_error"
5
+
6
+ # Permissible - Declarative permissions system for Rails models.
4
7
  #
5
- # Questo concern permette di definire permessi/capacità sui modelli utilizzando un DSL
6
- # semplice e dichiarativo, simile al pattern Statusable ma per le operazioni.
8
+ # This concern enables defining permissions/capabilities on models using a simple,
9
+ # declarative DSL, similar to the Statusable pattern but for operations.
7
10
  #
8
- # Esempio di utilizzo:
11
+ # @example Basic Usage
9
12
  # class Article < ApplicationRecord
10
13
  # include BetterModel::Permissible
11
14
  #
@@ -15,7 +18,7 @@
15
18
  # permit :archive, -> { is?(:published) && created_at < 1.year.ago }
16
19
  # end
17
20
  #
18
- # Utilizzo:
21
+ # @example Checking Permissions
19
22
  # article.permit?(:delete) # => true/false
20
23
  # article.permit_delete? # => true/false
21
24
  # article.permit_edit? # => true/false
@@ -26,58 +29,85 @@ module BetterModel
26
29
  extend ActiveSupport::Concern
27
30
 
28
31
  included do
29
- # Registry dei permessi definiti per questa classe
32
+ # Registry of permissions defined for this class
30
33
  class_attribute :permit_definitions
31
34
  self.permit_definitions = {}
32
35
  end
33
36
 
34
37
  class_methods do
35
- # DSL per definire permessi
38
+ # DSL to define permissions.
39
+ #
40
+ # Defines a permission check that can be evaluated against model instances.
41
+ # Automatically creates a convenience method permit_<permission_name>? for each permission.
36
42
  #
37
- # Parametri:
38
- # - permission_name: simbolo che rappresenta il permesso (es. :delete, :edit)
39
- # - condition_proc: lambda o proc che definisce la condizione
40
- # - block: blocco alternativo alla condition_proc
43
+ # @param permission_name [Symbol, String] Permission identifier (e.g., :delete, :edit)
44
+ # @param condition_proc [Proc, nil] Lambda or proc that defines the condition
45
+ # @yield Alternative to condition_proc parameter
46
+ # @raise [BetterModel::Errors::Permissible::ConfigurationError] If parameters are invalid
41
47
  #
42
- # Esempi:
48
+ # @example With lambda parameter
43
49
  # permit :delete, -> { status != "published" }
44
- # permit :edit, -> { is?(:draft) }
50
+ #
51
+ # @example With block
52
+ # permit :edit do
53
+ # is?(:draft)
54
+ # end
55
+ #
56
+ # @example Complex condition
45
57
  # permit :publish do
46
58
  # is?(:draft) && valid?(:publication)
47
59
  # end
48
60
  def permit(permission_name, condition_proc = nil, &block)
49
- # Valida i parametri prima di convertire
50
- raise ArgumentError, "Permission name cannot be blank" if permission_name.blank?
61
+ # Validate parameters before converting
62
+ if permission_name.blank?
63
+ raise BetterModel::Errors::Permissible::ConfigurationError, "Permission name cannot be blank"
64
+ end
51
65
 
52
66
  permission_name = permission_name.to_sym
53
67
  condition = condition_proc || block
54
- raise ArgumentError, "Condition proc or block is required" unless condition
55
- raise ArgumentError, "Condition must respond to call" unless condition.respond_to?(:call)
56
68
 
57
- # Registra il permesso nel registry
69
+ unless condition
70
+ raise BetterModel::Errors::Permissible::ConfigurationError, "Condition proc or block is required"
71
+ end
72
+
73
+ unless condition.respond_to?(:call)
74
+ raise BetterModel::Errors::Permissible::ConfigurationError, "Condition must respond to call"
75
+ end
76
+
77
+ # Register permission in registry
58
78
  self.permit_definitions = permit_definitions.merge(permission_name => condition.freeze).freeze
59
79
 
60
- # Genera il metodo dinamico permit_#{permission_name}?
80
+ # Generate dynamic method permit_#{permission_name}?
61
81
  define_permit_method(permission_name)
62
82
  end
63
83
 
64
- # Lista di tutti i permessi definiti per questa classe
65
- def defined_permissions
66
- permit_definitions.keys
67
- end
84
+ # List all permissions defined for this class.
85
+ #
86
+ # @return [Array<Symbol>] Array of defined permission names
87
+ #
88
+ # @example
89
+ # Article.defined_permissions # => [:delete, :edit, :publish]
90
+ def defined_permissions = permit_definitions.keys
68
91
 
69
- # Verifica se un permesso è definito
70
- def permission_defined?(permission_name)
71
- permit_definitions.key?(permission_name.to_sym)
72
- end
92
+ # Check if a permission is defined.
93
+ #
94
+ # @param permission_name [Symbol, String] Permission name to check
95
+ # @return [Boolean] true if permission is defined
96
+ #
97
+ # @example
98
+ # Article.permission_defined?(:delete) # => true
99
+ def permission_defined?(permission_name) = permit_definitions.key?(permission_name.to_sym)
73
100
 
74
101
  private
75
102
 
76
- # Genera dinamicamente il metodo permit_#{permission_name}? per ogni permesso definito
103
+ # Generate dynamic method permit_#{permission_name}? for each defined permission.
104
+ #
105
+ # @param permission_name [Symbol] Permission name
106
+ # @api private
77
107
  def define_permit_method(permission_name)
78
108
  method_name = "permit_#{permission_name}?"
79
109
 
80
- # Evita di ridefinire metodi se già esistono
110
+ # Avoid redefining methods if they already exist
81
111
  return if method_defined?(method_name)
82
112
 
83
113
  define_method(method_name) do
@@ -86,35 +116,33 @@ module BetterModel
86
116
  end
87
117
  end
88
118
 
89
- # Metodo generico per verificare se un permesso è garantito
119
+ # Generic method to check if a permission is granted.
90
120
  #
91
- # Parametri:
92
- # - permission_name: simbolo del permesso da verificare
121
+ # Evaluates the permission condition in the context of the model instance.
122
+ # Returns false if permission is not defined (secure by default).
93
123
  #
94
- # Ritorna:
95
- # - true se il permesso è garantito
96
- # - false se il permesso non è garantito o non è definito
124
+ # @param permission_name [Symbol, String] Permission name to check
125
+ # @return [Boolean] true if permission is granted, false otherwise
97
126
  #
98
- # Esempio:
99
- # article.permit?(:delete)
127
+ # @example
128
+ # article.permit?(:delete) # => true
100
129
  def permit?(permission_name)
101
130
  permission_name = permission_name.to_sym
102
131
  condition = self.class.permit_definitions[permission_name]
103
132
 
104
- # Se il permesso non è definito, ritorna false (secure by default)
133
+ # If permission is not defined, return false (secure by default)
105
134
  return false unless condition
106
135
 
107
- # Valuta la condizione nel contesto dell'istanza del modello
108
- # Gli errori si propagano naturalmente - fail fast
136
+ # Evaluate condition in context of model instance
137
+ # Errors propagate naturally - fail fast
109
138
  instance_exec(&condition)
110
139
  end
111
140
 
112
- # Ritorna tutti i permessi disponibili per questa istanza con i loro valori
141
+ # Returns all available permissions for this instance with their values.
113
142
  #
114
- # Ritorna:
115
- # - Hash con chiavi simbolo (permessi) e valori booleani (garantiti/negati)
143
+ # @return [Hash{Symbol => Boolean}] Hash with permission names and their granted status
116
144
  #
117
- # Esempio:
145
+ # @example
118
146
  # article.permissions
119
147
  # # => { delete: true, edit: false, publish: false, archive: false }
120
148
  def permissions
@@ -123,26 +151,49 @@ module BetterModel
123
151
  end
124
152
  end
125
153
 
126
- # Verifica se l'istanza ha almeno un permesso garantito
127
- def has_any_permission?
128
- permissions.values.any?
129
- end
154
+ # Check if instance has at least one granted permission.
155
+ #
156
+ # @return [Boolean] true if any permission is granted
157
+ #
158
+ # @example
159
+ # article.has_any_permission? # => true
160
+ def has_any_permission? = permissions.values.any?
130
161
 
131
- # Verifica se l'istanza ha tutti i permessi specificati garantiti
162
+ # Check if instance has all specified permissions granted.
163
+ #
164
+ # @param permission_names [Array<Symbol>] Permission names to check
165
+ # @return [Boolean] true if all permissions are granted
166
+ #
167
+ # @example
168
+ # article.has_all_permissions?([:edit, :publish]) # => false
132
169
  def has_all_permissions?(permission_names)
133
170
  Array(permission_names).all? { |permission_name| permit?(permission_name) }
134
171
  end
135
172
 
136
- # Filtra una lista di permessi restituendo solo quelli garantiti
173
+ # Filter a list of permissions returning only granted ones.
174
+ #
175
+ # @param permission_names [Array<Symbol>] Permission names to filter
176
+ # @return [Array<Symbol>] Granted permissions
177
+ #
178
+ # @example
179
+ # article.granted_permissions([:edit, :delete, :publish]) # => [:edit]
137
180
  def granted_permissions(permission_names)
138
181
  Array(permission_names).select { |permission_name| permit?(permission_name) }
139
182
  end
140
183
 
141
- # Override di as_json per includere automaticamente i permessi se richiesto
184
+ # Override as_json to automatically include permissions if requested.
185
+ #
186
+ # @param options [Hash] Options for as_json
187
+ # @option options [Boolean] :include_permissions Include permissions in JSON output
188
+ # @return [Hash] JSON representation
189
+ #
190
+ # @example
191
+ # article.as_json(include_permissions: true)
192
+ # # => { ..., "permissions" => { "delete" => true, "edit" => false } }
142
193
  def as_json(options = {})
143
194
  result = super
144
195
 
145
- # Include i permessi se esplicitamente richiesto, converting symbol keys to strings
196
+ # Include permissions if explicitly requested, converting symbol keys to strings
146
197
  result["permissions"] = permissions.transform_keys(&:to_s) if options[:include_permissions]
147
198
 
148
199
  result