class PStore

PStore 基于 Hash 实现了一个基于文件的持久化机制。用户代码可以通过名称(键)将 Ruby 对象(值)的层次结构存储到数据存储中。对象层次结构可以只是一个对象。用户代码稍后可以从数据存储中读取值,甚至根据需要更新数据。

事务行为确保任何更改都会一起成功或失败。这可以用来确保数据存储不会处于过渡状态,其中一些值被更新了而其他值没有。

在后台,Ruby 对象使用 Marshal 存储到数据存储文件中。这带来了通常的限制。例如,Proc 对象不能被序列化。

这里有三个重要的概念(详情请见链接)

关于示例

本页上的示例需要一个具有已知属性的存储。它们可以通过调用以下方式获取新的(和填充的)存储

example_store do |store|
  # Example code using store goes here.
end

我们真正需要了解的关于 example_store 的是,它会产生一个具有已知条目填充的新存储;它的实现

require 'pstore'
require 'tempfile'
# Yield a pristine store for use in examples.
def example_store
  # Create the store in a temporary file.
  Tempfile.create do |file|
    store = PStore.new(file)
    # Populate the store.
    store.transaction do
      store[:foo] = 0
      store[:bar] = 1
      store[:baz] = 2
    end
    yield store
  end
end

存储

存储的内容保存在一个文件中,该文件的路径在创建存储时指定(请参阅 PStore.new)。使用模块 Marshal 存储和检索对象,这意味着某些对象无法添加到存储中;请参阅 Marshal::dump

条目

一个存储可以有任意数量的条目。每个条目都有一个键和一个值,就像在哈希中一样

事务

事务块

调用方法 transaction 时给定的块包含一个事务,该事务由调用 PStore 方法组成,这些方法从存储中读取或写入存储(即,除了 transaction 本身、path 和 Pstore.new 之外的所有 PStore 方法)

example_store do |store|
  store.transaction do
    store.keys # => [:foo, :bar, :baz]
    store[:bat] = 3
    store.keys # => [:foo, :bar, :baz, :bat]
  end
end

事务的执行被延迟到块退出时,并且是原子执行的(要么全部执行,要么全部不执行):要么所有事务调用都执行,要么都不执行。这维护了存储的完整性。

块中的其他代码(甚至包括对 pathPStore.new 的调用)是立即执行的,而不是延迟执行的。

事务块

如上所述,事务中的更改会在块退出时自动进行。可以通过调用方法 commitabort 来提前退出块。

只读事务

默认情况下,事务允许从存储中读取和写入存储

store.transaction do
  # Read-write transaction.
  # Any code except a call to #transaction is allowed here.
end

如果参数 read_only 作为 true 传递,则只允许读取

store.transaction(true) do
  # Read-only transaction:
  # Calls to #transaction, #[]=, and #delete are not allowed here.
end

分层值

条目的值可以是一个简单对象(如上所示)。它也可以是嵌套到任意深度的对象层次结构

deep_store = PStore.new('deep.store')
deep_store.transaction do
  array_of_hashes = [{}, {}, {}]
  deep_store[:array_of_hashes] = array_of_hashes
  deep_store[:array_of_hashes] # => [{}, {}, {}]
  hash_of_arrays = {foo: [], bar: [], baz: []}
  deep_store[:hash_of_arrays] = hash_of_arrays
  deep_store[:hash_of_arrays]  # => {:foo=>[], :bar=>[], :baz=>[]}
  deep_store[:hash_of_arrays][:foo].push(:bat)
  deep_store[:hash_of_arrays]  # => {:foo=>[:bat], :bar=>[], :baz=>[]}
end

请记住,您可以在返回的对象层次结构中使用 dig 方法

使用存储

创建存储

使用方法 PStore.new 创建存储。新的存储会创建或打开其包含文件

store = PStore.new('t.store')

修改存储

使用方法 []= 来更新或创建条目

example_store do |store|
  store.transaction do
    store[:foo] = 1 # Update.
    store[:bam] = 1 # Create.
  end
end

使用方法 delete 来删除条目

example_store do |store|
  store.transaction do
    store.delete(:foo)
    store[:foo] # => nil
  end
end

检索值

使用方法 fetch (允许默认值)或 [](默认为 nil)来检索条目

example_store do |store|
  store.transaction do
    store[:foo]             # => 0
    store[:nope]            # => nil
    store.fetch(:baz)       # => 2
    store.fetch(:nope, nil) # => nil
    store.fetch(:nope)      # Raises exception.
  end
end

查询存储

使用方法 key? 来确定给定的键是否存在

example_store do |store|
  store.transaction do
    store.key?(:foo) # => true
  end
end

使用方法 keys 来检索键

example_store do |store|
  store.transaction do
    store.keys # => [:foo, :bar, :baz]
  end
end

使用方法 path 来检索存储的基础文件的路径;此方法可以在事务块之外调用

store = PStore.new('t.store')
store.path # => "t.store"

事务安全性

有关事务安全性,请参阅

不用说,如果您使用 PStore 存储有价值的数据,那么您应该不时备份 PStore 文件。

示例存储

require "pstore"

# A mock wiki object.
class WikiPage

  attr_reader :page_name

  def initialize(page_name, author, contents)
    @page_name = page_name
    @revisions = Array.new
    add_revision(author, contents)
  end

  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 wiki page.
home_page = WikiPage.new("HomePage", "James Edward Gray II",
                         "A page about the JoysOfDocumentation..." )

wiki = PStore.new("wiki_pages.pstore")
# Update page data and the index together, or not at all.
wiki.transaction do
  # Store page.
  wiki[home_page.page_name] = home_page
  # Create page index.
  wiki[:wiki_index] ||= Array.new
  # Update wiki index.
  wiki[:wiki_index].push(*home_page.wiki_page_references)
end

# Read wiki data, setting argument read_only to true.
wiki.transaction(true) do
  wiki.keys.each do |key|
    puts key
    puts wiki[key]
  end
end