Class: Familia::Migration::Script

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

Overview

Lua script registry for atomic Redis operations during migrations.

Provides class-level registration and execution of Lua scripts with EVALSHA/EVAL fallback pattern for efficiency. Scripts are precomputed with their SHA1 hashes at registration time.

Examples:

Registering a custom script

Familia::Migration::Script.register(:my_script, <<~LUA)
  local key = KEYS[1]
  return redis.call('GET', key)
LUA

Executing a script

result = Familia::Migration::Script.execute(
  redis,
  :rename_field,
  keys: ['user:123'],
  argv: ['old_name', 'new_name']
)

Defined Under Namespace

Classes: ScriptError, ScriptNotFound

Constant Summary collapse

ScriptEntry =

Holds script source and precomputed SHA1

Data.define(:source, :sha) do
  def initialize(source:, sha: nil)
    computed_sha = sha || Digest::SHA1.hexdigest(source)
    super(source: source.freeze, sha: computed_sha.freeze)
  end
end

Class Method Summary collapse

Class Method Details

.execute(redis, name, keys: [], argv: []) ⇒ Object

Execute a registered script with EVALSHA/EVAL fallback

Attempts EVALSHA first for efficiency. If the script is not cached on the Redis server (NOSCRIPT error), falls back to EVAL which also caches the script for future calls.

Parameters:

  • redis (Redis)

    Redis client connection

  • name (Symbol)

    Name of the registered script

  • keys (Array<String>) (defaults to: [])

    KEYS array for the Lua script

  • argv (Array) (defaults to: [])

    ARGV array for the Lua script

Returns:

  • (Object)

    The script's return value

Raises:

  • (ScriptNotFound)

    If the script name is not registered

  • (ScriptError)

    If script execution fails (other than NOSCRIPT)



78
79
80
81
82
83
# File 'lib/familia/migration/script.rb', line 78

def execute(redis, name, keys: [], argv: [])
  entry = scripts[name]
  raise ScriptNotFound, "Script not found: #{name}" unless entry

  execute_with_fallback(redis, entry, keys, argv, name)
end

.preload_all(redis) ⇒ Hash{Symbol => String}

Preload all registered scripts to the Redis server

Loads scripts using SCRIPT LOAD so subsequent EVALSHA calls will succeed without fallback. Useful at application startup.

Parameters:

  • redis (Redis)

    Redis client connection

Returns:

  • (Hash{Symbol => String})

    Map of script names to their SHAs



92
93
94
95
96
97
# File 'lib/familia/migration/script.rb', line 92

def preload_all(redis)
  scripts.each_with_object({}) do |(name, entry), loaded|
    sha = redis.script(:load, entry.source)
    loaded[name] = sha
  end
end

.register(name, lua_source) ⇒ ScriptEntry

Register a Lua script with the given name

Parameters:

  • name (Symbol)

    Unique identifier for the script

  • lua_source (String)

    The Lua script source code

Returns:

Raises:

  • (ArgumentError)

    If name is not a Symbol or source is empty



56
57
58
59
60
61
62
63
# File 'lib/familia/migration/script.rb', line 56

def register(name, lua_source)
  raise ArgumentError, 'Script name must be a Symbol' unless name.is_a?(Symbol)
  raise ArgumentError, 'Lua source cannot be empty' if lua_source.nil? || lua_source.strip.empty?

  entry = ScriptEntry.new(source: lua_source.strip)
  scripts[name] = entry
  entry
end

.registered?(name) ⇒ Boolean

Check if a script is registered

Parameters:

  • name (Symbol)

    Script name to check

Returns:

  • (Boolean)

    true if the script exists



103
104
105
# File 'lib/familia/migration/script.rb', line 103

def registered?(name)
  scripts.key?(name)
end

.reset!void

This method returns an undefined value.

Reset the registry (primarily for testing)



118
119
120
121
# File 'lib/familia/migration/script.rb', line 118

def reset!
  @scripts = {}
  register_builtin_scripts
end

.scriptsHash{Symbol => ScriptEntry}

Access the script registry

Returns:

  • (Hash{Symbol => ScriptEntry})

    Frozen hash of registered scripts



46
47
48
# File 'lib/familia/migration/script.rb', line 46

def scripts
  @scripts ||= {}
end

.sha_for(name) ⇒ String?

Get the SHA for a registered script

Parameters:

  • name (Symbol)

    Script name

Returns:

  • (String, nil)

    The script's SHA1 hash or nil if not found



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

def sha_for(name)
  scripts[name]&.sha
end