Class: ScriptTracker::Base
- Inherits:
-
Object
- Object
- ScriptTracker::Base
- Defined in:
- lib/script_tracker/base.rb
Defined Under Namespace
Classes: ScriptSkipped, ScriptTimeoutError
Class Method Summary collapse
-
.check_timeout!(start_time, max_duration = timeout) ⇒ Object
Check if execution has exceeded timeout (safer alternative to Timeout.timeout) Use this inside your scripts for manual timeout checking:.
- .execute ⇒ Object
- .execute_with_transaction ⇒ Object
- .log(message, level: :info) ⇒ Object
- .log_progress(current, total, message = nil) ⇒ Object
- .process_in_batches(relation, batch_size: 1000, &block) ⇒ Object
- .run(_executed_script_record = nil) ⇒ Object
- .skip!(reason = nil) ⇒ Object
-
.timeout ⇒ Object
Default timeout: 5 minutes Override in subclass to customize: def self.timeout 3600 # 1 hour end.
Class Method Details
.check_timeout!(start_time, max_duration = timeout) ⇒ Object
Check if execution has exceeded timeout (safer alternative to Timeout.timeout) Use this inside your scripts for manual timeout checking:
def self.execute
start_time = Time.current
User.find_each do |user|
check_timeout!(start_time, timeout)
# Process user...
end
end
120 121 122 123 124 125 |
# File 'lib/script_tracker/base.rb', line 120 def check_timeout!(start_time, max_duration = timeout) elapsed = Time.current - start_time return unless max_duration&.positive? && elapsed > max_duration raise ScriptTimeoutError, "Script execution exceeded #{max_duration} seconds (elapsed: #{elapsed.round(2)}s)" end |
.execute ⇒ Object
70 71 72 |
# File 'lib/script_tracker/base.rb', line 70 def execute raise NotImplementedError, 'Subclasses must implement the execute method' end |
.execute_with_transaction ⇒ Object
64 65 66 67 68 |
# File 'lib/script_tracker/base.rb', line 64 def execute_with_transaction ActiveRecord::Base.transaction do execute end end |
.log(message, level: :info) ⇒ Object
80 81 82 83 84 |
# File 'lib/script_tracker/base.rb', line 80 def log(, level: :info) = Time.current.strftime('%Y-%m-%d %H:%M:%S') prefix = level == :error ? '[ERROR]' : '[INFO]' puts "#{prefix} [#{}] #{}" end |
.log_progress(current, total, message = nil) ⇒ Object
86 87 88 89 90 91 |
# File 'lib/script_tracker/base.rb', line 86 def log_progress(current, total, = nil) percentage = ((current.to_f / total) * 100).round(2) progress_detail = "(#{current}/#{total} - #{percentage}%)" msg = ? "#{} #{progress_detail}" : "Progress: #{current}/#{total} (#{percentage}%)" log(msg) end |
.process_in_batches(relation, batch_size: 1000, &block) ⇒ Object
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/script_tracker/base.rb', line 93 def process_in_batches(relation, batch_size: 1000, &block) total = relation.count log("There are #{total} records to process") return 0 if total == 0 processed = 0 log("Processing #{total} records in batches of #{batch_size}") relation.find_each(batch_size: batch_size) do |record| block.call(record) processed += 1 log_interval = [batch_size, (total * 0.1).to_i].max log_progress(processed, total) if (processed % log_interval) == 0 end log_progress(processed, total, 'Completed') processed end |
.run(_executed_script_record = nil) ⇒ Object
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/script_tracker/base.rb', line 18 def run(_executed_script_record = nil) require 'timeout' start_time = Time.current timeout_seconds = timeout begin result = nil # Wrap execution in timeout if specified # WARNING: Ruby's Timeout.timeout has known issues and can interrupt code at any point, # potentially leaving resources in inconsistent states. Consider these alternatives: # 1. Implement timeout logic within your script using Time.current checks # 2. Use database statement_timeout for PostgreSQL # 3. Monitor script execution time and kill from outside if needed # This timeout is provided as a last resort safety measure. if timeout_seconds && timeout_seconds > 0 Timeout.timeout(timeout_seconds, ScriptTimeoutError) do result = execute_with_transaction end else result = execute_with_transaction end duration = Time.current - start_time output = "Script completed successfully in #{duration.round(2)}s" log(output) { success: true, skipped: false, output: output, duration: duration } rescue ScriptSkipped => e duration = Time.current - start_time output = e..presence || 'Script was skipped (no action needed)' { success: false, skipped: true, output: output, duration: duration } rescue ScriptTimeoutError duration = Time.current - start_time = "Script execution exceeded timeout of #{timeout_seconds} seconds" log(, level: :error) { success: false, skipped: false, output: , duration: duration } rescue StandardError => e duration = Time.current - start_time = "#{e.class}: #{e.}\n#{e.backtrace.first(10).join("\n")}" log(, level: :error) { success: false, skipped: false, output: , duration: duration } end end |
.skip!(reason = nil) ⇒ Object
74 75 76 77 78 |
# File 'lib/script_tracker/base.rb', line 74 def skip!(reason = nil) = reason ? "Skipping: #{reason}" : 'Skipping script' log() raise ScriptSkipped, end |
.timeout ⇒ Object
Default timeout: 5 minutes Override in subclass to customize:
def self.timeout
3600 # 1 hour
end
14 15 16 |
# File 'lib/script_tracker/base.rb', line 14 def timeout 300 # 5 minutes in seconds end |