summaryrefslogtreecommitdiff
path: root/lib/pstore.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pstore.rb')
-rw-r--r--lib/pstore.rb222
1 files changed, 208 insertions, 14 deletions
diff --git a/lib/pstore.rb b/lib/pstore.rb
index 51cef6e..3f60b59 100644
--- a/lib/pstore.rb
+++ b/lib/pstore.rb
@@ -1,24 +1,91 @@
+# = PStore -- Transactional File Storage for Ruby Objects
#
-# How to use:
+# pstore.rb -
+# by unknown
+# documentation by Kev Jackson and James Edward Gray II
#
-# db = PStore.new("/tmp/foo")
-# db.transaction do
-# p db.roots
-# ary = db["root"] = [1,2,3,4]
-# ary[0] = [1,1.5]
-# end
+# See PStore for documentation.
-# db.transaction do
-# p db["root"]
-# end
require "fileutils"
require "digest/md5"
+#
+# PStore implements a file based persistance mechanism based on a Hash. User
+# code can store hierarchies of Ruby objects (values) into the data store file
+# 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 upated 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.
+#
+# == Usage example:
+#
+# require "pstore"
+#
+# # a mock wiki object...
+# class WikiPage
+# def initialize( page_name, author, contents )
+# @page_name = page_name
+# @revisions = Array.new
+#
+# add_revision(author, contents)
+# end
+#
+# attr_reader :page_name
+#
+# 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 page...
+# home_page = WikiPage.new( "HomePage", "James Edward Gray II",
+# "A page about the JoysOfDocumentation..." )
+#
+# # then we want to update page data and the index together, or not at all...
+# wiki = PStore.new("wiki_pages.pstore")
+# wiki.transaction do # begin transaction; do all of this or none of it
+# # store page...
+# wiki[home_page.page_name] = home_page
+# # ensure that an index has been created...
+# wiki[:wiki_index] ||= Array.new
+# # update wiki index...
+# wiki[:wiki_index].push(*home_page.wiki_page_references)
+# end # commit changes to wiki data store file
+#
+# ### Some time later... ###
+#
+# # read wiki data...
+# wiki.transaction(true) do # begin read-only transaction, no changes allowed
+# wiki.roots.each do |data_root_name|
+# p data_root_name
+# p wiki[data_root_name]
+# end
+# end
+#
class PStore
+ # The error type thrown by all PStore methods.
class Error < StandardError
end
+ #
+ # To construct a PStore object, pass in the _file_ path where you would like
+ # the data to be stored.
+ #
def initialize(file)
dir = File::dirname(file)
unless File::directory? dir
@@ -32,19 +99,41 @@ class PStore
@abort = false
end
+ # Raises PStore::Error if the calling code is not in a PStore#transaction.
def in_transaction
raise PStore::Error, "not in transaction" unless @transaction
end
+ #
+ # Raises PStore::Error if the calling code is not in a PStore#transaction or
+ # if the code is in a read-only PStore#transaction.
+ #
def in_transaction_wr()
in_transaction()
raise PStore::Error, "in read-only transaction" if @rdonly
end
private :in_transaction, :in_transaction_wr
+ #
+ # Retrieves a value from the PStore file data, by _name_. The hierarchy of
+ # Ruby objects stored under that root _name_ will be returned.
+ #
+ # *WARNING*: This method is only valid in a PStore#transaction. It will
+ # raise PStore::Error if called at any other time.
+ #
def [](name)
in_transaction
@table[name]
end
+ #
+ # This method is just like PStore#[], save that you may also provide a
+ # _default_ value for the object. In the event the specified _name_ is not
+ # found in the data store, your _default_ will be returned instead. If you do
+ # not specify a default, PStore::Error will be raised if the object is not
+ # found.
+ #
+ # *WARNING*: This method is only valid in a PStore#transaction. It will
+ # raise PStore::Error if called at any other time.
+ #
def fetch(name, default=PStore::Error)
unless @table.key? name
if default==PStore::Error
@@ -55,39 +144,138 @@ class PStore
end
self[name]
end
+ #
+ # Stores an individual Ruby object or a hierarchy of Ruby objects in the data
+ # store file under the root _name_. Assigning to a _name_ already in the data
+ # store clobbers the old data.
+ #
+ # == Example:
+ #
+ # require "pstore"
+ #
+ # store = PStore.new("data_file.pstore")
+ # store.transaction do # begin transaction
+ # # load some data into the store...
+ # store[:single_object] = "My data..."
+ # store[:obj_heirarchy] = { "Kev Jackson" => ["rational.rb", "pstore.rb"],
+ # "James Gray" => ["erb.rb", "pstore.rb"] }
+ # end # commit changes to data store file
+ #
+ # *WARNING*: This method is only valid in a PStore#transaction and it cannot
+ # be read-only. It will raise PStore::Error if called at any other time.
+ #
def []=(name, value)
in_transaction_wr()
@table[name] = value
end
+ #
+ # Removes an object hierarchy from the data store, by _name_.
+ #
+ # *WARNING*: This method is only valid in a PStore#transaction and it cannot
+ # be read-only. It will raise PStore::Error if called at any other time.
+ #
def delete(name)
in_transaction_wr()
@table.delete name
end
+ #
+ # Returns the names of all object hierarchies currently in the store.
+ #
+ # *WARNING*: This method is only valid in a PStore#transaction. It will
+ # raise PStore::Error if called at any other time.
+ #
def roots
in_transaction
@table.keys
end
+ #
+ # Returns true if the supplied _name_ is currently in the data store.
+ #
+ # *WARNING*: This method is only valid in a PStore#transaction. It will
+ # raise PStore::Error if called at any other time.
+ #
def root?(name)
in_transaction
@table.key? name
end
+ # Returns the path to the data store file.
def path
@filename
end
+ #
+ # Ends the current PStore#transaction, committing any changes to the data
+ # store immediately.
+ #
+ # == Example:
+ #
+ # require "pstore"
+ #
+ # store = PStore.new("data_file.pstore")
+ # store.transaction do # begin transaction
+ # # load some data into the store...
+ # store[:one] = 1
+ # store[:two] = 2
+ #
+ # store.commit # end transaction here, committing changes
+ #
+ # store[:three] = 3 # this change is never reached
+ # end
+ #
+ # *WARNING*: This method is only valid in a PStore#transaction. It will
+ # raise PStore::Error if called at any other time.
+ #
def commit
in_transaction
@abort = false
throw :pstore_abort_transaction
end
+ #
+ # Ends the current PStore#transaction, discarding any changes to the data
+ # store.
+ #
+ # == Example:
+ #
+ # require "pstore"
+ #
+ # store = PStore.new("data_file.pstore")
+ # store.transaction do # begin transaction
+ # store[:one] = 1 # this change is not applied, see below...
+ # store[:two] = 2 # this change is not applied, see below...
+ #
+ # store.abort # end transaction here, discard all changes
+ #
+ # store[:three] = 3 # this change is never reached
+ # end
+ #
+ # *WARNING*: This method is only valid in a PStore#transaction. It will
+ # raise PStore::Error if called at any other time.
+ #
def abort
in_transaction
@abort = true
throw :pstore_abort_transaction
end
- def transaction(read_only=false)
+ #
+ # Opens a new transaction for the data store. Code executed inside a block
+ # passed to this method may read and write data to and from the data store
+ # file.
+ #
+ # At the end of the block, changes are committed to the data store
+ # automatically. You may exit the transaction early with a call to either
+ # PStore#commit or PStore#abort. See those methods for details about how
+ # changes are handled. Raising an uncaught Exception in the block is
+ # equivalent to calling PStore#abort.
+ #
+ # If _read_only_ is set to +true+, you will only be allowed to read from the
+ # data store during the transaction and any attempts to change the data will
+ # raise a PStore::Error.
+ #
+ # Note that PStore does not support nested transactions.
+ #
+ def transaction(read_only=false) # :yields: pstore
raise PStore::Error, "nested transaction" if @transaction
begin
@rdonly = read_only
@@ -155,19 +343,23 @@ class PStore
value
end
- def dump(table)
+ # This method is just a wrapped around Marshal.dump.
+ def dump(table) # :nodoc:
Marshal::dump(table)
end
- def load(content)
+ # This method is just a wrapped around Marshal.load.
+ def load(content) # :nodoc:
Marshal::load(content)
end
- def load_file(file)
+ # This method is just a wrapped around Marshal.load.
+ def load_file(file) # :nodoc:
Marshal::load(file)
end
private
+ # Commits changes to the data store file.
def commit_new(f)
f.truncate(0)
f.rewind
@@ -180,6 +372,8 @@ class PStore
end
end
+# :enddoc:
+
if __FILE__ == $0
db = PStore.new("/tmp/foo")
db.transaction do