Class: Familia::BatchResult

Inherits:
Object
  • Object
show all
Defined in:
lib/familia/batch_result.rb

Overview

Represents the result of a batch iteration operation.

BatchResult tracks statistics and errors when processing multiple records via methods like each_record. It provides aggregated metrics for the entire batch run, distinct from MultiResult which wraps a single MULTI/EXEC or pipeline operation.

Examples:

Using BatchResult.collect

result = BatchResult.collect(User.instances) do |user|
  user.deactivate!
end
puts "Processed #{result.scanned}, modified #{result.modified}"
puts "Errors: #{result.errors.size}" if result.errors?

With strict mode

# Re-raises first error after completing iteration
BatchResult.collect(items, strict: true) { |item| item.process! }

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(scanned:, modified:, errors:, duration_ms:) ⇒ BatchResult

Creates a new BatchResult instance.

Parameters:

  • scanned (Integer)

    Total items processed

  • modified (Integer)

    Items where block returned truthy

  • errors (Array<Hash>)

    Array of error hashes with :id and :error keys

  • duration_ms (Float)

    Elapsed time in milliseconds



38
39
40
41
42
43
# File 'lib/familia/batch_result.rb', line 38

def initialize(scanned:, modified:, errors:, duration_ms:)
  @scanned = scanned
  @modified = modified
  @errors = errors
  @duration_ms = duration_ms
end

Instance Attribute Details

#duration_msFloat (readonly)

Total elapsed time in milliseconds

Returns:

  • (Float)

    the current value of duration_ms



29
30
31
# File 'lib/familia/batch_result.rb', line 29

def duration_ms
  @duration_ms
end

#errorsArray<Hash> (readonly)

Per-item errors as [error:, ...]

Returns:

  • (Array<Hash>)

    the current value of errors



29
30
31
# File 'lib/familia/batch_result.rb', line 29

def errors
  @errors
end

#modifiedInteger (readonly)

Count of items where block returned truthy

Returns:

  • (Integer)

    the current value of modified



29
30
31
# File 'lib/familia/batch_result.rb', line 29

def modified
  @modified
end

#scannedInteger (readonly)

Total number of items iterated

Returns:

  • (Integer)

    the current value of scanned



29
30
31
# File 'lib/familia/batch_result.rb', line 29

def scanned
  @scanned
end

Class Method Details

.collect(enumerable, strict: false) {|item| ... } ⇒ BatchResult

Iterates over an enumerable, collecting statistics and errors.

This is the primary factory method for creating BatchResult instances. It tracks how many items were processed, how many returned truthy values, and captures any exceptions that occur during iteration.

Examples:

Basic usage

result = BatchResult.collect(records) { |r| r.update!(status: 'done') }

Strict mode re-raises errors

begin
  BatchResult.collect(records, strict: true) { |r| r.validate! }
rescue => e
  puts "Batch failed: #{e.message}"
end

Parameters:

  • enumerable (Enumerable)

    The collection to iterate

  • strict (Boolean) (defaults to: false)

    When true, re-raises the first captured error after iteration completes. Default: false.

Yields:

  • (item)

    Each item from the enumerable

Yield Returns:

  • (Object)

    Truthy return values increment the modified count

Returns:

  • (BatchResult)

    Aggregated result of the batch operation



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/familia/batch_result.rb', line 68

def self.collect(enumerable, strict: false)
  scanned = 0
  modified = 0
  errors = []
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)

  enumerable.each do |*args|
    scanned += 1
    begin
      result = yield(*args)
      modified += 1 if result
    rescue StandardError => e
      # Extract identifier if possible
      identifier = extract_identifier(args.length == 1 ? args[0] : args)
      errors << { id: identifier, error: e }
    end
  end

  duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000

  batch_result = new(
    scanned: scanned,
    modified: modified,
    errors: errors,
    duration_ms: duration_ms
  )

  # In strict mode, re-raise the first error after completing iteration
  raise errors.first[:error] if strict && errors.any?

  batch_result
end

Instance Method Details

#errors?Boolean

Checks if any errors occurred during the batch.

Returns:

  • (Boolean)

    true if at least one error was captured



104
105
106
# File 'lib/familia/batch_result.rb', line 104

def errors?
  !errors.empty?
end

#skippedInteger

Returns the count of items that were scanned but not modified.

Returns:

  • (Integer)

    Number of items where block returned falsy



119
120
121
# File 'lib/familia/batch_result.rb', line 119

def skipped
  scanned - modified - errors.size
end

#successful?Boolean Also known as: success?

Checks if the batch completed without errors.

Returns:

  • (Boolean)

    true if no errors occurred



111
112
113
# File 'lib/familia/batch_result.rb', line 111

def successful?
  errors.empty?
end

#to_hHash

Returns a hash representation of the result.

Returns:

  • (Hash)

    Result data including all metrics



126
127
128
129
130
131
132
133
134
135
# File 'lib/familia/batch_result.rb', line 126

def to_h
  {
    scanned: scanned,
    modified: modified,
    skipped: skipped,
    errors: errors.size,
    duration_ms: duration_ms.round(2),
    successful: successful?
  }
end

#to_sString

Returns a human-readable summary.

Returns:

  • (String)

    Summary of the batch operation



140
141
142
# File 'lib/familia/batch_result.rb', line 140

def to_s
  "BatchResult: scanned=#{scanned} modified=#{modified} errors=#{errors.size} duration=#{duration_ms.round(2)}ms"
end