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)

Raises an exception if called outside a transaction block.

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)

Raises an exception if called outside a transaction block.

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

Raises an exception if called outside a transaction block.

{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

Raises an exception if called outside a transaction block.

{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)

Raises an exception if called outside a transaction block.

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:

: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)

Raises an exception if called outside a transaction block.

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

Raises PStore::Error if the calling code is not in a PStore#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)

Raises an exception if called outside a transaction block.

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

Raises an exception if called outside a transaction block.

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:

: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)

Hash, a checksum of the data, and the size of the data.
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

: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