класс 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
Атрибуты
Должен ли PStore делать все возможное, чтобы предотвратить повреждение файлов, даже в маловероятных условиях возникновения ошибок, таких как нехватка места и другие необычные ошибки файловой системы ОС. Установка этого флага приводит к потере производительности.
Этот флаг работает только на платформах, на которых переименование файлов является атомарным (например, все POSIX-платформы: Linux, MacOS X, FreeBSD и т. д.). Значение по умолчанию — false.
Публичные методы класса
# File lib/pstore.rb, line 119
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 передайте путь к файлу, где вы хотите хранить данные.
Объекты PStore всегда являются повторно-входящими. Но если thread_safe установлен в true, то он станет потокобезопасным ценой незначительного снижения производительности.
Публичные методы экземпляра
# File lib/pstore.rb, line 155 def [](name) in_transaction @table[name] end
Извлекает значение из данных файла PStore по имени. Будет возвращена иерархия объектов Ruby, хранящихся под этим корневым именем.
ПРЕДУПРЕЖДЕНИЕ: Этот метод действителен только в PStore#transaction. В противном случае будет выброшено исключение PStore::Error.
# File lib/pstore.rb, line 200 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_hierarchy] = { "Kev Jackson" => ["rational.rb", "pstore.rb"],
"James Gray" => ["erb.rb", "pstore.rb"] }
end # commit changes to data store file
ПРЕДУПРЕЖДЕНИЕ: Этот метод действителен только в PStore#transaction и не может быть доступен только для чтения. В противном случае будет выброшено исключение PStore::Error.
# File lib/pstore.rb, line 288 def abort in_transaction @abort = true throw :pstore_abort_transaction end
Завершает текущую PStore#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
ПРЕДУПРЕЖДЕНИЕ: Этот метод действителен только в PStore#transaction. В противном случае будет выброшено исключение PStore::Error.
# File lib/pstore.rb, line 262 def commit in_transaction @abort = false throw :pstore_abort_transaction end
Завершает текущую PStore#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
ПРЕДУПРЕЖДЕНИЕ: Этот метод действителен только в PStore#transaction. В противном случае будет выброшено исключение PStore::Error.
# File lib/pstore.rb, line 210 def delete(name) in_transaction_wr @table.delete name end
Удаляет иерархию объектов из хранилища данных по имени.
ПРЕДУПРЕЖДЕНИЕ: Этот метод действителен только в PStore#transaction и не может быть доступен только для чтения. В противном случае будет выброшено исключение PStore::Error.
# File lib/pstore.rb, line 169
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#[], за исключением того, что вы также можете указать значение по умолчанию для объекта. Если указанное имя не найдено в хранилище данных, вместо него будет возвращено ваше значение по умолчанию. Если вы не укажете значение по умолчанию, будет выброшено исключение PStore::Error, если объект не найден.
ПРЕДУПРЕЖДЕНИЕ: Этот метод действителен только в PStore#transaction. В противном случае будет выброшено исключение PStore::Error.
# File lib/pstore.rb, line 236 def path @filename end
Возвращает путь к файлу хранилища данных.
# File lib/pstore.rb, line 231 def root?(name) in_transaction @table.key? name end
Возвращает true, если указанное имя в данный момент находится в хранилище данных.
ПРЕДУПРЕЖДЕНИЕ: Этот метод действителен только в PStore#transaction. В противном случае будет выброшено исключение PStore::Error.
# File lib/pstore.rb, line 221 def roots in_transaction @table.keys end
Возвращает имена всех иерархий объектов, находящихся в данный момент в хранилище.
ПРЕДУПРЕЖДЕНИЕ: Этот метод действителен только в PStore#transaction. В противном случае будет выброшено исключение PStore::Error.
# File lib/pstore.rb, line 311
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 Открывает новую транзакцию для хранилища данных. Код, выполняемый внутри блока, переданного этому методу, может читать и записывать данные в файл хранилища данных.
В конце блока изменения автоматически сохраняются в хранилище данных. Вы можете досрочно завершить транзакцию, вызвав PStore#commit или PStore#abort. См. эти методы для получения подробной информации о том, как обрабатываются изменения. Вызов неперехваченного исключения Exception в блоке эквивалентен вызову PStore#abort.
Если read_only установлен в true, вам будет разрешено только читать из хранилища данных во время транзакции, и любые попытки изменить данные приведут к выбросу исключения PStore::Error.
Обратите внимание, что PStore не поддерживает вложенные транзакции.
Методы закрытого экземпляра
# File lib/pstore.rb, line 488 def empty_marshal_checksum EMPTY_MARSHAL_CHECKSUM end
# File lib/pstore.rb, line 485 def empty_marshal_data EMPTY_MARSHAL_DATA end
# File lib/pstore.rb, line 135 def in_transaction raise PStore::Error, "not in transaction" unless @lock.locked? end
Вызывает PStore::Error, если вызывающий код не находится в PStore#transaction.
# File lib/pstore.rb, line 142 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 399
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 имеет значение true, будет возвращён распарсенный Hash. Если read_only имеет значение false, будет возвращена тройка: распарсенный Hash, контрольная сумма данных и размер данных.
# File lib/pstore.rb, line 427
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 374
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 435
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 450
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 466 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.