PStore, a little known feature in the standard library

PStore(persistent store) implements a file based persistence mechanism based on a Hash. It writes Ruby objects to an external file so it can access easily if needed. If an I/O error occurs while PStore is writing to its file, then the file will become corrupted.You can prevent this by setting pstore.ultra_safe = true. Also, it supports thread-safe and uses Marshal internally. To use this library, you must require it and instantiate a new object.

require 'pstore'
store = PStore.new("filename.pstore")
Which would create a file that stores the content to be written. To store or retrieve data from the data store, you must open a transaction. Here transaction is a protective wrapper around SQL statements to ensure changes to the database only occur when all actions succeed together. We can access the content of database only through this transaction.
store.transaction do
  #read and write transactions.
end
At the end of the transaction, all changes are committed.

Public Instance methods

Instance methods are methods that are called on an instance of a class. We can use the below methods while using PStore instances.
  • p[name]=obj

Stores obj in the database under the key name. When the transaction is completed, all objects accessed reflexively by obj  are saved in a file.

  • p.root?(name)

Returns true if the key name exists in the database.

  • p.commit

Complete the transaction. When this method is called, the block passed to the transaction method is executed, and changes to the database are written to the database file.

  • p.abort

Aborts the transaction. When this method is called, the execution of the block passed to the transaction method is terminated, and changes made to database objects during the transaction aren’t written to the database file.

Let’s walk through a simple example. Below shows storing employee data into a simple PStore database. The file looks like: employee.rb
require 'pstore'
store = PStore.new("employee.pstore")
store.transaction do
  store["params"] = {"name" => "Abc", "age" => 22, "salary" => 20000 }
  #commit all changes
  store.commit
  #retrieve data
  emp = nil store.transaction { emp = store["params"] }
  # Delete value
  store.delete(:age)
  # View all key names:
  store.roots
  # => :name, :salary
  # abort()ing transactions will halt execution and revert all changes.
  store.abort
end
In the above example, to use the library we require it at the beginning and then create a new instance for PStore called ‘store’. Next, we have to open a transaction to store and retrieve data. In that, we are storing name, age and salary of an employee into PStore by using the command,
store["params"] = {"name" => "Abc", "age" => 22, "salary" => 20000 }
Also, you can commit all the changes that you have done using the commit() method. There are methods like delete(), roots and abort() to delete the value, view all key names and revert all changes respectively.

Features of PStore

  • It is stored in the persistent storage media(hard disk drives), not in volatile memory (RAM).
  • The content of the data file is binary so that it can’t be edited directly with an editor.
  • There exists a backup file so we can use that if the data file is corrupted.
  • You can save anything you can do with Marshal.dump and you can load whatever you can do with Marshal.load.
  • In PStore, all interactions happen within a transaction. So that two separate processes that are both accessing the same store will not have collisions with any modifications. Only a single transaction can be committed at any one time.
The main advantage of PStore is that it is very fast and space-efficient because it uses marshelling. But the disadvantage is that the created file is not human-readable.This is where YAML::Store steps in! It provides the same functionality as PStore, except it uses YAML to dump objects instead of Marshal. This may not be as fast as Marshal, but the result is human-readable and human-writable. To use YAML::Store instead of PStore, simply replace the first lines of the code above with:
require 'yaml/store'
store = YAML::Store.new('store.yml')
After that, the YAML::Store works exactly like the PStore. You can use PStore if it’s more important to you that reading and writing the data happens as fast as possible or if you care about the size of the created file. Otherwise, you can choose YAML::Store that provides human readability. Also, there is another method for file storage that is SDBM. It can only store String keys and values. You can use SDBM as:
require 'sdbm'
SDBM.open 'my_database' do |db|
  db['foo'] = 'a string'
  #Add / Update Many at Once
  db.update({foo: 'something', bar: 1})
  # Get all Values
  db.each do |k,v|
    puts "Key: #{k}, Value: #{v}"
  end
  # Retrieve Specific Value
  puts db['foo']
end
So, using this we can perform the file storage which is basically Pstore without transaction feature. Happy Coding!

References

]]>