class
Sepia::Object
- Sepia::Object
- Reference
- Object
Overview
Base class for all objects managed by Sepia.
Provides generation tracking functionality for optimistic concurrency control, automatic ID generation with UUIDs, and core persistence methods.
All classes that use Sepia's serialization features must inherit from this class.
Example
class MyDocument < Sepia::Object
include Sepia::Serializable
property content : String
def initialize(@content = "")
end
def to_sepia : String
@content
end
def self.from_sepia(sepia_string : String) : self
new(sepia_string)
end
end
doc = MyDocument.new("Hello, World!")
doc.save # Saves to storage with auto-generated UUID
Defined in:
sepia/object.crConstructors
-
.load(id : String, path : String | Nil = nil) : self
Loads an object from storage with transparent generation handling.
Class Method Summary
-
.exists?(id : String) : Bool
Check if an object with the given ID exists in storage.
-
.generation_separator
Separator character used between base ID and generation number.
-
.generation_separator=(generation_separator : String)
Separator character used between base ID and generation number.
-
.latest(base_id : String) : self | Nil
Find the latest version of an object by its base ID.
-
.versions(base_id : String) : Array(self)
Find all versions of an object by its base ID.
Instance Method Summary
-
#base_id : String
Returns the base ID without the generation suffix.
-
#canonical_path : String
Returns the canonical path for this object in storage.
-
#delete
Deletes the object from storage.
-
#generation : Int32
Returns the generation number extracted from the object's ID.
-
#save(path : String | Nil = nil)
Saves the object to storage.
-
#save(metadata = nil) : self
Saves the object to storage with optional metadata.
-
#save(*, force_new_generation : Bool, metadata = nil)
Saves the object with forced new generation.
-
#save_with_generation(metadata = nil) : self
Creates a new version of this object with an incremented generation number.
-
#sepia_id : String
Unique identifier for this object.
-
#sepia_id=(id : String)
Sets the unique identifier for this object.
-
#stale?(expected_generation : Int32) : Bool
Checks if a newer version of this object exists.
Constructor Detail
Loads an object from storage with transparent generation handling.
Generation-transparent loading:
- If you specify a generation (e.g., "note-123.2"), loads that specific generation
- If you specify base ID only (e.g., "note-123"), automatically loads the latest generation
- If no generations exist, loads the base object
This makes generations completely transparent - users get the latest content by default without needing to think about versioning.
# Load the latest version automatically (most common case)
doc = MyDocument.load("doc-uuid")
# Load a specific generation (rare, for version control)
old_doc = MyDocument.load("doc-uuid.1")
# Load from custom path (still respects latest generation)
doc = MyDocument.load("doc-uuid", "/custom/path")
Class Method Detail
Check if an object with the given ID exists in storage.
Returns true if an object of this class with the specified ID exists,
false otherwise. This is useful for checking existence before loading
or verifying if a specific generation exists.
# Check if a specific version exists
if Document.exists?("doc-123.2")
puts "Version 2 exists"
else
puts "Version 2 does not exist"
end
# Check for existence of legacy object (generation 0)
Document.exists?("legacy-doc") # => true if exists
# Common pattern: check before creating new generation
unless Document.exists?("doc-123.3")
# Safe to create version 3
end
Separator character used between base ID and generation number.
Default is "." which creates IDs like "note-123.1", "note-123.2". Can be overridden per class if needed.
class CustomNote < Sepia::Object
class_property generation_separator = "_"
end
note = CustomNote.new
note.save_with_generation # ID becomes "note-uuid_1"
Separator character used between base ID and generation number.
Default is "." which creates IDs like "note-123.1", "note-123.2". Can be overridden per class if needed.
class CustomNote < Sepia::Object
class_property generation_separator = "_"
end
note = CustomNote.new
note.save_with_generation # ID becomes "note-uuid_1"
Find the latest version of an object by its base ID.
Returns the object with the highest generation number, or nil if no versions exist.
This is useful for always retrieving the most recent version of an object.
# Get the latest version of a document
latest_doc = Document.latest("doc-123")
if latest_doc
puts "Latest version: #{latest_doc.generation}"
puts "Content: #{latest_doc.content}"
end
# Always returns nil for non-existent base IDs
Document.latest("non-existent") # => nil
Find all versions of an object by its base ID.
Returns an array of all object versions, sorted by generation number in ascending order. This allows you to access the complete version history of an object.
# Get all versions of a document
all_versions = Document.versions("doc-123")
# Print version history
all_versions.each do |version|
puts "Version #{version.generation}: #{version.created_at}"
end
# versions are sorted by generation
all_versions.first.generation # => 0 (oldest)
all_versions.last.generation # => 2 (newest)
Note: For FileStorage, this scans the directory and may be slow with many versions. Consider caching or cleanup strategies for long-running applications.
Instance Method Detail
Returns the base ID without the generation suffix.
The base ID is the unique identifier that remains constant across all generations.
obj.sepia_id = "note-123.2"
obj.base_id # => "note-123"
obj.sepia_id = "legacy-note"
obj.base_id # => "legacy-note"
Returns the canonical path for this object in storage.
The canonical path follows the pattern: {storage_path}/{ClassName}/{sepia_id}.
This is where Serializable objects are stored by default.
doc = MyDocument.new
doc.sepia_id = "my-doc"
doc.canonical_path # => "/tmp/storage/MyDocument/my-doc"
Deletes the object from storage.
Removes the object's file or directory from storage. For Container objects, also cleans up any nested objects and references.
doc = MyDocument.load("doc-uuid")
doc.delete # Removes the document from storage
Returns the generation number extracted from the object's ID.
For IDs without a generation suffix, returns 0.
obj.sepia_id = "note-123.2"
obj.generation # => 2
obj.sepia_id = "legacy-note"
obj.generation # => 0
Saves the object to storage.
For Serializable objects, serializes the object using its to_sepia method.
For Container objects, creates a directory structure and saves all nested objects.
The optional path parameter specifies where to save the object. If not provided,
uses the canonical path based on the object's class and sepia_id.
doc = MyDocument.new("Hello")
doc.save # Saves to default location
# Save to specific path
doc.save("/custom/path")
Saves the object to storage with optional metadata.
This is a convenience method that delegates to the global Storage API. It automatically detects whether to create a new object or update an existing one.
Parameters
- metadata : Optional JSON-serializable metadata for event logging
Returns
The object itself (for method chaining)
Example
note = Note.new("Hello World")
note.sepia_id = "my-note"
# Simple save
note.save
# Save with metadata
note.save(metadata: {"user" => "alice"})
Saves the object with forced new generation.
This method always creates a new version regardless of whether the object already exists in storage. Equivalent to save_with_generation but follows the same method signature pattern as save().
Parameters
- force_new_generation : Always increment generation (set to true)
- metadata : Optional JSON-serializable metadata for event logging
Returns
The object itself (for method chaining)
Example
note = Note.new("Hello World")
note.sepia_id = "my-note"
# Always creates new version
note.save(force_new_generation: true, metadata: {"user" => "alice"})
Creates a new version of this object with an incremented generation number.
Returns a new object instance with the same attributes but a new ID containing the next generation number. The original object is not modified.
obj.sepia_id = "note-123.2"
new_obj = obj.save_with_generation
new_obj.sepia_id # => "note-123.3"
NOTE This method requires the class to implement to_sepia and from_sepia
for Serializable objects. For Container objects, override this method.
Unique identifier for this object.
Defaults to a randomly generated UUIDv4 string. Can be manually set for specific use cases or when restoring objects with known IDs.
The ID format may include generation suffixes for version tracking:
- Without generation: "note-123e4567-e89b-12d3-a456-426614174000"
- With generation: "note-123e4567-e89b-12d3-a456-426614174000.1"
obj = MyClass.new
obj.sepia_id # => "myclass-uuid-string"
# Manually set ID
obj.sepia_id = "custom-id"
Sets the unique identifier for this object.
Use this when you need to control the object's ID, such as when restoring from external data or maintaining specific naming conventions.
obj = MyClass.new
obj.sepia_id = "document-2024-001"
Checks if a newer version of this object exists.
Returns true if an object with ID base_id.(expected_generation + 1) exists.
This is useful for optimistic concurrency control.
obj.sepia_id = "note-123.2"
obj.stale?(2) # => true if "note-123.3" exists