класс PStore
PStore реализует механизм персистенции на основе файлов, основанный на хеше. Код пользователя может хранить иерархии объектов 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.
Константы
- EMPTY_MARSHAL_CHECKSUM
- EMPTY_MARSHAL_DATA
- EMPTY_STRING
-
Константа для облегчения работы сборщика мусора Ruby.
- RDWR_ACCESS
- RD_ACCESS
- WR_ACCESS
Атрибуты
Указывает, должен ли PStore делать всё возможное, чтобы предотвратить повреждение файла, даже при маловероятных ошибках, таких как недостаток места и другие необычные ошибки файловой системы ОС. Установка этого флага влечёт за собой снижение производительности.
Этот флаг имеет эффект только на платформах, на которых переименование файлов атомарно (например, все платформы POSIX: Linux, MacOS X, FreeBSD и т. д.). Значение по умолчанию — 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 = Mutex.new
end Для создания объекта PStore укажите путь к файлу file, где вы хотите хранить данные.
Объекты PStore всегда являются реентерабельными. Но если thread_safe установлено в true, то он станет потокобезопасным с небольшим снижением производительности.
Общедоступные методы экземпляров
# File lib/pstore.rb, line 154 def [](name) in_transaction @table[name] end
Извлекает значение из данных файла PStore по имени. Возвращается иерархия объектов Ruby, сохранённых под этим корневым именем.
ПРЕДУПРЕЖДЕНИЕ: Этот метод допустим только в рамках #transaction. Он вызовет PStore::Error, если вызван в любое другое время.
# File lib/pstore.rb, line 199 def []=(name, value) in_transaction_wr @table[name] = value end
Сохраняет отдельный объект Ruby или иерархию объектов Ruby в файле хранилища под корневым именем. Присвоение имени, уже имеющемуся в хранилище данных, перезаписывает старые данные.
Пример:
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_heirarchy] = { "Kev Jackson" => ["rational.rb", "pstore.rb"],
"James Gray" => ["erb.rb", "pstore.rb"] }
end # commit changes to data store file
ПРЕДУПРЕЖДЕНИЕ: Этот метод допустим только в #transaction и не может быть только для чтения. Он вызовет PStore::Error, если вызван в любое другое время.
# 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, если вызван в любое другое время.
# 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, если вызван в любое другое время.
# File lib/pstore.rb, line 209 def delete(name) in_transaction_wr @table.delete name end
Удаляет иерархию объектов из хранилища данных по имени.
ПРЕДУПРЕЖДЕНИЕ: Этот метод допустим только в #transaction и не может быть только для чтения. Он вызовет 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 Этот метод аналогичен #[], за исключением того, что вы можете также указать значение по умолчанию для объекта. В случае, если указанное имя не найдено в хранилище данных, будет возвращено ваше значение по умолчанию. Если вы не укажете значение по умолчанию, при отсутствии объекта будет вызван PStore::Error.
ПРЕДУПРЕЖДЕНИЕ: Этот метод допустим только в #transaction. Он вызовет PStore::Error, если вызван в любое другое время.
# File lib/pstore.rb, line 235 def path @filename end
Возвращает путь к файлу хранилища данных.
# File lib/pstore.rb, line 230 def root?(name) in_transaction @table.key? name end
Возвращает true, если указанное имя в настоящее время находится в хранилище данных.
ПРЕДУПРЕЖДЕНИЕ: Этот метод допустим только в #transaction. Он вызовет PStore::Error, если вызван в любое другое время.
# File lib/pstore.rb, line 220 def roots in_transaction @table.keys end
Возвращает имена всех иерархий объектов, в настоящее время хранящихся в хранилище.
ПРЕДУПРЕЖДЕНИЕ: Этот метод допустим только в #transaction. Он вызовет PStore::Error, если вызван в любое другое время.
# 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 if !file.closed?
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 не поддерживает вложенные транзакции.
Скрытые методы экземпляра
# File lib/pstore.rb, line 481 def empty_marshal_checksum EMPTY_MARSHAL_CHECKSUM end
# File lib/pstore.rb, line 478 def empty_marshal_data EMPTY_MARSHAL_DATA end
# File lib/pstore.rb, line 134 def in_transaction raise PStore::Error, "not in transaction" unless @lock.locked? end
Вызывает PStore::Error, если вызывающий код не находится в транзакции #transaction.
# File lib/pstore.rb, line 141 def in_transaction_wr in_transaction raise PStore::Error, "in read-only transaction" if @rdonly end
Вызывает PStore::Error, если вызывающий код не находится в транзакции #transaction или если код находится в транзакции только для чтения #transaction.
# File lib/pstore.rb, line 392
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 = Digest::MD5.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 равно true, будет возвращена разархивированная хеш-таблица Hash. Если read_only равно false, будет возвращена тройка: разархивированная хеш-таблица Hash, контрольная сумма MD5 данных и размер данных.
# File lib/pstore.rb, line 420
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 367
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 равно true, и файл не существует, то будет возвращено nil.
Все исключения передаются вверх.
# File lib/pstore.rb, line 428
def save_data(original_checksum, original_file_size, file)
new_data = dump(@table)
if new_data.bytesize != original_file_size || Digest::MD5.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 443
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 459 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.