класс PStore
PStore реализует механизм сохранения данных в файле на основе Hash. Пользовательский код может сохранять иерархии объектов Ruby (значения) в хранилище по имени (ключи). Иерархия объектов может состоять всего из одного объекта. Пользовательский код может позже считывать значения из хранилища или даже обновлять данные по мере необходимости.
Транзакционное поведение гарантирует, что все изменения происходят успешно или терпят неудачу вместе. Это можно использовать для обеспечения того, что хранилище данных не останется в переходном состоянии, где некоторые значения были обновлены, а другие — нет.
За кулисами объекты Ruby сохраняются в файл хранилища с помощью Marshal. Это влечёт за собой обычные ограничения. Например, объекты Proc не могут быть сохраняемы.
Здесь три важных понятия (подробности по ссылкам):
-
Хранилище: хранилище — это экземпляр PStore.
-
Элементы: хранилище подобно хэшу; каждый элемент — это ключ для сохранённого объекта.
-
Транзакции: каждая транзакция — это набор предполагаемых изменений в хранилище; транзакция определяется в блоке, заданном при вызове
PStore#transaction.
О примерах
Примеры на этой странице нуждаются в хранилище с известными свойствами. Они могут получить новое (и заполненное) хранилище, вызвав его таким образом:
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.
Элементы
Хранилище может содержать любое количество элементов. Каждый элемент имеет ключ и значение, как и в хэше:
-
Ключ: как и в хэше, ключ может быть (почти) любым объектом; см. Ключи хэша. Для простоты можно использовать только символы или строки в качестве ключей.
-
Значение: значение может быть любым объектом, который может быть сохраняем Marshal (см. Marshal::dump), и фактически может быть коллекцией (например, массивом, хэшем, множеством, диапазоном и т. д.). Эта коллекция может, в свою очередь, содержать вложенные объекты, включая коллекции, на любой глубине; эти объекты также должны быть сохраняемы Marshal. См. Иерархические значения.
Транзакции
Блок транзакции
Блок, переданный при вызове метода transaction, содержит транзакцию, которая состоит из вызовов методов PStore, которые считывают или записывают данные в хранилище (то есть, все методы PStore, кроме transaction сам по себе, path и Pstore.new):
example_store do |store|
store.transaction do
store.keys # => [:foo, :bar, :baz]
store[:bat] = 3
store.keys # => [:foo, :bar, :baz, :bat]
end
end
Выполнение транзакции откладывается до выхода из блока, и выполняется атомарно (все или ничего): либо все вызовы транзакции выполняются, либо ни один. Это поддерживает целостность хранилища.
Другой код в блоке (включая даже вызовы path и PStore.new) выполняется немедленно, а не откладывается.
Блок транзакции:
-
Не может содержать вложенный вызов
transaction. -
Является единственным контекстом, где разрешены методы, считывающие или записывающие данные в хранилище.
Как видно выше, изменения в транзакции производятся автоматически при выходе из блока. Блок может быть досрочно завершен вызовом метода commit или abort.
-
Methodcommitвызывает обновление хранилища и завершает блок:example_store do |store| store.transaction do store.keys # => [:foo, :bar, :baz] store[:bat] = 3 store.commit fail 'Cannot get here' end store.transaction do # Update was completed. store.keys # => [:foo, :bar, :baz, :bat] end end -
Methodabortотбрасывает обновление хранилища и завершает блок:example_store do |store| store.transaction do store.keys # => [:foo, :bar, :baz] store[:bat] = 3 store.abort fail 'Cannot get here' end store.transaction do # Update was not completed. store.keys # => [:foo, :bar, :baz] end end
Только чтение транзакций
По умолчанию транзакция позволяет как чтение, так и запись в хранилище:
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"
Безопасность транзакций
Для безопасности транзакций см.:
-
Необязательный аргумент
thread_safeв методеPStore.new. -
Атрибут
ultra_safe.
Само собой разумеется, если вы храните ценные данные с помощью 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
Константы
- CHECKSUM_ALGO
-
Константа для облегчения работы сборщика мусора Ruby.
- EMPTY_MARSHAL_CHECKSUM
- EMPTY_MARSHAL_DATA
- EMPTY_STRING
- RDWR_ACCESS
- RD_ACCESS
- VERSION
- WR_ACCESS
Атрибуты
Определяет, должен ли PStore приложить все усилия для предотвращения повреждения файла, даже при возникновении маловероятной ошибки (например, ошибки памяти или ошибки файловой системы):
-
true: изменения публикуются путём создания временного файла, записи обновлённых данных в него, а затем переименования файла в заданныйpath. Сохраняется целостностьFile. Примечание: имеет эффект только если файловая система поддерживает атомное переименование файлов (как это делают POSIX-платформы Linux, macOS, FreeBSD и другие). -
false(по умолчанию): изменения публикуются путём перемотки открытого файла и записи обновлённых данных. Сохраняется целостностьFile, если файловая система не поднимает неожиданную ошибку ввода-вывода; если при записи в хранилище происходит такая ошибка, файл может быть повреждён.
Открытые методы класса
Исходный код
# File lib/pstore.rb, line 372
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.
path = 't.store' store = PStore.new(path)
Объект PStore является рекурсивным. Если аргумент thread_safe задан как true, объект также является потокобезопасным (со снижением производительности):
store = PStore.new(path, true)
Публичные методы экземпляра
Исходный код
# File lib/pstore.rb, line 417 def [](key) in_transaction @table[key] end
Возвращает значение для заданного key, если ключ существует. nil в противном случае; если не nil, возвращаемое значение представляет собой объект или иерархию объектов:
example_store do |store|
store.transaction do
store[:foo] # => 0
store[:nope] # => nil
end
end
Возвращает nil, если такого ключа нет.
См. также Иерархические значения.
Вызывает исключение, если вызывается за пределами блока транзакции.
Исходный код
# File lib/pstore.rb, line 459 def []=(key, value) in_transaction_wr @table[key] = value end
Создает или заменяет значение для заданного key:
example_store do |store|
temp.transaction do
temp[:bat] = 3
end
end
См. также Иерархические значения.
Вызывает исключение, если вызывается за пределами блока транзакции.
Исходный код
# File lib/pstore.rb, line 535 def abort in_transaction @abort = true throw :pstore_abort_transaction end
Выходит из текущего блока транзакции, отбрасывая все изменения, указанные в блоке транзакции.
Вызывает исключение, если вызывается за пределами блока транзакции.
Исходный код
# File lib/pstore.rb, line 524 def commit in_transaction @abort = false throw :pstore_abort_transaction end
Выходит из текущего блока транзакции, подтверждая все изменения, указанные в блоке транзакции.
Вызывает исключение, если вызывается за пределами блока транзакции.
Исходный код
# File lib/pstore.rb, line 476 def delete(key) in_transaction_wr @table.delete key end
Удаляет и возвращает значение по key, если оно существует:
example_store do |store|
store.transaction do
store[:bat] = 3
store.delete(:bat)
end
end
Возвращает nil, если такого ключа нет.
Вызывает исключение, если вызывается за пределами блока транзакции.
Исходный код
# File lib/pstore.rb, line 436
def fetch(key, default=PStore::Error)
in_transaction
unless @table.key? key
if default == PStore::Error
raise PStore::Error, format("undefined key '%s'", key)
else
return default
end
end
@table[key]
end Как [], за исключением того, что он принимает значение по умолчанию для хранилища. Если key не существует:
-
Вызывает исключение, если
defaultявляетсяPStore::Error. -
Возвращает значение
defaultв противном случае:example_store do |store| store.transaction do store.fetch(:nope, nil) # => nil store.fetch(:nope) # Raises an exception. end end
Вызывает исключение, если вызывается за пределами блока транзакции.
Исходный код
# File lib/pstore.rb, line 505 def key?(key) in_transaction @table.key? key end
Возвращает true, если key существует, false в противном случае:
example_store do |store|
store.transaction do
store.key?(:foo) # => true
end
end
Вызывает исключение, если вызывается за пределами блока транзакции.
Исходный код
# File lib/pstore.rb, line 490 def keys in_transaction @table.keys end
Возвращает массив существующих ключей:
example_store do |store|
store.transaction do
store.keys # => [:foo, :bar, :baz]
end
end
Вызывает исключение, если вызывается за пределами блока транзакции.
Исходный код
# File lib/pstore.rb, line 515 def path @filename end
Возвращает строковый путь к файлу, используемый для создания хранилища:
store.path # => "flat.store"
Исходный код
# File lib/pstore.rb, line 551
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 Открывает блок транзакции для хранилища. См. Транзакции.
С аргументом read_only как false, блок может как читать из хранилища, так и записывать в него.
С аргументом read_only как true, блок не может включать вызовы transaction, []= или delete.
Вызывает исключение, если вызывается внутри блока транзакции.
Методы экземпляра (приватные)
Исходный код
# File lib/pstore.rb, line 728 def empty_marshal_checksum EMPTY_MARSHAL_CHECKSUM end
Исходный код
# File lib/pstore.rb, line 725 def empty_marshal_data EMPTY_MARSHAL_DATA end
Исходный код
# File lib/pstore.rb, line 388 def in_transaction raise PStore::Error, "not in transaction" unless @lock.locked? end
Вызывает исключение PStore::Error, если вызывающий код не находится внутри PStore#transaction.
Исходный код
# File lib/pstore.rb, line 395 def in_transaction_wr in_transaction raise PStore::Error, "in read-only transaction" if @rdonly end
Вызывает исключение PStore::Error, если вызывающий код не находится внутри PStore#transaction или если код находится в режиме только чтения PStore#transaction.
Исходный код
# File lib/pstore.rb, line 639
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, контрольная сумма данных и размер данных.
Исходный код
# File lib/pstore.rb, line 667
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 Исходный код
# File lib/pstore.rb, line 614
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.
Все исключения будут перехвачены.
Исходный код
# File lib/pstore.rb, line 675
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 Исходный код
# File lib/pstore.rb, line 690
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 Исходный код
# File lib/pstore.rb, line 706 def save_data_with_fast_strategy(data, file) file.rewind file.write(data) file.truncate(data.bytesize) end
Ruby Core © 1993–2024 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.