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: ScriptEntry, ScriptError, ScriptNotFound

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)



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

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



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

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



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

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



105
106
107
# File 'lib/familia/migration/script.rb', line 105

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

.reset!void

This method returns an undefined value.

Reset the registry (primarily for testing)



120
121
122
123
# File 'lib/familia/migration/script.rb', line 120

def reset!
  @scripts = {}
  register_builtin_scripts
end

.scriptsHash{Symbol => ScriptEntry}

Access the script registry

Returns:

  • (Hash{Symbol => ScriptEntry})

    Frozen hash of registered scripts



48
49
50
# File 'lib/familia/migration/script.rb', line 48

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



113
114
115
# File 'lib/familia/migration/script.rb', line 113

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