intro.txt

Path: intro.txt
Last Update: Mon May 29 14:43:11 PDT 2006

Preferences

The Preferences class is an easy way to make variables in an application persist in a file. The file is human readable, through the magic of YAML. Preferences vary from user to user, so typically the file used for persistence will be chosen based on the user’s environment as well as the name of the app. Any pair of methods that look like a reader/writer pair can be persisted, so existing variables can be stored with little additional fuss and bother.

Synopsis

  require 'preferences'

  PREFS = Preferences.new("/tmp/prefs")

  class Window
    attr_accessor :x, :y, :z
    def initialize
      PREFS.register "my app/window" do |entry|
        entry.var "x", "y" => "default_y"
        entry.var "z"
      end
    end

    def run
      @x = 7 # emulate user's interaction
    end
  end

  Window.new.run
  PREFS.save

After executing this code, /tmp/prefs contains:

  my app:
    window:
      x: 7
      y: default_y
      z:

When the program is run again, these saved preferences will be loaded into the variables when the PREFS.register method is called.

How it works

Each object with persistent variables registers (typically in its initialize method) to have its variables stored in the file. The structure of the preferences file is nested hashes. Each object registers to have its data stored in a hash nested at a certain key in this structure. The key is usually a path string of the form "key1/key2/…". The components of this string are used in sequence to access the nested hash which stores the data for the object. For example, examples/foursplit-prefs.rb generates the following preferences file:

  ---
  main window:
    x: 200
    y: 100
    splitter:
      hSplit: 1219
      expanded: -1
      subsplitter:
        hSplit: 1641
        expanded: -1
        vSplit: 2307
      vSplit: 1491
    height: 600
    width: 800

The key "main window/splitter/subsplitter" accesses the hash of persistent values of variables belonging to the subsplitter. This hash specifies values for the three variables, vSplit hSplit and expanded.

Note that there are two kinds of keys involved: keys like "splitter", which define a path through the nesting of hashes, and keys like "x", which specify variables. The former are branching nodes and the latter are leaf nodes in the preferences tree. An optional convention is to use Strings for the former and Symbols for the latter, but this is not necessary and can make the file a little harder to read.

When you call Preferences#register, the key does not have to be a literal string, but can of course be different with each call to initialize. This allows different instances of the same class (distinguished perhaps by their arguments or order of creation) to have distinct persistent data.

Each object can register only one key for a particular preferences file. However, two or more objects may register the same key.

Registering variables does not define them in the program. They must already exist, either defined by means of attr_accessor or defined as methods. Registering existing methods can be very useful with a library like FXRuby. The foursplit-prefs.rb example shows how to apply Preferences to a FXRuby app.

When registering a variable, the object may also register a default value for it.

A class may have its own persistent preferences. See examples/class-prefs.rb.

Choosing a preferences directory and file

The preferences dir should depend on the user, on the app, and on the platform. The Preferences class provides a convenience method, Preferences.dir, to select a suitable user-specific and platform-specific dir in which the app can create its app-specific dir and/or file.

Overriding the persistence methods

Preferences uses two undocumented methods, read_pref_file and write_pref_file, to save and restore preferences from a file. The default implementations use FileUtils.mkdir_p, File.open, YAML.dump, and YAML.load. However, an alternate implementation might use the Windows registry, or a database. Simply subclass Preferences and implement these two methods as desired.

Concurrency issues

The Preferences class makes no effort to reconcile the preferences written with the file as it exists on disk. If the file on disk has changed between the time preferences were loaded and the time preferences were saved, those changes will be lost.

Legal and Contact Information

Usable under the Ruby license. Copyright (C)2004-2006 Joel VanderWerf. Questions to vjoel@users.sourceforge.net.

[Validate]