class PStore
PStore implements a file based persistence mechanism based on a Hash. User code can store hierarchies of Ruby objects (values) into the data store by name (keys). An object hierarchy may be just a single object. User code may later read values back from the data store or even update data, as needed.
The transactional behavior ensures that any changes succeed or fail together. This can be used to ensure that the data store is not left in a transitory state, where some values were updated but others were not.
Behind the scenes, Ruby objects are stored to the data store file with Marshal. That carries the usual limitations. Proc objects cannot be marshalled, for example.
There are three important concepts here (details at the links):
-
Entries: the store is hash-like; each entry is the key for a stored object.
-
Transactions: each transaction is a collection of prospective changes to the store; a transaction is defined in the block given with a call to
PStore#transaction.
About the Examples
Examples on this page need a store that has known properties. They can get a new (and populated) store by calling thus:
example_store do |store| # Example code using store goes here. end
All we really need to know about example_store is that it yields a fresh store with a known population of entries; its implementation:
require 'pstore' require 'tempfile' # Yield a pristine store for use in examples. def example_store # Create the store in a temporary file. Tempfile.create do |file| store = PStore.new(file) # Populate the store. store.transaction do store[:foo] = 0 store[:bar] = 1 store[:baz] = 2 end yield store end end
The Store
The contents of the store are maintained in a file whose path is specified when the store is created (see PStore.new). The objects are stored and retrieved using module Marshal, which means that certain objects cannot be added to the store; see Marshal::dump.
Entries
A store may have any number of entries. Each entry has a key and a value, just as in a hash:
-
Key: as in a hash, the key can be (almost) any object; see Hash Keys. You may find it convenient to keep it simple by using only symbols or strings as keys.
-
Value: the value may be any object that can be marshalled by
Marshal(see Marshal::dump) and in fact may be a collection (e.g., an array, a hash, a set, a range, etc). That collection may in turn contain nested objects, including collections, to any depth; those objects must also be Marshal-able. See Hierarchical Values.
Transactions
The Transaction Block
The block given with a call to method transaction# contains a transaction, which consists of calls to PStore methods that read from or write to the store (that is, all PStore methods except transaction itself, path, and Pstore.new):
example_store do |store| store.transaction do store.keys # => [:foo, :bar, :baz] store[:bat] = 3 store.keys # => [:foo, :bar, :baz, :bat] end end
Execution of the transaction is deferred until the block exits, and is executed atomically (all-or-nothing): either all transaction calls are executed, or none are. This maintains the integrity of the store.
Other code in the block (including even calls to path and PStore.new) is executed immediately, not deferred.
The transaction block:
-
May not contain a nested call to
transaction. -
Is the only context where methods that read from or write to the store are allowed.
As seen above, changes in a transaction are made automatically when the block exits. The block may be exited early by calling method commit or abort.
-
Methodcommittriggers the update to the store and exits the block:example_store do |store| store.transaction do store.keys # => [:foo, :bar, :baz] store[:bat] = 3 store.commit fail 'Cannot get here' end store.transaction do # Update was completed. store.keys # => [:foo, :bar, :baz, :bat] end end
-
Methodabortdiscards the update to the store and exits the block:example_store do |store| store.transaction do store.keys # => [:foo, :bar, :baz] store[:bat] = 3 store.abort fail 'Cannot get here' end store.transaction do # Update was not completed. store.keys # => [:foo, :bar, :baz] end end
Read-Only Transactions
By default, a transaction allows both reading from and writing to the store:
store.transaction do # Read-write transaction. # Any code except a call to #transaction is allowed here. end
If argument read_only is passed as true, only reading is allowed:
store.transaction(true) do # Read-only transaction: # Calls to #transaction, #[]=, and #delete are not allowed here. end
Hierarchical Values
The value for an entry may be a simple object (as seen above). It may also be a hierarchy of objects nested to any depth:
deep_store = PStore.new('deep.store') deep_store.transaction do array_of_hashes = [{}, {}, {}] deep_store[:array_of_hashes] = array_of_hashes deep_store[:array_of_hashes] # => [{}, {}, {}] hash_of_arrays = {foo: [], bar: [], baz: []} deep_store[:hash_of_arrays] = hash_of_arrays deep_store[:hash_of_arrays] # => {:foo=>[], :bar=>[], :baz=>[]} deep_store[:hash_of_arrays][:foo].push(:bat) deep_store[:hash_of_arrays] # => {:foo=>[:bat], :bar=>[], :baz=>[]} end
And recall that you can use dig methods in a returned hierarchy of objects.
Working with the Store
Creating a Store
Use method PStore.new to create a store. The new store creates or opens its containing file:
store = PStore.new('t.store')
Modifying the Store
Use method []= to update or create an entry:
example_store do |store| store.transaction do store[:foo] = 1 # Update. store[:bam] = 1 # Create. end end
Use method delete to remove an entry:
example_store do |store| store.transaction do store.delete(:foo) store[:foo] # => nil end end
Retrieving Values
Use method fetch (allows default) or [] (defaults to nil) to retrieve an entry:
example_store do |store| store.transaction do store[:foo] # => 0 store[:nope] # => nil store.fetch(:baz) # => 2 store.fetch(:nope, nil) # => nil store.fetch(:nope) # Raises exception. end end
Querying the Store
Use method key? to determine whether a given key exists:
example_store do |store| store.transaction do store.key?(:foo) # => true end end
Use method keys to retrieve keys:
example_store do |store| store.transaction do store.keys # => [:foo, :bar, :baz] end end
Use method path to retrieve the path to the store’s underlying file; this method may be called from outside a transaction block:
store = PStore.new('t.store') store.path # => "t.store"
Transaction Safety
For transaction safety, see:
-
Optional argument
thread_safeat methodPStore.new. -
Attribute
ultra_safe.
Needless to say, if you’re storing valuable data with PStore, then you should backup the PStore file from time to time.
An Example Store
require "pstore" # A mock wiki object. class WikiPage attr_reader :page_name def initialize(page_name, author, contents) @page_name = page_name @revisions = Array.new add_revision(author, contents) end def add_revision(author, contents) @revisions << {created: Time.now, author: author, contents: contents} end def wiki_page_references [@page_name] + @revisions.last[:contents].scan(/\b(?:[A-Z]+[a-z]+){2,}/) end end # Create a new wiki page. home_page = WikiPage.new("HomePage", "James Edward Gray II", "A page about the JoysOfDocumentation..." ) wiki = PStore.new("wiki_pages.pstore") # Update page data and the index together, or not at all. wiki.transaction do # Store page. wiki[home_page.page_name] = home_page # Create page index. wiki[:wiki_index] ||= Array.new # Update wiki index. wiki[:wiki_index].push(*home_page.wiki_page_references) end # Read wiki data, setting argument read_only to true. wiki.transaction(true) do wiki.keys.each do |key| puts key puts wiki[key] end end
Constants
- EMPTY_MARSHAL_CHECKSUM
- EMPTY_MARSHAL_DATA
- EMPTY_STRING
- RDWR_ACCESS
- RD_ACCESS
- VERSION
- WR_ACCESS
Public Class Methods
(path file, ?boolish thread_safe) → void
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 538
def initialize: (path file, ?boolish thread_safe) -> void
Returns a new PStore object.
Argument file is the path to the file in which objects are to be stored; if the file exists, it should be one that was written by PStore.
path = 't.store' store = PStore.new(path)
A PStore object is reentrant. If argument thread_safe is given as true, the object is also thread-safe (at the cost of a small performance penalty):
store = PStore.new(path, true)
Public Instance Methods
(K name) → V?
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 323
def []: (K name) -> V?
Returns the value for the given key if the key exists. nil otherwise; if not nil, the returned value is an object or a hierarchy of objects:
example_store do |store| store.transaction do store[:foo] # => 0 store[:nope] # => nil end end
Returns nil if there is no such key.
See also Hierarchical Values.
Raises an exception if called outside a transaction block.
(K name, V value) → V
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 341
def []=: (K name, V value) -> V
Creates or replaces the value for the given key:
example_store do |store| temp.transaction do temp[:bat] = 3 end end
See also Hierarchical Values.
Raises an exception if called outside a transaction block.
() → void
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 352
def abort: () -> void
Exits the current transaction block, discarding any changes specified in the transaction block.
Raises an exception if called outside a transaction block.
() → void
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 363
def commit: () -> void
Exits the current transaction block, committing any changes specified in the transaction block.
Raises an exception if called outside a transaction block.
(K name) → V?
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 382
def delete: (K name) -> V?
Removes and returns the value at key if it exists:
example_store do |store| store.transaction do store[:bat] = 3 store.delete(:bat) end end
Returns nil if there is no such key.
Raises an exception if called outside a transaction block.
(untyped table) → untyped
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 486
def dump: (untyped table) -> untyped
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 493
def empty_marshal_checksum: () -> String
() → String
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 500
def empty_marshal_data: () -> String
(K name, ?V default) → V
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 403
def fetch: (K name, ?V default) -> V
Like [], except that it accepts a default value for the store. If the key does not exist:
-
Raises an exception if
defaultisPStore::Error. -
Returns the value of
defaultotherwise:example_store do |store| store.transaction do store.fetch(:nope, nil) # => nil store.fetch(:nope) # Raises an exception. end end
Raises an exception if called outside a transaction block.
() → void
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 508
def in_transaction: () -> void
Raises PStore::Error if the calling code is not in a PStore#transaction.
() → void
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 517
def in_transaction_wr: () -> void
Raises PStore::Error if the calling code is not in a PStore#transaction or if the code is in a read-only PStore#transaction.
(K name) → bool
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 420
def key?: (K name) -> bool
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 429
def keys: () -> Array[K]
(untyped content) → untyped
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 540
def load: (untyped content) -> untyped
(path file, true read_only) → Hash[untyped, untyped]
(path file, false read_only) → [ Hash[untyped, untyped], String, Integer ]
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 550
def load_data: (path file, true read_only) -> Hash[untyped, untyped]
| (path file, false read_only) -> [Hash[untyped, untyped], String, Integer]
() → bool
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 558
def on_windows?: () -> bool
(string filename, bool read_only) → File?
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 572
def open_and_lock_file: (string filename, bool read_only) -> File?
Open the specified filename (either in read-only mode or in read-write mode) and lock it for reading or writing.
The opened File object will be returned. If read_only is true, and the file does not exist, then nil will be returned.
All exceptions are propagated.
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 413
def path: () -> String
Returns the string file path used to create the store:
store.path # => "flat.store"
(untyped original_checksum, untyped original_file_size, untyped file) → untyped
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 579
def save_data: (untyped original_checksum, untyped original_file_size, untyped file) -> untyped
(string data, File file) → void
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 586
def save_data_with_atomic_file_rename_strategy: (string data, File file) -> void
(string data, File file) → void
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 593
def save_data_with_fast_strategy: (string data, File file) -> void
[U] (?bool read_only) { (self) → U } → U
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 448
def transaction: [U] (?bool read_only) { (self) -> U } -> U
Opens a transaction block for the store. See Transactions.
With argument read_only as false, the block may both read from and write to the store.
With argument read_only as true, the block may not include calls to
transaction, []=, or delete.
Raises an exception if called within a transaction block.
() → bool
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 465
def ultra_safe: () -> bool
Whether PStore should do its best to prevent file corruptions, even when an unlikely error (such as memory-error or filesystem error) occurs:
-
true: changes are posted by creating a temporary file, writing the updated data to it, then renaming the file to the givenpath.Fileintegrity is maintained. Note: has effect only if the filesystem has atomic file rename (as do POSIX platforms Linux, MacOS, FreeBSD and others). -
false(the default): changes are posted by rewinding the open file and writing the updated data.Fileintegrity is maintained if the filesystem raises no unexpected I/O error; if such an error occurs during a write to the store, the file may become corrupted.
(bool) → bool
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/stdlib/pstore/0/pstore.rbs, line 482
def ultra_safe=: (bool) -> bool
Whether PStore should do its best to prevent file corruptions, even when an unlikely error (such as memory-error or filesystem error) occurs:
-
true: changes are posted by creating a temporary file, writing the updated data to it, then renaming the file to the givenpath.Fileintegrity is maintained. Note: has effect only if the filesystem has atomic file rename (as do POSIX platforms Linux, MacOS, FreeBSD and others). -
false(the default): changes are posted by rewinding the open file and writing the updated data.Fileintegrity is maintained if the filesystem raises no unexpected I/O error; if such an error occurs during a write to the store, the file may become corrupted.