Module: Familia::Horreum::DirtyTracking

Included in:
Familia::Horreum
Defined in:
lib/familia/horreum/dirty_tracking.rb

Overview

DirtyTracking - Tracks in-memory field changes since last save/refresh.

Provides a minimal ActiveModel::Dirty-inspired API for detecting which scalar fields have been modified. This is useful for:

  • Knowing whether a save is needed
  • Warning when collection writes happen with unsaved scalar changes
  • Inspecting what changed and the old/new values

Fields are marked dirty automatically by the setter defined in FieldType. Dirty state is cleared after save, commit_fields, and refresh operations.

Examples:

user = User.new(name: "Alice")
user.dirty?            # => false (just initialized)
user.name = "Bob"
user.dirty?            # => true
user.dirty?(:name)     # => true
user.changed_fields    # => { name: ["Alice", "Bob"] }
user.save
user.dirty?            # => false

Instance Method Summary collapse

Instance Method Details

#changed_fieldsHash{Symbol => Array(Object, Object)}

Returns a hash of changed fields with [old_value, new_value] pairs.

The old value is captured at the time of the first change since the last clear. The new value is read from the current instance variable.

Returns:

  • (Hash{Symbol => Array(Object, Object)})


82
83
84
85
86
87
88
# File 'lib/familia/horreum/dirty_tracking.rb', line 82

def changed_fields
  @dirty_fields ||= {}
  @dirty_fields.each_with_object({}) do |(field_name, old_value), result|
    current_value = instance_variable_get(:"@#{field_name}")
    result[field_name] = [old_value, current_value]
  end
end

#clear_dirty!(*field_names) ⇒ void

This method returns an undefined value.

Clears dirty tracking state for all or specific fields.

Called automatically after save, commit_fields, and refresh. When field names are provided, only those fields are cleared, preserving dirty state for fields that were not persisted.

Parameters:

  • field_names (Array<Symbol, String>)

    optional field names to clear. When empty, clears all dirty state (blanket reset).



100
101
102
103
104
105
106
# File 'lib/familia/horreum/dirty_tracking.rb', line 100

def clear_dirty!(*field_names)
  if field_names.empty?
    @dirty_fields = {}
  else
    field_names.each { |f| @dirty_fields.delete(f.to_sym) }
  end
end

#dirty?(field = nil) ⇒ Boolean

Whether any fields (or a specific field) have unsaved changes.

Parameters:

  • field (Symbol, String, nil) (defaults to: nil)

    optional field to check

Returns:

  • (Boolean)


56
57
58
59
60
61
62
63
64
# File 'lib/familia/horreum/dirty_tracking.rb', line 56

def dirty?(field = nil)
  @dirty_fields ||= {}

  if field
    @dirty_fields.key?(field.to_sym)
  else
    !@dirty_fields.empty?
  end
end

#dirty_fieldsArray<Symbol>

Returns the set of field names that have been modified.

Returns:

  • (Array<Symbol>)

    field names with unsaved changes



70
71
72
73
# File 'lib/familia/horreum/dirty_tracking.rb', line 70

def dirty_fields
  @dirty_fields ||= {}
  @dirty_fields.keys
end

#mark_dirty!(field_name, old_value) ⇒ void

This method returns an undefined value.

Mark a field as dirty, recording its old value before the change.

Called by the field setter in FieldType#define_setter. Only records the original value on the first change (subsequent changes update the current value but preserve the original baseline).

Parameters:

  • field_name (Symbol)

    the field that changed

  • old_value (Object)

    the value before the change



39
40
41
42
43
44
45
46
47
48
49
# File 'lib/familia/horreum/dirty_tracking.rb', line 39

def mark_dirty!(field_name, old_value)
  @dirty_fields ||= {}
  field_sym = field_name.to_sym

  # Only record the original old value on the first mutation.
  # Subsequent changes keep the original baseline so changed_fields
  # shows [original, current] rather than [previous, current].
  unless @dirty_fields.key?(field_sym)
    @dirty_fields[field_sym] = old_value
  end
end