класс 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.
Константы
- 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 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 = Mutex.new
end Для создания объекта PStore, укажите путь к file, где вы хотите хранить данные.
Объекты PStore всегда реентерабельны. Но если thread_safe установлено в true, то он станет потокобезопасным за счёт незначительного снижения производительности.
Публичные методы экземпляра
# File lib/pstore.rb, line 155 def [](name) in_transaction @table[name] end
Извлекает значение из файла данных PStore по name. Возвращается иерархия объектов Ruby, хранящихся под этим корневым name.
ВНИМАНИЕ: Этот метод допустим только в рамках #transaction. Он вызовет PStore::Error, если будет вызван в другое время.
# File lib/pstore.rb, line 200 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, если будет вызван в другое время.
# File lib/pstore.rb, line 288 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 262 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 210 def delete(name) in_transaction_wr @table.delete name end
Удаляет иерархию объектов из хранилища данных по name.
ВНИМАНИЕ: Этот метод допустим только в #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 Этот метод похож на #[], но вы также можете указать default значение для объекта. Если указанный name не найден в хранилище данных, возвращается default. Если вы не укажете значение по умолчанию, будет выброшено PStore::Error, если объект не найден.
ВНИМАНИЕ: Этот метод допустим только в #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, если предоставленный name находится в хранилище данных.
ВНИМАНИЕ: Этот метод допустим только в #transaction. Он вызовет PStore::Error, если будет вызван в другое время.
# File lib/pstore.rb, line 221 def roots in_transaction @table.keys end
Возвращает имена всех иерархий объектов, хранящихся в хранилище.
ВНИМАНИЕ: Этот метод допустим только в #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 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 482 def empty_marshal_checksum EMPTY_MARSHAL_CHECKSUM end
# File lib/pstore.rb, line 479 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, если вызывающий код не находится в транзакции.
# File lib/pstore.rb, line 142 def in_transaction_wr in_transaction raise PStore::Error, "in read-only transaction" if @rdonly end
Вызывает PStore::Error, если вызывающий код не находится в транзакции или если код находится в только для чтения транзакции.
# File lib/pstore.rb, line 393
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 421
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 368
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 429
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 444
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 460 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.