класс 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 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, то он станет потокобезопасным с потерей производительности.
Публичные методы экземпляра
# 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_hierarchy] = { "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 Этот метод похож на #[], за исключением того, что вы можете также указать значение default для объекта. В случае, если указанное имя не найдено в хранилище данных, возвращается ваше значение default. Если вы не укажете значение по умолчанию, 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
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. Подробную информацию об обработке изменений см. в этих методах. Вызов необработанного исключения Exception в блоке эквивалентен вызову #abort.
Если read_only установлено в true, вам разрешено только считывать данные из хранилища данных во время транзакции, а любые попытки изменить данные приведут к вызову PStore::Error.
Обратите внимание, что PStore не поддерживает вложенные транзакции.
Методы экземпляра (приватные)
# File lib/pstore.rb, line 487 def empty_marshal_checksum EMPTY_MARSHAL_CHECKSUM end
# File lib/pstore.rb, line 484 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, если вызывающий код не находится в #транзакции.
# File lib/pstore.rb, line 141 def in_transaction_wr in_transaction raise PStore::Error, "in read-only transaction" if @rdonly end
Вызывает PStore::Error, если вызывающий код не находится в #транзакции или если код находится в чтении-только #транзакции.
# 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 имеет значение true, возвращается разархивированный Hash. Если read_only имеет значение false, возвращается кортеж из 3 элементов: разархивированный Hash, контрольная сумма данных и размер данных.
# 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 # 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 имеет значение true, и файл не существует, возвращается nil.
Все исключения передаются вверх.
# 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 # 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 # 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.