active_record-undo 0.1.5 → 0.3.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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +78 -2
  3. data/DOCUMENTATION.md +340 -133
  4. data/README.md +473 -190
  5. data/app/controllers/active_record/undo/application_controller.rb +100 -0
  6. data/app/controllers/active_record/undo/logs_controller/async_handler.rb +50 -0
  7. data/app/controllers/active_record/undo/logs_controller.rb +116 -0
  8. data/app/controllers/active_record/undo/restores_controller.rb +11 -0
  9. data/app/jobs/active_record/undo/restore_job.rb +72 -0
  10. data/app/jobs/active_record/undo/soft_delete_job.rb +75 -0
  11. data/config/routes.rb +13 -0
  12. data/db/migrate/20260920000000_add_status_and_error_message_to_undo_logs.rb +15 -0
  13. data/lib/active_record/undo/cascade_handler.rb +66 -0
  14. data/lib/active_record/undo/configuration.rb +9 -1
  15. data/lib/active_record/undo/engine.rb +14 -1
  16. data/lib/active_record/undo/model_extension/async_methods.rb +68 -0
  17. data/lib/active_record/undo/model_extension/attribution_helper.rb +8 -0
  18. data/lib/active_record/undo/model_extension/tenant_verification.rb +1 -1
  19. data/lib/active_record/undo/model_extension.rb +22 -6
  20. data/lib/active_record/undo/safe_redirect.rb +60 -0
  21. data/lib/active_record/undo/status_predicates.rb +39 -0
  22. data/lib/active_record/undo/undo_log/token_verifier.rb +48 -0
  23. data/lib/active_record/undo/undo_log.rb +77 -2
  24. data/lib/active_record/undo/version.rb +1 -1
  25. data/lib/active_record/undo/view_helpers/status_badge_helper.rb +84 -0
  26. data/lib/active_record/undo/view_helpers.rb +105 -0
  27. data/lib/active_record/undo.rb +37 -1
  28. data/sig/active_record/undo.rbs +50 -1
  29. metadata +29 -1
@@ -4,6 +4,7 @@
4
4
  require_relative 'cascade_handler'
5
5
  require_relative 'model_extension/attribution_helper'
6
6
  require_relative 'model_extension/tenant_verification'
7
+ require_relative 'model_extension/async_methods'
7
8
 
8
9
  module ActiveRecord
9
10
  module Undo
@@ -53,6 +54,8 @@ module ActiveRecord
53
54
  module InstanceMethods
54
55
  include AttributionHelper
55
56
  include TenantVerification
57
+ include AsyncMethods
58
+ include StatusPredicates
56
59
 
57
60
  def soft_deleted?
58
61
  public_send(self.class.undoable_column).present?
@@ -69,13 +72,25 @@ module ActiveRecord
69
72
  end
70
73
 
71
74
  def undoable?
72
- return false unless soft_deleted?
75
+ return false unless persisted? && soft_deleted?
76
+
77
+ log = undo_log
78
+ return false unless log
79
+
80
+ log.respond_to?(:completed?) ? log.completed? : true
81
+ end
82
+
83
+ def undo_log
84
+ return nil unless persisted? && soft_deleted?
85
+
86
+ find_latest_undo_log_item&.undo_log
87
+ end
88
+ alias latest_undo_log undo_log
73
89
 
74
- ActiveRecord::Undo::UndoLogItem.joins(:undo_log).exists?(
75
- item_type: self.class.name,
76
- item_id: id
77
- )
90
+ def signed_token(expires_in: ActiveRecord::Undo.config.token_expires_in, purpose: :restore)
91
+ undo_log&.signed_token(expires_in: expires_in, purpose: purpose)
78
92
  end
93
+ alias to_signed_token signed_token
79
94
 
80
95
  def soft_delete!(whodunnit: nil, tenant: nil)
81
96
  ensure_undoable_column_exists!
@@ -113,8 +128,9 @@ module ActiveRecord
113
128
 
114
129
  def find_latest_undo_log_item
115
130
  ActiveRecord::Undo::UndoLogItem
131
+ .joins(:undo_log)
116
132
  .where(item: self)
117
- .order(created_at: :desc)
133
+ .order(created_at: :desc, id: :desc)
118
134
  .first
119
135
  end
120
136
 
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+
5
+ module ActiveRecord
6
+ module Undo
7
+ module SafeRedirect
8
+ private
9
+
10
+ def determine_redirect_path
11
+ safe_redirect_path(params[:redirect_to]) ||
12
+ safe_redirect_path(request.referer) ||
13
+ resolve_fallback_path
14
+ end
15
+
16
+ def safe_redirect_path(path)
17
+ return nil if invalid_path_string?(path)
18
+
19
+ uri = URI.parse(path)
20
+ return path if valid_relative_path?(path, uri)
21
+ return path if valid_same_host_path?(uri)
22
+
23
+ nil
24
+ rescue URI::InvalidURIError
25
+ nil
26
+ end
27
+
28
+ def invalid_path_string?(path)
29
+ path.blank? || !path.is_a?(String) || path.match?(/[\r\n]/)
30
+ end
31
+
32
+ def valid_relative_path?(path, uri)
33
+ uri.relative? && uri.scheme.nil? && path.start_with?('/') && !path.start_with?('//') && !path.start_with?('/\\')
34
+ end
35
+
36
+ def valid_same_host_path?(uri)
37
+ %w[http https].include?(uri.scheme) &&
38
+ uri.host == request.host &&
39
+ (uri.port.nil? || uri.port == request.port)
40
+ end
41
+
42
+ def resolve_fallback_path
43
+ cfg = ActiveRecord::Undo.config.default_redirect_path
44
+ if cfg.respond_to?(:call)
45
+ cfg.arity.zero? ? cfg.call : cfg.call(main_app)
46
+ elsif cfg.present?
47
+ cfg.to_s
48
+ else
49
+ default_root_path
50
+ end
51
+ rescue StandardError
52
+ '/'
53
+ end
54
+
55
+ def default_root_path
56
+ main_app.respond_to?(:root_path) ? main_app.root_path : '/'
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Undo
5
+ module StatusPredicates
6
+ def soft_delete_pending?
7
+ soft_delete_status == 'pending'
8
+ end
9
+
10
+ def soft_delete_processing?
11
+ soft_delete_status == 'processing'
12
+ end
13
+
14
+ def soft_delete_completed?
15
+ soft_delete_status == 'completed'
16
+ end
17
+
18
+ def soft_delete_failed?
19
+ soft_delete_status == 'failed'
20
+ end
21
+
22
+ def restore_pending?
23
+ restore_status == 'pending'
24
+ end
25
+
26
+ def restore_processing?
27
+ restore_status == 'processing'
28
+ end
29
+
30
+ def restore_completed?
31
+ restore_status == 'completed'
32
+ end
33
+
34
+ def restore_failed?
35
+ restore_status == 'failed'
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/message_verifier'
4
+
5
+ module ActiveRecord
6
+ module Undo
7
+ class UndoLog < ActiveRecord::Base
8
+ module TokenVerifier
9
+ extend ActiveSupport::Concern
10
+
11
+ class_methods do
12
+ def find_by_signed_token(token, purpose: :restore)
13
+ return nil unless token.is_a?(String) && token.present?
14
+
15
+ verified_id = verify_signed_token(token, purpose: purpose)
16
+ return nil unless verified_id
17
+
18
+ find_by(id: verified_id)
19
+ rescue StandardError
20
+ nil
21
+ end
22
+
23
+ def verify_signed_token(token, purpose: :restore)
24
+ return nil unless token.is_a?(String) && token.present?
25
+
26
+ token_verifier.verified(token, purpose: purpose)
27
+ rescue StandardError
28
+ nil
29
+ end
30
+
31
+ def token_verifier
32
+ if defined?(Rails.application) && Rails.application.respond_to?(:message_verifier)
33
+ Rails.application.message_verifier(:active_record_undo)
34
+ else
35
+ key = ActiveRecord::Undo.config.token_secret_key || 'active_record_undo_default_secret_key_32_bytes'
36
+ @token_verifier ||= ActiveSupport::MessageVerifier.new(key, digest: 'SHA256')
37
+ end
38
+ end
39
+ end
40
+
41
+ def signed_token(expires_in: ActiveRecord::Undo.config.token_expires_in, purpose: :restore)
42
+ self.class.token_verifier.generate(id, purpose: purpose, expires_in: expires_in)
43
+ end
44
+ alias to_signed_token signed_token
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,16 +1,25 @@
1
- # lib/active_record/undo/undo_log.rb
2
1
  # frozen_string_literal: true
3
2
 
3
+ require_relative 'undo_log/token_verifier'
4
+
4
5
  module ActiveRecord
5
6
  module Undo
6
7
  class UndoLog < ActiveRecord::Base
7
8
  self.table_name = 'undo_logs'
8
9
 
10
+ include ModelExtension::AttributionHelper
11
+ include ModelExtension::TenantVerification
12
+ include TokenVerifier
13
+ include StatusPredicates
14
+
9
15
  has_many :undo_log_items, class_name: 'ActiveRecord::Undo::UndoLogItem', dependent: :destroy
10
16
 
11
17
  belongs_to :whodunnit, polymorphic: true, optional: true
12
18
  belongs_to :tenant, polymorphic: true, optional: true
13
19
 
20
+ enum :status, { pending: 0, processing: 1, completed: 2, failed: 3 }, default: :completed
21
+ enum :action, { soft_delete: 0, restore: 1 }, default: :soft_delete
22
+
14
23
  scope :for_whodunnit, lambda { |user|
15
24
  if user.is_a?(ActiveRecord::Base)
16
25
  where(whodunnit: user)
@@ -36,10 +45,76 @@ module ActiveRecord
36
45
  end
37
46
  }
38
47
 
48
+ def expired?
49
+ period = ActiveRecord::Undo.config.retention_period
50
+ return false unless period && created_at
51
+
52
+ created_at < Time.current - period
53
+ end
54
+
55
+ def soft_delete_status
56
+ return status if action == 'soft_delete' || action.nil?
57
+
58
+ 'completed'
59
+ end
60
+
61
+ def restore_status
62
+ return status if action == 'restore'
63
+
64
+ nil
65
+ end
66
+
67
+ def restore_later!(whodunnit: nil, queue: nil)
68
+ verify_restore_preconditions!
69
+ with_lock do
70
+ validate_restore_status_eligibility!
71
+ update!(action: :restore, status: :pending)
72
+ end
73
+ enqueue_restore_job!(whodunnit, queue)
74
+ self
75
+ end
76
+
39
77
  # Restores all records associated with this deletion batch
40
- def restore!
78
+ def restore!(whodunnit: nil)
79
+ verify_restore_preconditions!
80
+ validate_restore_status_eligibility!
81
+
82
+ actor = resolve_whodunnit(whodunnit)
83
+ with_whodunnit_context(actor) do
84
+ execute_restore_transaction!
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ def verify_restore_preconditions!
41
91
  ActiveRecord::Undo.verify_configured_context!
92
+ verify_tenant_match!(self) if tenant_present?
93
+ raise ActiveRecord::Undo::Error, 'Undo action has expired.' if expired?
94
+ end
95
+
96
+ def validate_restore_status_eligibility!
97
+ raise ActiveRecord::Undo::Error, 'Cannot restore: UndoLog is currently processing.' if processing?
98
+
99
+ if pending? && action == 'restore'
100
+ raise ActiveRecord::Undo::Error, 'Cannot restore: UndoLog is already scheduled for restore.'
101
+ end
102
+ return if completed? || (action == 'restore' && failed?)
103
+
104
+ raise ActiveRecord::Undo::Error, "Cannot restore: UndoLog is not completed (current status: #{status})."
105
+ end
106
+
107
+ def enqueue_restore_job!(whodunnit, queue)
108
+ job_class = ActiveRecord::Undo::RestoreJob
109
+ job = queue.present? ? job_class.set(queue: queue) : job_class
110
+ job.perform_later(id, whodunnit: whodunnit)
111
+ end
112
+
113
+ def tenant_present?
114
+ tenant_id.present? || tenant_type.present?
115
+ end
42
116
 
117
+ def execute_restore_transaction!
43
118
  transaction do
44
119
  # Reverse order ensures child records are restored before or after parents as needed
45
120
  undo_log_items.reverse_each(&:restore_item!)
@@ -3,6 +3,6 @@
3
3
 
4
4
  module ActiveRecord
5
5
  module Undo
6
- VERSION = '0.1.5'
6
+ VERSION = '0.3.0'
7
7
  end
8
8
  end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Undo
5
+ module ViewHelpers
6
+ module StatusBadgeHelper
7
+ def undo_status_badge(target, action: :auto, **html_options)
8
+ return nil if target.nil?
9
+
10
+ act, status = resolve_target_action_and_status(target, action)
11
+ return nil unless status
12
+
13
+ classes = build_badge_classes(act, status, html_options.delete(:class))
14
+ label = html_options.delete(:text) || status.to_s.humanize
15
+ render_badge_tag(label, classes, html_options)
16
+ end
17
+
18
+ def soft_delete_status_badge(target, **html_options)
19
+ undo_status_badge(target, action: :soft_delete, **html_options)
20
+ end
21
+
22
+ def restore_status_badge(target, **html_options)
23
+ undo_status_badge(target, action: :restore, **html_options)
24
+ end
25
+
26
+ private
27
+
28
+ def resolve_target_action_and_status(target, action)
29
+ return nil if target.nil?
30
+
31
+ case action
32
+ when :soft_delete
33
+ ['soft_delete', extract_target_status(target, :soft_delete_status)]
34
+ when :restore
35
+ ['restore', extract_target_status(target, :restore_status)]
36
+ else
37
+ resolve_auto_action_and_status(target)
38
+ end
39
+ end
40
+
41
+ def extract_target_status(target, status_method)
42
+ if target.respond_to?(status_method)
43
+ target.public_send(status_method)
44
+ elsif target.respond_to?(:status)
45
+ target.status
46
+ end
47
+ end
48
+
49
+ def resolve_auto_action_and_status(target)
50
+ return ['restore', target.restore_status] if auto_restore_status?(target)
51
+ return ['soft_delete', target.soft_delete_status] if auto_soft_delete_status?(target)
52
+ return [log_action(target), target.status] if target.respond_to?(:status)
53
+
54
+ nil
55
+ end
56
+
57
+ def auto_restore_status?(target)
58
+ target.respond_to?(:restore_status) && target.restore_status.present?
59
+ end
60
+
61
+ def auto_soft_delete_status?(target)
62
+ target.respond_to?(:soft_delete_status) && target.soft_delete_status.present?
63
+ end
64
+
65
+ def log_action(target)
66
+ target.respond_to?(:action) ? target.action : nil
67
+ end
68
+
69
+ def build_badge_classes(action, status, custom_class)
70
+ action_class = action ? "undo-#{action.to_s.tr('_', '-')}-badge" : nil
71
+ ['undo-status-badge', action_class, "undo-status-#{status}", custom_class].compact.join(' ')
72
+ end
73
+
74
+ def render_badge_tag(label, classes, html_options)
75
+ if respond_to?(:content_tag)
76
+ content_tag(:span, label, html_options.merge(class: classes))
77
+ else
78
+ "<span class=\"#{classes}\">#{label}</span>"
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'view_helpers/status_badge_helper'
4
+
5
+ module ActiveRecord
6
+ module Undo
7
+ module ViewHelpers
8
+ include StatusBadgeHelper
9
+
10
+ def undo_button_to(target, text = nil, signed: false, **html_options)
11
+ btn_text = extract_undo_text(text, html_options)
12
+ url = resolve_undo_path(target, signed: signed)
13
+ button_to(btn_text, url, method: :post, **html_options)
14
+ end
15
+
16
+ def undo_link_to(target, text = nil, signed: false, **html_options)
17
+ link_text = extract_undo_text(text, html_options)
18
+ url = resolve_undo_path(target, signed: signed)
19
+ data = { turbo_method: :post }.merge(html_options.delete(:data) || {})
20
+ link_to(link_text, url, method: :post, data: data, **html_options)
21
+ end
22
+
23
+ private
24
+
25
+ def extract_undo_text(text, html_options)
26
+ text || html_options.delete(:text) || 'Undo'
27
+ end
28
+
29
+ def resolve_undo_path(target, signed: false)
30
+ router = undo_engine_router
31
+ if signed
32
+ token = extract_signed_token(target)
33
+ router.signed_restore_path(token: token)
34
+ else
35
+ router.restore_log_path(extract_log_target(target))
36
+ end
37
+ end
38
+
39
+ def extract_signed_token(target)
40
+ return target if target.is_a?(String)
41
+
42
+ token = token_from_target(target)
43
+ token || (raise ArgumentError, "Cannot generate signed token for #{target.inspect}")
44
+ end
45
+
46
+ def token_from_target(target)
47
+ if target.respond_to?(:signed_token)
48
+ target.signed_token
49
+ elsif target.respond_to?(:undo_log)
50
+ target.undo_log&.signed_token
51
+ else
52
+ find_log_for_model(target)&.signed_token
53
+ end
54
+ end
55
+
56
+ def extract_log_target(target)
57
+ return target if target.is_a?(ActiveRecord::Undo::UndoLog)
58
+
59
+ if model_target?(target)
60
+ log = log_for_target(target)
61
+ return log if log
62
+
63
+ raise ArgumentError, "Cannot resolve undo log for #{target.inspect}. " \
64
+ 'Ensure the record is soft-deleted and undoable.'
65
+ end
66
+
67
+ target
68
+ end
69
+
70
+ def model_target?(target)
71
+ target.respond_to?(:undoable?) || target.respond_to?(:undo_log)
72
+ end
73
+
74
+ def log_for_target(target)
75
+ if target.respond_to?(:undo_log)
76
+ target.undo_log
77
+ else
78
+ find_log_for_model(target)
79
+ end
80
+ end
81
+
82
+ def find_log_for_model(model)
83
+ return nil unless model.respond_to?(:undoable?)
84
+
85
+ ActiveRecord::Undo::UndoLogItem
86
+ .joins(:undo_log)
87
+ .where(item: model)
88
+ .order(created_at: :desc, id: :desc)
89
+ .first&.undo_log
90
+ end
91
+
92
+ def undo_engine_router
93
+ if respond_to?(:active_record_undo)
94
+ active_record_undo
95
+ elsif defined?(main_app) && main_app.respond_to?(:active_record_undo)
96
+ main_app.active_record_undo
97
+ elsif respond_to?(:restore_log_path)
98
+ self
99
+ else
100
+ ActiveRecord::Undo::Engine.routes.url_helpers
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -12,12 +12,14 @@ end
12
12
  require 'set'
13
13
  require_relative 'undo/version'
14
14
  require_relative 'undo/engine' if defined?(Rails::Engine)
15
+ require_relative 'undo/view_helpers' if defined?(ActionView)
15
16
  require_relative 'undo/configuration'
17
+ require_relative 'undo/safe_redirect'
18
+ require_relative 'undo/status_predicates'
16
19
  require_relative 'undo/model_extension'
17
20
  require_relative 'undo/undo_log'
18
21
  require_relative 'undo/undo_log_item'
19
22
  require_relative 'undo/purger'
20
- require_relative 'undo/purge_job'
21
23
 
22
24
  module ActiveRecord
23
25
  module Undo
@@ -85,6 +87,28 @@ module ActiveRecord
85
87
  raise ActiveRecord::Undo::SecurityError, msg
86
88
  end
87
89
 
90
+ def base_controller_class
91
+ configured = config.base_controller
92
+ return configured if configured.is_a?(Class)
93
+
94
+ (configured.presence && configured.to_s.safe_constantize) || default_base_controller
95
+ end
96
+
97
+ def default_base_controller
98
+ defined?(ActionController::Base) ? ActionController::Base : Object
99
+ end
100
+
101
+ def base_job_class
102
+ configured = config.base_job
103
+ return configured if configured.is_a?(Class)
104
+
105
+ (configured.presence && configured.to_s.safe_constantize) || default_base_job
106
+ end
107
+
108
+ def default_base_job
109
+ defined?(ActiveJob::Base) ? ActiveJob::Base : Object
110
+ end
111
+
88
112
  private
89
113
 
90
114
  def eager_load_rails!
@@ -98,6 +122,18 @@ module ActiveRecord
98
122
  end
99
123
  end
100
124
 
125
+ begin
126
+ require 'active_job'
127
+ rescue LoadError
128
+ # ActiveJob is optional
129
+ end
130
+
131
+ if defined?(ActiveJob::Base)
132
+ require_relative 'undo/purge_job'
133
+ require_relative '../../app/jobs/active_record/undo/soft_delete_job'
134
+ require_relative '../../app/jobs/active_record/undo/restore_job'
135
+ end
136
+
101
137
  if defined?(ActiveSupport)
102
138
  ActiveSupport.on_load(:active_record) do
103
139
  include ActiveRecord::Undo::ModelExtension
@@ -1,6 +1,55 @@
1
1
  module ActiveRecord
2
2
  module Undo
3
3
  VERSION: String
4
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+
5
+ class Error < StandardError
6
+ end
7
+
8
+ class SecurityError < Error
9
+ end
10
+
11
+ def self.whodunnit: () -> untyped
12
+ def self.whodunnit=: (untyped) -> untyped
13
+ def self.current_tenant: () -> untyped
14
+ def self.current_tenant=: (untyped) -> untyped
15
+ def self.config: () -> Configuration
16
+ def self.configure: () { (Configuration) -> void } -> void
17
+ def self.registered_models: () -> Set[untyped]
18
+ def self.undoable_models: () -> Array[untyped]
19
+ def self.base_controller_class: () -> untyped
20
+ def self.base_job_class: () -> untyped
21
+
22
+ class Configuration
23
+ attr_accessor retention_period: untyped
24
+ attr_accessor current_user_method: untyped
25
+ attr_accessor current_tenant_method: untyped
26
+ attr_accessor base_controller: untyped
27
+ attr_accessor base_job: untyped
28
+ attr_accessor default_redirect_path: untyped
29
+ attr_accessor token_secret_key: String?
30
+ attr_accessor token_expires_in: untyped
31
+ attr_accessor error_handling: Symbol
32
+
33
+ def initialize: () -> void
34
+ end
35
+
36
+ module StatusPredicates
37
+ def soft_delete_pending?: () -> bool
38
+ def soft_delete_processing?: () -> bool
39
+ def soft_delete_completed?: () -> bool
40
+ def soft_delete_failed?: () -> bool
41
+ def restore_pending?: () -> bool
42
+ def restore_processing?: () -> bool
43
+ def restore_completed?: () -> bool
44
+ def restore_failed?: () -> bool
45
+ end
46
+
47
+ module ViewHelpers
48
+ def undo_button_to: (untyped target, ?String? text, ?signed: bool, **untyped html_options) -> untyped
49
+ def undo_link_to: (untyped target, ?String? text, ?signed: bool, **untyped html_options) -> untyped
50
+ def undo_status_badge: (untyped target, ?action: Symbol, **untyped html_options) -> untyped
51
+ def soft_delete_status_badge: (untyped target, **untyped html_options) -> untyped
52
+ def restore_status_badge: (untyped target, **untyped html_options) -> untyped
53
+ end
5
54
  end
6
55
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record-undo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Saurabh Sharma
@@ -9,6 +9,20 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activejob
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 7.0.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 7.0.0
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: activerecord
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -136,8 +150,16 @@ files:
136
150
  - LICENSE.txt
137
151
  - README.md
138
152
  - Rakefile
153
+ - app/controllers/active_record/undo/application_controller.rb
154
+ - app/controllers/active_record/undo/logs_controller.rb
155
+ - app/controllers/active_record/undo/logs_controller/async_handler.rb
156
+ - app/controllers/active_record/undo/restores_controller.rb
157
+ - app/jobs/active_record/undo/restore_job.rb
158
+ - app/jobs/active_record/undo/soft_delete_job.rb
159
+ - config/routes.rb
139
160
  - db/migrate/20260808000000_create_active_record_undo_tables.rb
140
161
  - db/migrate/20260821000000_add_tenant_and_user_attribution_to_undo_logs.rb
162
+ - db/migrate/20260920000000_add_status_and_error_message_to_undo_logs.rb
141
163
  - lib/active_record/undo.rb
142
164
  - lib/active_record/undo/cascade_handler.rb
143
165
  - lib/active_record/undo/cascade_handler/association_finder.rb
@@ -145,15 +167,21 @@ files:
145
167
  - lib/active_record/undo/configuration.rb
146
168
  - lib/active_record/undo/engine.rb
147
169
  - lib/active_record/undo/model_extension.rb
170
+ - lib/active_record/undo/model_extension/async_methods.rb
148
171
  - lib/active_record/undo/model_extension/attribution_helper.rb
149
172
  - lib/active_record/undo/model_extension/tenant_verification.rb
150
173
  - lib/active_record/undo/purge_job.rb
151
174
  - lib/active_record/undo/purger.rb
152
175
  - lib/active_record/undo/purger/reflection_helper.rb
176
+ - lib/active_record/undo/safe_redirect.rb
177
+ - lib/active_record/undo/status_predicates.rb
153
178
  - lib/active_record/undo/tasks/purge.rake
154
179
  - lib/active_record/undo/undo_log.rb
180
+ - lib/active_record/undo/undo_log/token_verifier.rb
155
181
  - lib/active_record/undo/undo_log_item.rb
156
182
  - lib/active_record/undo/version.rb
183
+ - lib/active_record/undo/view_helpers.rb
184
+ - lib/active_record/undo/view_helpers/status_badge_helper.rb
157
185
  - lib/tasks/active_record_undo_tasks.rake
158
186
  - sig/active_record/undo.rbs
159
187
  homepage: https://github.com/saurabh-activecode/active_record-undo