Class: PStore
- Inherits:
-
Object
- Object
- PStore
- Defined in:
- lib/pstore.rb
Overview
PStore implements a file based persistance mechanism based on a Hash. User code can store hierarchies of Ruby objects (values) into the data store file by name (keys). An object hierarchy may be just a single object. User code may later read values back from the data store or even update data, as needed.
The transactional behavior ensures that any changes succeed or fail together. This can be used to ensure that the data store is not left in a transitory state, where some values were upated but others were not.
Behind the scenes, Ruby objects are stored to the data store file with Marshal. That carries the usual limitations. Proc objects cannot be marshalled, for example.
Usage example:
require "pstore"
# a mock wiki object...
class WikiPage
def initialize( page_name, , contents )
@page_name = page_name
@revisions = Array.new
add_revision(, contents)
end
attr_reader :page_name
def add_revision( , contents )
@revisions << { :created => Time.now,
: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
Defined Under Namespace
Classes: Error
Constant Summary collapse
- RDWR_ACCESS =
File::RDWR | File::CREAT | binmode
- RD_ACCESS =
File::RDONLY | binmode
- WR_ACCESS =
File::WRONLY | File::CREAT | File::TRUNC | binmode
Instance Method Summary collapse
-
#[](name) ⇒ Object
Retrieves a value from the PStore file data, by name.
-
#[]=(name, value) ⇒ Object
Stores an individual Ruby object or a hierarchy of Ruby objects in the data store file under the root name.
-
#abort ⇒ Object
Ends the current PStore#transaction, discarding any changes to the data store.
-
#commit ⇒ Object
Ends the current PStore#transaction, committing any changes to the data store immediately.
-
#delete(name) ⇒ Object
Removes an object hierarchy from the data store, by name.
-
#dump(table) ⇒ Object
This method is just a wrapped around Marshal.dump.
-
#fetch(name, default = PStore::Error) ⇒ Object
This method is just like PStore#[], save that you may also provide a default value for the object.
-
#initialize(file) ⇒ PStore
constructor
To construct a PStore object, pass in the file path where you would like the data to be stored.
-
#load(content) ⇒ Object
This method is just a wrapped around Marshal.load.
-
#load_file(file) ⇒ Object
This method is just a wrapped around Marshal.load.
-
#path ⇒ Object
Returns the path to the data store file.
-
#root?(name) ⇒ Boolean
Returns true if the supplied name is currently in the data store.
-
#roots ⇒ Object
Returns the names of all object hierarchies currently in the store.
-
#transaction(read_only = false) ⇒ Object
Opens a new transaction for the data store.
Constructor Details
#initialize(file) ⇒ PStore
To construct a PStore object, pass in the file path where you would like the data to be stored.
94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/pstore.rb', line 94 def initialize(file) 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 @transaction = false @filename = file @abort = false end |
Instance Method Details
#[](name) ⇒ Object
Retrieves a value from the PStore file data, by name. The hierarchy of Ruby objects stored under that root name will be returned.
WARNING: This method is only valid in a PStore#transaction. It will raise PStore::Error if called at any other time.
128 129 130 131 |
# File 'lib/pstore.rb', line 128 def [](name) in_transaction @table[name] end |
#[]=(name, value) ⇒ Object
Stores an individual Ruby object or a hierarchy of Ruby objects in the data store file under the root name. Assigning to a name already in the data store clobbers the old data.
Example:
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
WARNING: This method is only valid in a PStore#transaction and it cannot be read-only. It will raise PStore::Error if called at any other time.
173 174 175 176 |
# File 'lib/pstore.rb', line 173 def []=(name, value) in_transaction_wr() @table[name] = value end |
#abort ⇒ Object
Ends the current PStore#transaction, discarding any changes to the data store.
Example:
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
WARNING: This method is only valid in a PStore#transaction. It will raise PStore::Error if called at any other time.
261 262 263 264 265 |
# File 'lib/pstore.rb', line 261 def abort in_transaction @abort = true throw :pstore_abort_transaction end |
#commit ⇒ Object
Ends the current PStore#transaction, committing any changes to the data store immediately.
Example:
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
WARNING: This method is only valid in a PStore#transaction. It will raise PStore::Error if called at any other time.
235 236 237 238 239 |
# File 'lib/pstore.rb', line 235 def commit in_transaction @abort = false throw :pstore_abort_transaction end |
#delete(name) ⇒ Object
Removes an object hierarchy from the data store, by name.
WARNING: This method is only valid in a PStore#transaction and it cannot be read-only. It will raise PStore::Error if called at any other time.
183 184 185 186 |
# File 'lib/pstore.rb', line 183 def delete(name) in_transaction_wr() @table.delete name end |
#dump(table) ⇒ Object
This method is just a wrapped around Marshal.dump.
348 349 350 |
# File 'lib/pstore.rb', line 348 def dump(table) # :nodoc: Marshal::dump(table) end |
#fetch(name, default = PStore::Error) ⇒ Object
This method is just like PStore#[], save that you may also provide a default value for the object. In the event the specified name is not found in the data store, your default will be returned instead. If you do not specify a default, PStore::Error will be raised if the object is not found.
WARNING: This method is only valid in a PStore#transaction. It will raise PStore::Error if called at any other time.
142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/pstore.rb', line 142 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 |
#load(content) ⇒ Object
This method is just a wrapped around Marshal.load.
353 354 355 |
# File 'lib/pstore.rb', line 353 def load(content) # :nodoc: Marshal::load(content) end |
#load_file(file) ⇒ Object
This method is just a wrapped around Marshal.load.
358 359 360 |
# File 'lib/pstore.rb', line 358 def load_file(file) # :nodoc: Marshal::load(file) end |
#path ⇒ Object
Returns the path to the data store file.
209 210 211 |
# File 'lib/pstore.rb', line 209 def path @filename end |
#root?(name) ⇒ Boolean
Returns true if the supplied name is currently in the data store.
WARNING: This method is only valid in a PStore#transaction. It will raise PStore::Error if called at any other time.
204 205 206 207 |
# File 'lib/pstore.rb', line 204 def root?(name) in_transaction @table.key? name end |
#roots ⇒ Object
Returns the names of all object hierarchies currently in the store.
WARNING: This method is only valid in a PStore#transaction. It will raise PStore::Error if called at any other time.
194 195 196 197 |
# File 'lib/pstore.rb', line 194 def roots in_transaction @table.keys end |
#transaction(read_only = false) ⇒ Object
Opens a new transaction for the data store. Code executed inside a block passed to this method may read and write data to and from the data store file.
At the end of the block, changes are committed to the data store automatically. You may exit the transaction early with a call to either PStore#commit or PStore#abort. See those methods for details about how changes are handled. Raising an uncaught Exception in the block is equivalent to calling PStore#abort.
If read_only is set to true
, you will only be allowed to read from the data store during the transaction and any attempts to change the data will raise a PStore::Error.
Note that PStore does not support nested transactions.
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/pstore.rb', line 284 def transaction(read_only=false) # :yields: pstore raise PStore::Error, "nested transaction" if @transaction begin @rdonly = read_only @abort = false @transaction = true value = nil new_file = @filename + ".new" content = nil unless read_only file = File.open(@filename, RDWR_ACCESS) file.flock(File::LOCK_EX) commit_new(file) if FileTest.exist?(new_file) content = file.read() else begin file = File.open(@filename, RD_ACCESS) file.flock(File::LOCK_SH) content = (File.open(new_file, RD_ACCESS) {|n| n.read} rescue file.read()) rescue Errno::ENOENT content = "" end end if content != "" @table = load(content) if !read_only size = content.size md5 = Digest::MD5.digest(content) end else @table = {} end content = nil # unreference huge data begin catch(:pstore_abort_transaction) do value = yield(self) end rescue Exception @abort = true raise ensure if !read_only and !@abort tmp_file = @filename + ".tmp" content = dump(@table) if !md5 || size != content.size || md5 != Digest::MD5.digest(content) File.open(tmp_file, WR_ACCESS) {|t| t.write(content)} File.rename(tmp_file, new_file) commit_new(file) end content = nil # unreference huge data end end ensure @table = nil @transaction = false file.close if file end value end |