Class: PStore

Class | PStore
In: | pstore.rb
Parent: | Object

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

### Methods

[] []= abort commit delete fetch new path root? roots transaction

### Classes and Modules

Class PStore::Error

### Public Class methods

new(file)

To construct a PStore object, pass in the file path where you would like the data to be stored.

[Source]

    # File pstore.rb, line 89
      def initialize(file)
        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
        @transaction = false
        @filename = file
        @abort = false
      end

### Public Instance methods

[](name)

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.

[Source]

    # File pstore.rb, line 123
      def [](name)
        in_transaction
        @table[name]
      end

[]=(name, value)

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.

[Source]

    # File pstore.rb, line 167
      def []=(name, value)
        in_transaction_wr()
        @table[name] = value
      end

abort()

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.

[Source]

    # File pstore.rb, line 255
      def abort
        in_transaction
        @abort = true
        throw :pstore_abort_transaction
      end

commit()

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.

[Source]

    # File pstore.rb, line 229
      def commit
        in_transaction
        @abort = false
        throw :pstore_abort_transaction
      end

delete(name)

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.

[Source]

    # File pstore.rb, line 177
      def delete(name)
        in_transaction_wr()
        @table.delete name
      end

fetch(name, default=PStore::Error)

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.

[Source]

    # File pstore.rb, line 137
      def fetch(name, default=PStore::Error)
        unless @table.key? name
          if default==PStore::Error
            raise PStore::Error, format("undefined root name `%s'", name)
          else
            default
          end
        end
        self[name]
      end

path()

Returns the path to the data store file.

[Source]

    # File pstore.rb, line 203
      def path
        @filename
      end

root?(name)

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.

[Source]

    # File pstore.rb, line 198
      def root?(name)
        in_transaction
        @table.key? name
      end

roots()

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.

[Source]

    # File pstore.rb, line 188
      def roots
        in_transaction
        @table.keys
      end

transaction(read_only=false) {|pstore| ...}

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.

[Source]

    # File pstore.rb, line 278
      def transaction(read_only=false)  # :yields:  pstore
        raise PStore::Error, "nested transaction" if @transaction
        begin
          @rdonly = read_only
          @abort = false
          @transaction = true
          value = nil
          new_file = @filename + ".new"

          content = nil
          unless read_only
            file = File.open(@filename, File::RDWR | File::CREAT)
            file.binmode
            file.flock(File::LOCK_EX)
            commit_new(file) if FileTest.exist?(new_file)
            content = file.read()
          else
            begin
              file = File.open(@filename, File::RDONLY)
              file.binmode
              file.flock(File::LOCK_SH)
              content = (File.read(new_file) rescue file.read())
            rescue Errno::ENOENT
              content = ""
            end
          end

          if content != ""
            @table = load(content)
            if !read_only
              size = content.size
              md5 = Digest::MD5.digest(content)
            end
          else
            @table = {}
          end
          content = nil             # unreference huge data

          begin
            catch(:pstore_abort_transaction) do
              value = yield(self)
            end
          rescue Exception
            @abort = true
            raise
          ensure
            if !read_only and !@abort
              tmp_file = @filename + ".tmp"
              content = dump(@table)
              if !md5 || size != content.size || md5 != Digest::MD5.digest(content)
                File.open(tmp_file, "w") {|t|
                  t.binmode
                  t.write(content)
                }
                File.rename(tmp_file, new_file)
                commit_new(file)
              end
              content = nil         # unreference huge data
            end
          end
        ensure
          @table = nil
          @transaction = false
          file.close if file
        end
        value
      end

[Validate]