class PStore
end
end
puts wiki[key]
puts key
wiki.keys.each do |key|
wiki.transaction(true) do
# Read wiki data, setting argument read_only to true.<br><br>end<br>wiki.push(*home_page.wiki_page_references)
# Update wiki index.<br>wiki ||= Array.new
# Create page index.<br>wiki = home_page
# Store page.
wiki.transaction do
# Update page data and the index together, or not at all.
wiki = PStore.new(“wiki_pages.pstore”)
“A page about the JoysOfDocumentation…” )
home_page = WikiPage.new(“HomePage”, “James Edward Gray II”,
# Create a new wiki page.<br><br>end<br><br>end<br> + @revisions.last.scan(/b(?:[a-z]){2,}/)
def wiki_page_references
end
contents: contents}
author: author,
@revisions << {created: Time.now,
def add_revision(author, contents)
end
add_revision(author, contents)
@revisions = Array.new
@page_name = page_name
def initialize(page_name, author, contents)
attr_reader :page_name
class WikiPage
# A mock wiki object.
require “pstore”
== An Example Store
backup the PStore file from time to time.
Needless to say, if you’re storing valuable data with PStore, then you should
- Attribute #ultra_safe.
- Optional argument thread_safe at method PStore.new.
For transaction safety, see:
== Transaction Safety
store.path # => “t.store”
store = PStore.new(‘t.store’)
this method may be called from outside a transaction block:
Use method #path to retrieve the path to the store’s underlying file;
end
end
store.keys # => [:foo, :bar, :baz]
store.transaction do
example_store do |store|
Use method #keys to retrieve keys:
end
end
store.key?(:foo) # => true
store.transaction do
example_store do |store|
Use method #key? to determine whether a given key exists:
=== Querying the Store
end
end
store.fetch(:nope) # Raises exception.
store.fetch(:nope, nil) # => nil
store.fetch(:baz) # => 2<br>store # => nil<br>store # => 0
store.transaction do
example_store do |store|
to retrieve an entry:
Use method #fetch (allows default) or #[] (defaults to nil)
=== Retrieving Values<br><br>end<br>end<br>store # => nil
store.delete(:foo)
store.transaction do
example_store do |store|
Use method #delete to remove an entry:<br><br>end<br>end<br>store = 1 # Create.<br>store = 1 # Update.
store.transaction do
example_store do |store|
Use method #[]= to update or create an entry:
=== Modifying the Store
store = PStore.new(‘t.store’)
The new store creates or opens its containing file:
Use method PStore.new to create a store.
=== Creating a Store
== Working with the Store
in a returned hierarchy of objects.
And recall that you can use<br><br>end<br>deep_store # => {:foo=>, :bar=>[], :baz=>[]}<br>deep_store[:foo].push(:bat)<br>deep_store # => {:foo=>[], :bar=>[], :baz=>[]}<br>deep_store = hash_of_arrays
hash_of_arrays = {foo: [], bar: [], baz: []}
# => [{}, {}, {}]
= array_of_hashes
array_of_hashes = [{}, {}, {}]
deep_store.transaction do
deep_store = PStore.new(‘deep.store’)
It may also be a hierarchy of objects nested to any depth:
The value for an entry may be a simple object (as seen above).
== Hierarchical Values
end
# Calls to #transaction, #[]=, and #delete are not allowed here.
# Read-only transaction:
store.transaction(true) do
only reading is allowed:
If argument read_only is passed as true,
end
# Any code except a call to #transaction is allowed here.
# Read-write transaction.
store.transaction do
the store:
By default, a transaction allows both reading from and writing to
=== Read-Only Transactions
end
end
store.keys # => [:foo, :bar, :baz]
# Update was not completed.
store.transaction do
end
fail ‘Cannot get here’<br>store.abort<br>store = 3
store.keys # => [:foo, :bar, :baz]
store.transaction do
example_store do |store|
- Method #abort discards the update to the store and exits the block:
end
end
store.keys # => [:foo, :bar, :baz, :bat]
# Update was completed.
store.transaction do
end
fail ‘Cannot get here’<br>store.commit<br>store = 3
store.keys # => [:foo, :bar, :baz]
store.transaction do
example_store do |store|
- Method #commit triggers the update to the store and exits the block:
The block may be exited early by calling method #commit or #abort.
when the block exits.
As seen above, changes in a transaction are made automatically
the store are allowed.
- Is the only context where methods that read from or write to
- May not contain a nested call to #transaction.
The transaction block:
is executed immediately, not deferred.
Other code in the block (including even calls to #path and PStore.new)
This maintains the integrity of the store.
either all transaction calls are executed, or none are.
and is executed atomically (all-or-nothing):
Execution of the transaction is deferred until the block exits,
end
end
store.keys # => [:foo, :bar, :baz, :bat]<br>store = 3
store.keys # => [:foo, :bar, :baz]
store.transaction do
example_store do |store|
#path, and Pstore.new):
(that is, all PStore methods except #transaction itself,
read from or write to the store
which consists of calls to PStore methods that
contains a transaction,
The block given with a call to method #transaction#
=== The Transaction Block
== Transactions
See Hierarchical Values.
those objects must also be Marshal-able.
including collections, to any depth;
That collection may in turn contain nested objects,
(e.g., an array, a hash, a set, a range, etc).
and in fact may be a collection
(see Marshal::dump)
- Value: the value may be any object that can be marshalled by Marshal
symbols or strings as keys.
You may find it convenient to keep it simple by using only
see Hash Keys.
- Key: as in a hash, the key can be (almost) any object;
Each entry has a key and a value, just as in a hash:
A store may have any number of entries.
== Entries
see Marshal::dump.
module Marshal, which means that certain objects cannot be added to the store;
The objects are stored and retrieved using
when the store is created (see PStore.new).
The contents of the store are maintained in a file whose path is specified
== The Store
end
end
yield store<br>end<br>store = 2<br>store = 1<br>store = 0
store.transaction do
# Populate the store.
store = PStore.new(file)
Tempfile.create do |file|
# Create the store in a temporary file.
def example_store
# Yield a pristine store for use in examples.
require ‘tempfile’
require ‘pstore’
its implementation:
is that it yields a fresh store with a known population of entries;
All we really need to know about example_store
end
# Example code using store goes here.
example_store do |store|
They can get a new (and populated) store by calling thus:
Examples on this page need a store that has known properties.
== About the Examples
to PStore#transaction.
a transaction is defined in the block given with a call
of prospective changes to the store;
- Transactions: each transaction is a collection
each entry is the key for a stored object.
- Entries: the store is hash-like;
- Store: a store is an instance of PStore.
There are three important concepts here (details at the links):
for example.
That carries the usual limitations. Proc objects cannot be marshalled,
Behind the scenes, Ruby objects are stored to the data store file with Marshal.
where some values were updated but others were not.
This can be used to ensure that the data store is not left in a transitory state,
The transactional behavior ensures that any changes succeed or fail together.
or even update data, as needed.
User code may later read values back from the data store
An object hierarchy may be just a single object.
into the data store by name (keys).
User code can store hierarchies of Ruby objects (values)
PStore implements a file based persistence mechanism based on a Hash.
def [](key)
See also {Hierarchical Values}[rdoc-ref:PStore@Hierarchical+Values].
Returns +nil+ if there is no such key.
end
end
store[:nope] # => nil
store[:foo] # => 0
store.transaction do
example_store do |store|
if not +nil+, the returned value is an object or a hierarchy of objects:
+nil+ otherwise;
Returns the value for the given +key+ if the key exists.
def [](key) in_transaction @table[key] end
def []=(key, value)
See also {Hierarchical Values}[rdoc-ref:PStore@Hierarchical+Values].
end
end
temp[:bat] = 3
temp.transaction do
example_store do |store|
Creates or replaces the value for the given +key+:
def []=(key, value) in_transaction_wr @table[key] = value end
def abort
{transaction block}[rdoc-ref:PStore@The+Transaction+Block].
specified in the
Exits the current transaction block, discarding any changes
def abort in_transaction @abort = true throw self end
def commit
{transaction block}[rdoc-ref:PStore@The+Transaction+Block].
specified in the
Exits the current transaction block, committing any changes
def commit in_transaction @abort = false throw self end
def delete(key)
Returns +nil+ if there is no such key.
end
end
store.delete(:bat)
store[:bat] = 3
store.transaction do
example_store do |store|
Removes and returns the value at +key+ if it exists:
def delete(key) in_transaction_wr @table.delete key end
def dump(table) # :nodoc:
to allow subclass overriding used in YAML::Store.
This method is just a wrapped around Marshal.dump
def dump(table) # :nodoc: Marshal::dump(table) end
def empty_marshal_checksum
def empty_marshal_checksum EMPTY_MARSHAL_CHECKSUM end
def empty_marshal_data
def empty_marshal_data EMPTY_MARSHAL_DATA end
def fetch(key, default=PStore::Error)
end
end
store.fetch(:nope) # Raises an exception.
store.fetch(:nope, nil) # => nil
store.transaction do
example_store do |store|
- Returns the value of +default+ otherwise:
- Raises an exception if +default+ is +PStore::Error+.
If the +key+ does not exist:
Like #[], except that it accepts a default value for the store.
def fetch(key, default=PStore::Error) in_transaction unless @table.key? key if PStore::Error.equal?(default) raise PStore::Error, format("undefined key '%s'", key) else return default end end @table[key] end
def in_transaction
def in_transaction raise PStore::Error, "not in transaction" unless @lock.owned? end
def in_transaction_wr
if the code is in a read-only PStore#transaction.
Raises PStore::Error if the calling code is not in a PStore#transaction or
def in_transaction_wr in_transaction raise PStore::Error, "in read-only transaction" if @rdonly end
def initialize(file, _thread_safe = false, thread_safe: _thread_safe,
store = PStore.new(path, follow_symlink: false)
not affected.
that do not support IO::NOFOLLOW. Symbolic links in parent directories are
itself must not be a symbolic link. This option has no effect on platforms
If keyword argument +follow_symlink+ is given as +false+, the store file
store = PStore.new(path, ultra_safe: true)
set to +ultra_safe+ mode.
If keyword argument +ultra_safe+ is given as +true+, the object is
store = PStore.new(path, thread_safe: true)
store = PStore.new(path, true)
the object is also thread-safe (at the cost of a small performance penalty):
If argument or keyword argument +thread_safe+ is given as +true+,
{reentrant}[https://en.wikipedia.org/wiki/Reentrancy_(computing)].
A \PStore object is
store = PStore.new(path)
path = 't.store'
if the file exists, it should be one that was written by \PStore.
Argument +file+ is the path to the file in which objects are to be stored;
Returns a new \PStore object.
def initialize(file, _thread_safe = false, thread_safe: _thread_safe, ultra_safe: false, follow_symlink: true) dir = File::dirname(file) unless File::directory? dir raise PStore::Error, format("directory %s does not exist", dir) end if File::exist? file and not File::readable? file raise PStore::Error, format("file %s not readable", file) end @filename = File.path(file) @abort = false @ultra_safe = ultra_safe @thread_safe = thread_safe @nofollow = follow_symlink ? 0 : NOFOLLOW @lock = Thread::Mutex.new end
def key?(key)
end
end
store.key?(:foo) # => true
store.transaction do
example_store do |store|
Returns +true+ if +key+ exists, +false+ otherwise:
def key?(key) in_transaction @table.key? key end
def keys
end
end
store.keys # => [:foo, :bar, :baz]
store.transaction do
example_store do |store|
Returns an array of the existing keys:
def keys in_transaction @table.keys end
def load(content) # :nodoc:
to allow subclass overriding used in YAML::Store.
This method is just a wrapped around Marshal.load.
def load(content) # :nodoc: Marshal::load(content) end
def load_data(file, read_only)
If +read_only+ is false, a 3-tuple will be returned: the unmarshalled
If +read_only+ is true, the unmarshalled Hash will be returned.
Load the given PStore file.
def load_data(file, read_only) if read_only begin table = load(file) raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash) rescue EOFError raise unless file.size == 0 # This seems to be a newly-created file. table = {} end table else data = file.read if data.empty? # This seems to be a newly-created file. table = {} checksum = empty_marshal_checksum size = empty_marshal_data.bytesize else table = load(data) checksum = CHECKSUM_ALGO.digest(data) size = data.bytesize raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash) end data.replace(EMPTY_STRING) [table, checksum, size] end end
def open_and_lock_file(filename, read_only)
All exceptions are propagated.
and the file does not exist, then nil will be returned.
The opened File object will be returned. If _read_only_ is true,
read-write mode) and lock it for reading or writing.
Open the specified filename (either in read-only mode or in
def open_and_lock_file(filename, read_only) filename = File.path(filename) loop do if read_only begin file = File.new(filename, mode: RD_ACCESS | @nofollow, encoding: Encoding::ASCII_8BIT) rescue Errno::ENOENT return nil end else file = File.new(filename, mode: RDWR_ACCESS | @nofollow, encoding: Encoding::ASCII_8BIT) end current = false begin file.flock(read_only ? File::LOCK_SH : File::LOCK_EX) # An atomic save may have replaced the file while this lock was pending. current = File.identical?(file, filename) return file if current ensure file.close unless current end end end
def path
store.path # => "flat.store"
Returns the string file path used to create the store:
def path @filename end
def save_data(original_checksum, original_file_size, file)
def save_data(original_checksum, original_file_size, file) new_data = dump(@table) if new_data.bytesize != original_file_size || CHECKSUM_ALGO.digest(new_data) != original_checksum if @ultra_safe && !ON_WINDOWS # Once a file is locked, Windows does not guarantee that the # lock will be released until the file is closed. save_data_with_atomic_file_rename_strategy(new_data, file) else save_data_with_fast_strategy(new_data, file) end end new_data.replace(EMPTY_STRING) end
def save_data_with_atomic_file_rename_strategy(data, file)
def save_data_with_atomic_file_rename_strategy(data, file) temp_filename = "#{@filename}.tmp.#{Process.pid}.#{rand 1000000}" temp_file = File.new(temp_filename, mode: WR_ACCESS | @nofollow, encoding: Encoding::ASCII_8BIT, perm: 0o000) begin temp_file.flock(File::LOCK_EX) temp_file.write(data) temp_file.flush temp_file.chmod(file.stat.mode) File.rename(temp_filename, @filename) rescue Exception File.unlink(temp_file) rescue nil raise ensure temp_file.close end end
def save_data_with_fast_strategy(data, file)
def save_data_with_fast_strategy(data, file) file.rewind file.write(data) file.truncate(data.bytesize) end
def transaction(read_only = false) # :yields: pstore
Raises an exception if called within a transaction block.
to #transaction, #[]=, or #delete.
With argument +read_only+ as +true+, the block may not include calls
and write to the store.
With argument +read_only+ as +false+, the block may both read from
See {Transactions}[rdoc-ref:PStore@Transactions].
Opens a transaction block for the store.
def transaction(read_only = false) # :yields: pstore value = nil if !@thread_safe raise PStore::Error, "nested transaction" unless @lock.try_lock else begin @lock.lock rescue ThreadError raise PStore::Error, "nested transaction" end end begin @rdonly = read_only @abort = false file = open_and_lock_file(@filename, read_only) if file begin @table, checksum, original_data_size = load_data(file, read_only) catch(self) do value = yield(self) end if !@abort && !read_only save_data(checksum, original_data_size, file) end ensure file.close end else # This can only occur if read_only == true. @table = {} catch(self) do value = yield(self) end end ensure @lock.unlock end value end