### Initialize and Start Server Source: https://context7.com/ghostganz/madeleine/llms.txt Configures the Madeleine system, sets up a background thread for periodic snapshots, and starts the DRb service. ```ruby madeleine = SnapshotMadeleine.new("dictionary-base") { Dictionary.new } # Periodic snapshots Thread.new(madeleine) do loop do sleep(30) madeleine.take_snapshot end end DRb.start_service("druby://localhost:1234", DictionaryServer.new(madeleine)) puts "Dictionary server running on druby://localhost:1234" DRb.thread.join ``` -------------------------------- ### Initialize and Use Madeleine System Source: https://github.com/ghostganz/madeleine/blob/master/README.md Demonstrates bootstrapping a prevalent system with an initial state, defining a command class, and executing modifications. ```ruby require 'madeleine' # Create an application as a prevalent system madeleine = SnapshotMadeleine.new("my_example_storage") { # Creating the initial empty system. This is how the application # is bootstrapped on each startup (until the first snapshot is taken). # All the persistent data needs to be reachable from this object. # For this example, the application's whole dataset is a hash # with a counter: {:counter => 0} } # To operate on the system, we need commands. For this example, # we can do additions to our counter: class AdditionCommand def initialize(number) @number = number end def execute(system) # This is the only place from where we're allowed to modify # the system. system[:counter] += @number end end # Do modifications of the system by sending commands through # the Madeleine instance: command = AdditionCommand.new(12) madeleine.execute_command(command) # The commands are written to the command log before executed. # The next time the application starts, all the commands in the # log are re-applied to the system, returning it to the state it # had when it was shut down. # To avoid long start-up times when the command log gets large, # you can do occasional snapshots of the entire system. You must # also do a snapshot before deploying any changes in your application # logic. madeleine.take_snapshot ``` -------------------------------- ### Execute Command on Prevalent System Source: https://context7.com/ghostganz/madeleine/llms.txt Executes a command, logging it before execution. The command's `execute` method is called with the system and optional context. The return value of `execute` is returned by this method. ```ruby require 'madeleine' # Define a command class - must implement execute(system) class AddItemCommand def initialize(item) @item = item end def execute(system) system[:items] << @item system[:items].length # Return value is passed back to caller end end class IncrementCommand def initialize(amount) @amount = amount end def execute(system) system[:counter] += @amount system[:counter] end end # Command with execution context (receives system and context) class SendNotificationCommand def initialize(user_id, message) @user_id = user_id @message = message end def execute(system, context) # context is the execution_context passed to SnapshotMadeleine.new user = system[:users].find { |u| u[:id] == @user_id } context.notify(user[:email], @message) if user end end # Create system and execute commands madeleine = SnapshotMadeleine.new("command_demo") { { counter: 0, items: [] } } # Execute commands - they are logged before execution count = madeleine.execute_command(IncrementCommand.new(5)) puts count # => 5 item_count = madeleine.execute_command(AddItemCommand.new("apple")) puts item_count # => 1 madeleine.execute_command(AddItemCommand.new("banana")) puts madeleine.system[:items] # => ["apple", "banana"] # After restart, commands are replayed automatically # System state is restored to { counter: 5, items: ["apple", "banana"] } ``` -------------------------------- ### Use Execution Context in Madeleine Source: https://github.com/ghostganz/madeleine/blob/master/README.md Shows how to pass non-persistent dependencies like a Rack application into commands via an execution context. ```ruby madeleine = SnapshotMadeleine.new("my_example_storage", execution_context: rack_app) { Hash.new } class WebRelatedCommand def execute(system, context) # The context will now be the Rack application ... end end ``` -------------------------------- ### Implement Dictionary Client Source: https://context7.com/ghostganz/madeleine/llms.txt Connects to the remote DRb service to perform additions and lookups on the persistent dictionary. ```ruby require 'drb' DRb.start_service dictionary = DRbObject.new(nil, "druby://localhost:1234") # Add entries dictionary.add("ruby", "A dynamic programming language") dictionary.add("madeleine", "Object prevalence for Ruby") # Lookup entries puts dictionary.lookup("ruby") # => "A dynamic programming language" puts dictionary.lookup("madeleine") # => "Object prevalence for Ruby" ``` -------------------------------- ### Take system snapshots with Madeleine Source: https://context7.com/ghostganz/madeleine/llms.txt Persist the current system state to disk to optimize recovery. Periodic snapshots or threshold-based triggers are recommended for long-running applications. ```ruby require 'madeleine' madeleine = SnapshotMadeleine.new("snapshot_demo") { { data: [] } } # Execute some commands 100.times { |i| madeleine.execute_command(AddDataCommand.new(i)) } # Take a snapshot to speed up future startups madeleine.take_snapshot # Recommended: automatic snapshots in a background thread Thread.new(madeleine) do |m| loop do sleep(60 * 60) # Every hour m.take_snapshot puts "Snapshot taken at #{Time.now}" end end # Or snapshot based on command count class SnapshotAfterCommands def initialize(madeleine, threshold) @madeleine = madeleine @threshold = threshold @count = 0 end def execute_command(command) result = @madeleine.execute_command(command) @count += 1 if @count >= @threshold @madeleine.take_snapshot @count = 0 end result end end wrapper = SnapshotAfterCommands.new(madeleine, 1000) wrapper.execute_command(AddDataCommand.new("item")) ``` -------------------------------- ### Implement DRb Service Wrapper Source: https://context7.com/ghostganz/madeleine/llms.txt Wraps the Madeleine instance to expose state-modifying commands and read-only queries over DRb. ```ruby class DictionaryServer def initialize(madeleine) @madeleine = madeleine end def add(key, value) @madeleine.execute_command(Addition.new(key, value)) end def lookup(key) @madeleine.execute_query(Lookup.new(key)) end end ``` -------------------------------- ### Implement deterministic time with ClockedSystem Source: https://context7.com/ghostganz/madeleine/llms.txt Use the ClockedSystem module to synchronize time via commands, ensuring deterministic behavior during system replay. ```ruby require 'madeleine/clock' # System that needs time - include ClockedSystem class EventStore include Madeleine::Clock::ClockedSystem def initialize @events = [] end def add_event(name) # Use clock.time instead of Time.now for deterministic replay @events << { name: name, created_at: clock.time } end def events_after(time) @events.select { |e| e[:created_at] > time } end def latest_event_time @events.last&.dig(:created_at) end end class AddEventCommand def initialize(name) @name = name end def execute(system) system.add_event(@name) end end # Use ClockedSnapshotMadeleine for clocked systems madeleine = ClockedSnapshotMadeleine.new("clocked_demo") { EventStore.new } # Launch TimeActor to keep clock updated # Sends periodic Tick commands to advance system time time_actor = Madeleine::Clock::TimeActor.launch(madeleine, 0.1) # tick every 0.1 seconds madeleine.execute_command(AddEventCommand.new("user_login")) sleep(1) madeleine.execute_command(AddEventCommand.new("user_logout")) puts madeleine.system.events_after(Time.at(0)) # Stop time actor on shutdown time_actor.destroy madeleine.close ``` -------------------------------- ### Integrate Madeleine with DRb Source: https://context7.com/ghostganz/madeleine/llms.txt Create a persistent, network-accessible service by combining Madeleine with Distributed Ruby. ```ruby # dictionary_server.rb require 'madeleine' require 'drb' # The prevalent system - a simple dictionary class Dictionary def initialize @data = {} end def add(key, value) @data[key] = value end def lookup(key) @data[key] end end # Command for modifications class Addition def initialize(key, value) @key, @value = key, value end def execute(system) system.add(@key, @value) end end ``` -------------------------------- ### Compress snapshots with ZMarshal Source: https://context7.com/ghostganz/madeleine/llms.txt Reduce snapshot file size by wrapping the marshaller with zlib compression. Supports both default Marshal and YAML formats. ```ruby require 'madeleine' require 'madeleine/zmarshal' # Compressed snapshots with default Marshal compressed_marshaller = Madeleine::ZMarshal.new madeleine = SnapshotMadeleine.new("compressed_storage", snapshot_marshaller: compressed_marshaller) { { large_data: Array.new(10000) { |i| "item_#{i}" } } } madeleine.take_snapshot # Snapshot is gzip compressed # Compressed YAML snapshots (human-readable when decompressed) require 'yaml' yaml_compressed = Madeleine::ZMarshal.new(YAML) madeleine = SnapshotMadeleine.new("yaml_compressed", snapshot_marshaller: yaml_compressed) { { config: { debug: true }, users: [] } } madeleine.take_snapshot ``` -------------------------------- ### Create SnapshotMadeleine Instance Source: https://context7.com/ghostganz/madeleine/llms.txt Instantiates a Madeleine prevalence system. Restores from snapshot if available, otherwise initializes with the provided block. Supports custom marshallers and execution contexts. ```ruby require 'madeleine' # Basic usage - create a prevalent system with a hash as the root object madeleine = SnapshotMadeleine.new("storage_directory") { # Initial system state - executed only when no snapshot exists { counter: 0, items: [] } } # Access the underlying system directly (read-only) puts madeleine.system[:counter] # => 0 # With custom marshaller (YAML instead of Marshal) require 'yaml' madeleine = SnapshotMadeleine.new("yaml_storage", snapshot_marshaller: YAML) { { name: "Example", data: {} } } # With execution context (passed to commands as second argument) class MyApp attr_reader :config def initialize @config = { api_key: "secret" } end end app = MyApp.new madeleine = SnapshotMadeleine.new("app_storage", execution_context: app) { { users: [] } } ``` -------------------------------- ### Close Madeleine instance Source: https://context7.com/ghostganz/madeleine/llms.txt Flush command logs and terminate the instance cleanly. Always invoke before application shutdown to prevent data corruption. ```ruby require 'madeleine' madeleine = SnapshotMadeleine.new("close_demo") { { sessions: [] } } # Normal operations madeleine.execute_command(AddSessionCommand.new("user123")) # Clean shutdown begin madeleine.take_snapshot # Optional: snapshot before close madeleine.close puts "System closed cleanly" rescue Madeleine::MadeleineClosedException puts "System was already closed" end # After close, commands raise MadeleineClosedException begin madeleine.execute_command(AddSessionCommand.new("user456")) rescue Madeleine::MadeleineClosedException => e puts "Cannot execute: #{e.message}" end ``` -------------------------------- ### Define Read-Only Lookup Operation Source: https://context7.com/ghostganz/madeleine/llms.txt Defines a command class for executing read-only queries against the Madeleine system. ```ruby class Lookup def initialize(key) @key = key end def execute(system) system.lookup(@key) end end ``` -------------------------------- ### Execute Read-Only Query Source: https://context7.com/ghostganz/madeleine/llms.txt Executes a read-only query on the prevalent system without logging. This uses a shared lock, allowing concurrent reads while blocking writes. Ideal for non-modifying operations to prevent log growth. ```ruby require 'madeleine' # Define a query class class CountItemsQuery def execute(system) system[:items].length end end class FindItemQuery def initialize(name) @name = name end def execute(system) system[:items].find { |item| item[:name] == @name } end end class GetStatsQuery def execute(system) { total_items: system[:items].length, counter_value: system[:counter], item_names: system[:items].map { |i| i[:name] } } end end madeleine = SnapshotMadeleine.new("query_demo") { { counter: 10, items: [{ name: "apple" }, { name: "banana" }] } } # Execute queries - not logged, allows concurrent reads count = madeleine.execute_query(CountItemsQuery.new) puts count # => 2 item = madeleine.execute_query(FindItemQuery.new("apple")) puts item # => { name: "apple" } stats = madeleine.execute_query(GetStatsQuery.new) puts stats # => { total_items: 2, counter_value: 10, item_names: ["apple", "banana"] } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.