Spec-Zone.ru › Ruby 2.5

класс PStore

Родитель:
Объект

PStore реализует механизм персистенции на основе файлов, основанный на Hash. Пользовательский код может хранить иерархии объектов Ruby (значения) в файле хранилища по имени (ключи). Иерархия объектов может быть просто одним объектом. Пользовательский код может позже считывать значения из хранилища данных или даже обновлять данные по мере необходимости.

Транзакционное поведение гарантирует, что любые изменения либо полностью завершаются успешно, либо полностью проваливаются. Это можно использовать для обеспечения того, что хранилище данных не останется в переходном состоянии, где некоторые значения были обновлены, а другие нет.

За кулисами объекты Ruby сохраняются в файле хранилища с помощью Marshal. Это влечёт за собой обычные ограничения. Например, объекты Proc не могут быть сериализованы.

Пример использования:

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

Режимы транзакций

По умолчанию целостность файла гарантируется только до тех пор, пока операционная система (и базовое оборудование) не генерируют неожиданные ошибки ввода-вывода. Если во время записи в файл PStore произойдёт ошибка ввода-вывода, файл будет повреждён.

Вы можете предотвратить это, установив pstore.ultra_safe = true. Однако это приведёт к незначительному снижению производительности и будет работать только на платформах, поддерживающих атомарные переименования файлов. Подробнее см. в документации для ultra_safe.

Само собой разумеется, если вы храните ценные данные с помощью PStore, вам следует время от времени создавать резервные копии файлов PStore.

Константы

CHECKSUM_ALGO

Константа для облегчения работы сборщика мусора Ruby.

EMPTY_MARSHAL_CHECKSUM
EMPTY_MARSHAL_DATA
EMPTY_STRING
RDWR_ACCESS
RD_ACCESS
WR_ACCESS

Атрибуты

ultra_safe[RW]

Указывает, следует ли PStore делать всё возможное для предотвращения повреждения файлов, даже при возникновении маловероятных ошибок, таких как недостаток места и других необычных ошибок файловой системы ОС. Установка этого флага влечёт за собой потерю производительности.

Этот флаг имеет эффект только на платформах, на которых переименование файлов атомарно (например, все POSIX-платформы: Linux, MacOS X, FreeBSD и т.д.). Значение по умолчанию — false.

Общедоступные методы класса

new(file, thread_safe = false) Показать исходный код
# File lib/pstore.rb, line 118
def initialize(file, thread_safe = false)
  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
  @filename = file
  @abort = false
  @ultra_safe = false
  @thread_safe = thread_safe
  @lock = Thread::Mutex.new
end

Для создания объекта PStore передайте путь к file, где вы хотите хранить данные.

Объекты PStore всегда рекурсивны. Но если thread_safe установлено в true, то он станет потокобезопасным с незначительной потерей производительности.

Общедоступные методы экземпляра

[](name) Показать исходный код
# File lib/pstore.rb, line 154
def [](name)
  in_transaction
  @table[name]
end

Извлекает значение из файла данных PStore по name. Возвращается иерархия объектов Ruby, хранящихся под этим корневым name.

ПРЕДУПРЕЖДЕНИЕ: Этот метод допустим только в рамках #transaction. Он вызовет PStore::Error, если будет вызван в другое время.

[]=(name, value) Показать исходный код
# File lib/pstore.rb, line 199
def []=(name, value)
  in_transaction_wr
  @table[name] = value
end

Хранит отдельный объект Ruby или иерархию объектов Ruby в файле хранилища под корневым name. Присваивание name, уже существующему в хранилище, перезаписывает старые данные.

Пример:

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_hierarchy] = { "Kev Jackson" => ["rational.rb", "pstore.rb"],
                            "James Gray"  => ["erb.rb", "pstore.rb"] }
end                   # commit changes to data store file

ПРЕДУПРЕЖДЕНИЕ: Этот метод допустим только в рамках #transaction и не может быть только для чтения. Он вызовет PStore::Error, если будет вызван в другое время.

abort() Показать исходный код
# File lib/pstore.rb, line 287
def abort
  in_transaction
  @abort = true
  throw :pstore_abort_transaction
end

Завершает текущую транзакцию #transaction, отказываясь от любых изменений в хранилище данных.

Пример:

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

ПРЕДУПРЕЖДЕНИЕ: Этот метод допустим только в рамках #transaction. Он вызовет PStore::Error, если будет вызван в другое время.

commit() Показать исходный код
# File lib/pstore.rb, line 261
def commit
  in_transaction
  @abort = false
  throw :pstore_abort_transaction
end

Завершает текущую транзакцию #transaction, немедленно фиксируя любые изменения в хранилище данных.

Пример:

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

ПРЕДУПРЕЖДЕНИЕ: Этот метод допустим только в рамках #transaction. Он вызовет PStore::Error, если будет вызван в другое время.

delete(name) Показать исходный код
# File lib/pstore.rb, line 209
def delete(name)
  in_transaction_wr
  @table.delete name
end

Удаляет иерархию объектов из хранилища данных по name.

ПРЕДУПРЕЖДЕНИЕ: Этот метод допустим только в рамках #transaction и не может быть только для чтения. Он вызовет PStore::Error, если будет вызван в другое время.

fetch(name, default=PStore::Error) Показать исходный код
# File lib/pstore.rb, line 168
def fetch(name, default=PStore::Error)
  in_transaction
  unless @table.key? name
    if default == PStore::Error
      raise PStore::Error, format("undefined root name `%s'", name)
    else
      return default
    end
  end
  @table[name]
end

Этот метод похож на #[], за исключением возможности указать значение по умолчанию для объекта. В случае, если указанный name не найден в хранилище данных, возвращается ваше значение по умолчанию. Если вы не укажете значение по умолчанию, при отсутствии объекта будет вызвана PStore::Error.

ПРЕДУПРЕЖДЕНИЕ: Этот метод допустим только в рамках #transaction. Он вызовет PStore::Error, если будет вызван в другое время.

path() Показать исходный код
# File lib/pstore.rb, line 235
def path
  @filename
end

Возвращает путь к файлу хранилища данных.

root?(name) Показать исходный код
# File lib/pstore.rb, line 230
def root?(name)
  in_transaction
  @table.key? name
end

Возвращает true, если предоставленное name присутствует в хранилище данных.

ПРЕДУПРЕЖДЕНИЕ: Этот метод допустим только в рамках #transaction. Он вызовет PStore::Error, если будет вызван в другое время.

roots() Показать исходный код
# File lib/pstore.rb, line 220
def roots
  in_transaction
  @table.keys
end

Возвращает имена всех иерархий объектов, хранящихся в хранилище.

ПРЕДУПРЕЖДЕНИЕ: Этот метод допустим только в рамках #transaction. Он вызовет PStore::Error, если будет вызван в другое время.

transaction(read_only = false) { |pstore| ... } Показать исходный код
# File lib/pstore.rb, line 310
def transaction(read_only = false)  # :yields:  pstore
  value = nil
  if !@thread_safe
    raise PStore::Error, "nested transaction" unless @lock.try_lock
  else
    begin
      @lock.lock
    rescue ThreadError
      raise PStore::Error, "nested transaction"
    end
  end
  begin
    @rdonly = read_only
    @abort = false
    file = open_and_lock_file(@filename, read_only)
    if file
      begin
        @table, checksum, original_data_size = load_data(file, read_only)

        catch(:pstore_abort_transaction) do
          value = yield(self)
        end

        if !@abort && !read_only
          save_data(checksum, original_data_size, file)
        end
      ensure
        file.close
      end
    else
      # This can only occur if read_only == true.
      @table = {}
      catch(:pstore_abort_transaction) do
        value = yield(self)
      end
    end
  ensure
    @lock.unlock
  end
  value
end

Открывает новую транзакцию для хранилища данных. Код, выполняемый внутри блока, переданного в этот метод, может читать и записывать данные в файл хранилища данных.

В конце блока изменения автоматически фиксируются в хранилище данных. Вы можете выйти из транзакции досрочно, вызвав либо #commit, либо #abort. Подробности о том, как обрабатываются изменения, см. в документации к этим методам. Выбрасывание неперехваченного Исключения в блоке эквивалентно вызову #abort.

Если read_only установлено в true, вы сможете только читать из хранилища данных во время транзакции, и любые попытки изменить данные вызовут PStore::Error.

Обратите внимание, что PStore не поддерживает вложенные транзакции.

Методы частного экземпляра

empty_marshal_checksum() Показать исходный код
# File lib/pstore.rb, line 487
def empty_marshal_checksum
  EMPTY_MARSHAL_CHECKSUM
end
empty_marshal_data() Показать исходный код
# File lib/pstore.rb, line 484
def empty_marshal_data
  EMPTY_MARSHAL_DATA
end
in_transaction() Показать исходный код
# File lib/pstore.rb, line 134
def in_transaction
  raise PStore::Error, "not in transaction" unless @lock.locked?
end

Вызывает PStore::Error, если вызывающий код не находится в #транзакции.

in_transaction_wr() Показать исходный код
# File lib/pstore.rb, line 141
def in_transaction_wr
  in_transaction
  raise PStore::Error, "in read-only transaction" if @rdonly
end

Вызывает PStore::Error, если вызывающий код не находится в #транзакции или если код находится в #только для чтения транзакции.

load_data(file, read_only) Показать исходный код
# File lib/pstore.rb, line 398
def load_data(file, read_only)
  if read_only
    begin
      table = load(file)
      raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash)
    rescue EOFError
      # This seems to be a newly-created file.
      table = {}
    end
    table
  else
    data = file.read
    if data.empty?
      # This seems to be a newly-created file.
      table = {}
      checksum = empty_marshal_checksum
      size = empty_marshal_data.bytesize
    else
      table = load(data)
      checksum = CHECKSUM_ALGO.digest(data)
      size = data.bytesize
      raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash)
    end
    data.replace(EMPTY_STRING)
    [table, checksum, size]
  end
end

Загрузить указанный файл PStore. Если read_only истинно, будет возвращена разархивированная Hash. Если read_only ложно, будет возвращена тройка: разархивированная Hash, контрольная сумма данных и размер данных.

on_windows?() Показать исходный код
# File lib/pstore.rb, line 426
def on_windows?
  is_windows = RUBY_PLATFORM =~ /mswin|mingw|bccwin|wince/
  self.class.__send__(:define_method, :on_windows?) do
    is_windows
  end
  is_windows
end
open_and_lock_file(filename, read_only) Показать исходный код
# File lib/pstore.rb, line 373
def open_and_lock_file(filename, read_only)
  if read_only
    begin
      file = File.new(filename, RD_ACCESS)
      begin
        file.flock(File::LOCK_SH)
        return file
      rescue
        file.close
        raise
      end
    rescue Errno::ENOENT
      return nil
    end
  else
    file = File.new(filename, RDWR_ACCESS)
    file.flock(File::LOCK_EX)
    return file
  end
end

Открыть указанный файл с именем (в режиме только для чтения или чтения/записи) и заблокировать его для чтения или записи.

Будет возвращён открытый объект File. Если read_only истинно, а файла не существует, то будет возвращён nil.

Все исключения передаются.

save_data(original_checksum, original_file_size, file) Показать исходный код
# File lib/pstore.rb, line 434
def save_data(original_checksum, original_file_size, file)
  new_data = dump(@table)

  if new_data.bytesize != original_file_size || CHECKSUM_ALGO.digest(new_data) != original_checksum
    if @ultra_safe && !on_windows?
      # Windows doesn't support atomic file renames.
      save_data_with_atomic_file_rename_strategy(new_data, file)
    else
      save_data_with_fast_strategy(new_data, file)
    end
  end

  new_data.replace(EMPTY_STRING)
end
save_data_with_atomic_file_rename_strategy(data, file) Показать исходный код
# File lib/pstore.rb, line 449
def save_data_with_atomic_file_rename_strategy(data, file)
  temp_filename = "#{@filename}.tmp.#{Process.pid}.#{rand 1000000}"
  temp_file = File.new(temp_filename, WR_ACCESS)
  begin
    temp_file.flock(File::LOCK_EX)
    temp_file.write(data)
    temp_file.flush
    File.rename(temp_filename, @filename)
  rescue
    File.unlink(temp_file) rescue nil
    raise
  ensure
    temp_file.close
  end
end
save_data_with_fast_strategy(data, file) Показать исходный код
# File lib/pstore.rb, line 465
def save_data_with_fast_strategy(data, file)
  file.rewind
  file.write(data)
  file.truncate(data.bytesize)
end

Ruby Core © 1993–2017 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API