Module: Familia::Features::Housekeeping::ModelClassMethods

Defined in:
lib/familia/features/housekeeping.rb

Overview

Housekeeping::ModelClassMethods

Instance Method Summary collapse

Instance Method Details

#chore(name) {|obj| ... } ⇒ Proc

Register a chore by name. The block receives the instance.

Parameters:

  • name (Symbol, String)

    chore identifier

Yields:

  • (obj)

    block invoked with the instance during do_chore!/do_chores!

Returns:

  • (Proc)

    the registered block

Raises:

  • (ArgumentError)

    if name is blank or no block is given



68
69
70
71
72
73
# File 'lib/familia/features/housekeeping.rb', line 68

def chore(name, &block)
  raise ArgumentError, 'chore name required' if name.nil? || name.to_s.empty?
  raise ArgumentError, "chore #{name.inspect} requires a block" unless block

  chores[name.to_sym] = block
end

#choresHash{Symbol => Proc}

Registered chores in registration order. Subclasses inherit a copy of their parent's chores on first access, so registering a new chore on a subclass does not mutate the parent.

Returns:

  • (Hash{Symbol => Proc})


80
81
82
83
84
85
86
# File 'lib/familia/features/housekeeping.rb', line 80

def chores
  @chores ||= if superclass.respond_to?(:chores)
    superclass.chores.dup
  else
    {}
  end
end

#run_chores!(chore_name: nil, limit: nil, batch_size: DEFAULT_BATCH_SIZE) ⇒ Hash

Run registered chores against every record reachable via the instances class-level sorted set. Records are loaded in pipelined batches (load_multi) and each chore is executed per record with error isolation -- one failing chore call does not abort the run.

Stats shape:

{ model: 'User', scanned: 4200, chores: { downcase_email: { modified: 37, errors: 0 }, default_timezone: { modified: 102, errors: 1 }, }, }

The modified counter increments when the chore block returns a truthy value (the conventional "modified" signal). The errors counter increments when the chore block raises; the exception is logged via Familia.warn and iteration continues.

Requires the class to expose an instances collection (Horreum's default class-level sorted set) and load_multi (Horreum default).

Parameters:

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

    specific chore to run; nil runs every registered chore against each record

  • limit (Integer, nil) (defaults to: nil)

    cap on records scanned; nil iterates all

  • batch_size (Integer) (defaults to: DEFAULT_BATCH_SIZE)

    pipeline batch size for load_multi

Returns:

  • (Hash)

    stats hash (see above)

Raises:

  • (ArgumentError)

    if the class has no chores, the named chore is not registered, or instances/load_multi are unavailable



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/familia/features/housekeeping.rb', line 119

def run_chores!(chore_name: nil, limit: nil, batch_size: DEFAULT_BATCH_SIZE)
  unless respond_to?(:instances) && respond_to?(:load_multi)
    raise ArgumentError, "#{name} cannot run_chores! without instances and load_multi"
  end

  chore_keys = resolve_chore_keys(chore_name)
  stats      = chore_keys.to_h { |key| [key, { modified: 0, errors: 0 }] }
  scanned    = 0

  instances.to_a.each_slice(batch_size) do |batch_ids|
    break if limit && scanned >= limit

    batch_ids = batch_ids.take(limit - scanned) if limit
    records   = load_multi(batch_ids).compact
    records.each do |record|
      scanned += 1
      chore_keys.each do |key|
        begin
          stats[key][:modified] += 1 if record.do_chore!(key)
        rescue StandardError => e
          stats[key][:errors] += 1
          Familia.warn "[run_chores!] #{name}##{record.identifier} chore=#{key} failed: #{e.message}"
        end
      end
    end
  end

  { model: name, scanned: scanned, chores: stats }
end