Class: StrokeDB::Document
- Includes:
- Validations::InstanceMethods
- Defined in:
- lib/strokedb/document.rb,
lib/strokedb/document/delete.rb,
lib/strokedb/document/versions.rb
Overview
Document is one of the core classes. It is being used to represent database document.
Database document is an entity that:
-
is uniquely identified with UUID
-
has a number of slots, where each slot is a key-value pair (whereas pair could be a JSON object)
Here is a simplistic example of document:
1e3d02cc-0769-4bd8-9113-e033b246b013:
name: "My Document"
language: "English"
authors: ["Yurii Rashkovskii","Oleg Andreev"]
Defined Under Namespace
Classes: MetaModulesCollector, Metas, Versions
Instance Attribute Summary collapse
-
#callbacks ⇒ Object
readonly
:nodoc:.
Class Method Summary collapse
-
.create!(*args, &block) ⇒ Object
Instantiates new document with given arguments (which are the same as in Document#new), and saves it right away.
-
.find(*args) ⇒ Object
Find document(s) by:.
-
.from_raw(store, raw_slots, opts = {}, &block) ⇒ Object
Creates a document from a serialized representation.
Instance Method Summary collapse
-
#+(document) ⇒ Object
Instantiate a composite document.
-
#==(doc) ⇒ Object
:nodoc:.
-
#[](slotname) ⇒ Object
Get slot value by its name:.
-
#[]=(slotname, value) ⇒ Object
Set slot value by its name:.
-
#__reference__ ⇒ Object
:nodoc:.
-
#add_callback(cbk) ⇒ Object
:nodoc:.
- #delete! ⇒ Object
-
#diff(from) ⇒ Object
Creates Diff document from
from
document to this document. -
#eql?(doc) ⇒ Boolean
:nodoc:.
-
#has_slot?(slotname) ⇒ Boolean
Checks slot presence.
-
#hash ⇒ Object
documents are hashed by their UUID.
-
#head? ⇒ Boolean
Returns
true
if this document is a latest version of document being saved to a respective store. -
#initialize(*args, &block) ⇒ Document
constructor
Instantiates new document.
- #make_immutable! ⇒ Object
-
#marshal_dump ⇒ Object
:nodoc:.
-
#marshal_load(content) ⇒ Object
:nodoc:.
-
#meta ⇒ Object
Returns document’s metadocument (if any).
-
#metas ⇒ Object
Should be used to add metadocuments on the fly:.
-
#method_missing(sym, *args) ⇒ Object
:nodoc:.
- #mutable? ⇒ Boolean
-
#new? ⇒ Boolean
Returns
true
if this is a document that has never been saved. -
#pretty_print ⇒ Object
(also: #to_s, #inspect)
:nodoc:.
-
#previous_version ⇒ Object
Returns document’s previous version (which is stored in
previous_version
slot). -
#raw_uuid ⇒ Object
:nodoc:.
-
#reload ⇒ Object
Reloads head of the same document from store.
-
#remove_slot!(slotname) ⇒ Object
Removes slot.
-
#reverse_update_slots(hash) ⇒ Object
Updates nil/false slots with a specified
hash
and returns itself. -
#reverse_update_slots!(hash) ⇒ Object
Same as reverse_update_slots, but also saves the document.
-
#save!(perform_validation = true) ⇒ Object
Saves the document.
-
#slotnames ⇒ Object
Returns an
Array
of explicitely defined slots. - #store ⇒ Object
-
#to_json ⇒ Object
Returns string with Document’s JSON representation.
-
#to_optimized_raw ⇒ Object
:nodoc:.
-
#to_raw ⇒ Object
Primary serialization.
-
#to_xml(opts = {}) ⇒ Object
Returns string with Document’s XML representation.
-
#update_slots(hash) ⇒ Object
Updates slots with a specified
hash
and returns itself. -
#update_slots!(hash) ⇒ Object
Same as update_slots, but also saves the document.
-
#uuid ⇒ Object
Return document’s uuid.
-
#version ⇒ Object
Returns document’s version (which is stored in
version
slot). -
#version=(v) ⇒ Object
:nodoc:.
-
#versions ⇒ Object
Returns an instance of Document::Versions.
Methods included from Validations::InstanceMethods
Constructor Details
#initialize(*args, &block) ⇒ Document
Instantiates new document
Here are few ways to call it:
Document.new(:slot_1 => slot_1_value, :slot_2 => slot_2_value)
This way new document with slots slot_1
and slot_2
will be initialized in the default store.
Document.new(store,:slot_1 => slot_1_value, :slot_2 => slot_2_value)
This way new document with slots slot_1
and slot_2
will be initialized in the given store
.
Document.new({:slot_1 => slot_1_value, :slot_2 => slot_2_value},uuid)
where uuid
is a string with UUID. WARNING: this way of initializing Document should not be used unless you know what are you doing!
187 188 189 190 191 192 193 194 195 196 |
# File 'lib/strokedb/document.rb', line 187 def initialize(*args, &block) @initialization_block = block if args.first.is_a?(Hash) || args.empty? raise NoDefaultStoreError unless StrokeDB.default_store do_initialize(StrokeDB.default_store, *args) else do_initialize(*args) end end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(sym, *args) ⇒ Object
:nodoc:
586 587 588 589 590 591 592 593 594 595 596 597 598 599 |
# File 'lib/strokedb/document.rb', line 586 def method_missing(sym, *args) #:nodoc: sym = sym.to_s return send(:[]=, sym.chomp('='), *args) if sym.ends_with? '=' return self[sym] if slotnames.include? sym return !!send(sym.chomp('?'), *args) if sym.ends_with? '?' raise SlotNotFoundError.new(sym) if (callbacks['when_slot_not_found'] || []).empty? r = execute_callbacks(:when_slot_not_found, sym) raise r if r.is_a? SlotNotFoundError # TODO: spec this behavior r end |
Instance Attribute Details
#callbacks ⇒ Object (readonly)
:nodoc:
75 76 77 |
# File 'lib/strokedb/document.rb', line 75 def callbacks @callbacks end |
Class Method Details
.create!(*args, &block) ⇒ Object
Instantiates new document with given arguments (which are the same as in Document#new), and saves it right away
163 164 165 |
# File 'lib/strokedb/document.rb', line 163 def self.create!(*args, &block) new(*args, &block).save! end |
.find(*args) ⇒ Object
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 |
# File 'lib/strokedb/document.rb', line 370 def self.find(*args) store = nil if (txns = Thread.current[:strokedb_transactions]) && !txns.nil? && !txns.empty? store = txns.last else if args.empty? || args.first.is_a?(String) || args.first.is_a?(Hash) || args.first.nil? store = StrokeDB.default_store else store = args.shift end end raise NoDefaultStoreError.new unless store query = args.first case query when UUID_RE store.find(query) when Hash store.search(query) else raise ArgumentError, "use UUID or query to find document(s)" end end |
.from_raw(store, raw_slots, opts = {}, &block) ⇒ Object
Creates a document from a serialized representation
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
# File 'lib/strokedb/document.rb', line 342 def self.from_raw(store, raw_slots, opts = {}, &block) #:nodoc: doc = new(store, raw_slots, true, &block) (store, raw_slots['meta']).each do || unless doc.is_a? doc.extend() end end unless opts[:skip_callbacks] doc.send! :execute_callbacks, :on_initialization doc.send! :execute_callbacks, :on_load end doc end |
Instance Method Details
#+(document) ⇒ Object
Instantiate a composite document
498 499 500 501 502 |
# File 'lib/strokedb/document.rb', line 498 def +(document) original, target = [to_raw, document.to_raw].map{ |raw| raw.except(*%w(uuid version previous_version)) } Document.new(@store, original.merge(target).merge(:uuid => Util.random_uuid), true) end |
#==(doc) ⇒ Object
:nodoc:
556 557 558 559 560 561 562 563 564 565 566 |
# File 'lib/strokedb/document.rb', line 556 def ==(doc) #:nodoc: case doc when Document, DocumentReferenceValue doc = doc.load if doc.kind_of? DocumentReferenceValue # we make a quick UUID check here to skip two heavy to_raw calls doc.uuid == uuid && doc.to_raw == to_raw else false end end |
#[](slotname) ⇒ Object
Get slot value by its name:
document[:slot_1]
If slot was not found, it will return nil
205 206 207 208 |
# File 'lib/strokedb/document.rb', line 205 def [](slotname) slotname = slotname.document.uuid if (slotname.is_a?(Meta) && slotname.is_a?(Module)) || (slotname == Meta) @slots[slotname.to_s].value rescue nil end |
#[]=(slotname, value) ⇒ Object
Set slot value by its name:
document[:slot_1] = "some value"
215 216 217 218 219 220 221 222 223 |
# File 'lib/strokedb/document.rb', line 215 def []=(slotname, value) slotname = slotname.document.uuid if (slotname.is_a?(Meta) && slotname.is_a?(Module)) || (slotname == Meta) slotname = slotname.to_s (@slots[slotname] ||= Slot.new(self, slotname)).value = value update_version!(slotname) value end |
#__reference__ ⇒ Object
:nodoc:
552 553 554 |
# File 'lib/strokedb/document.rb', line 552 def __reference__ #:nodoc: "@##{uuid}.#{version}" end |
#add_callback(cbk) ⇒ Object
:nodoc:
601 602 603 604 605 606 607 608 609 610 611 612 |
# File 'lib/strokedb/document.rb', line 601 def add_callback(cbk) #:nodoc: name, uid = cbk.name, cbk.uid callbacks[name] ||= [] # if uid is specified, previous callback with the same uid is deleted if uid && old_cb = callbacks[name].find{ |cb| cb.uid == uid } callbacks[name].delete old_cb end callbacks[name] << cbk end |
#delete! ⇒ Object
20 21 22 23 24 25 |
# File 'lib/strokedb/document/delete.rb', line 20 def delete! raise DocumentDeletionError, "can't delete non-head document" unless head? << DeletedDocument save! make_immutable! end |
#diff(from) ⇒ Object
Creates Diff document from from
document to this document
document.diff(original_document) #=> #<StrokeDB::Diff added_slots: {"b"=>2}, from: #<Doc a: 1>, removed_slots: {"a"=>1}, to: #<Doc b: 2>, updated_slots: {}>
267 268 269 |
# File 'lib/strokedb/document.rb', line 267 def diff(from) Diff.new(store, :from => from, :to => self) end |
#eql?(doc) ⇒ Boolean
:nodoc:
568 569 570 |
# File 'lib/strokedb/document.rb', line 568 def eql?(doc) #:nodoc: self == doc end |
#has_slot?(slotname) ⇒ Boolean
Checks slot presence. Unlike Document#slotnames it allows you to find even ‘virtual slots’ that could be computed runtime by associations or when_slot_found
callbacks
document.has_slot?(:slotname)
231 232 233 234 235 236 237 |
# File 'lib/strokedb/document.rb', line 231 def has_slot?(slotname) v = send(slotname) (v.nil? && slotnames.include?(slotname.to_s)) ? true : !!v rescue SlotNotFoundError false end |
#hash ⇒ Object
documents are hashed by their UUID
573 574 575 |
# File 'lib/strokedb/document.rb', line 573 def hash #:nodoc: uuid.hash end |
#head? ⇒ Boolean
Returns true
if this document is a latest version of document being saved to a respective store
411 412 413 414 |
# File 'lib/strokedb/document.rb', line 411 def head? return false if new? || is_a?(VersionedDocument) store.head_version(uuid) == version end |
#make_immutable! ⇒ Object
577 578 579 580 |
# File 'lib/strokedb/document.rb', line 577 def make_immutable! extend ImmutableDocument self end |
#marshal_dump ⇒ Object
:nodoc:
85 86 87 |
# File 'lib/strokedb/document.rb', line 85 def marshal_dump #:nodoc: (@new ? '1' : '0') + (@saved ? '1' : '0') + to_raw.to_json end |
#marshal_load(content) ⇒ Object
:nodoc:
89 90 91 92 93 94 |
# File 'lib/strokedb/document.rb', line 89 def marshal_load(content) #:nodoc: @callbacks = {} initialize_raw_slots(JSON.parse(content[2,content.length])) @saved = content[1,1] == '1' @new = content[0,1] == '1' end |
#meta ⇒ Object
Returns document’s metadocument (if any). In case if document has more than one metadocument, it will combine all metadocuments into one ‘virtual’ metadocument
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 |
# File 'lib/strokedb/document.rb', line 472 def unless (m = self[:meta]).kind_of? Array # simple case return m || Document.new(@store) end return m.first if m.size == 1 mm = m.clone = mm.shift.clone names = [:name].split(',') rescue [] mm.each do || = .clone += names << .name if [:name] end .name = names.uniq.join(',') .make_immutable! end |
#metas ⇒ Object
Should be used to add metadocuments on the fly:
document. << Buyer
document. << Buyer.document
Please not that it accept both meta modules and their documents, there is no difference
512 513 514 |
# File 'lib/strokedb/document.rb', line 512 def @metas ||= Metas.new(self) end |
#mutable? ⇒ Boolean
582 583 584 |
# File 'lib/strokedb/document.rb', line 582 def mutable? true end |
#new? ⇒ Boolean
Returns true
if this is a document that has never been saved.
403 404 405 |
# File 'lib/strokedb/document.rb', line 403 def new? !!@new end |
#pretty_print ⇒ Object Also known as: to_s, inspect
:nodoc:
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
# File 'lib/strokedb/document.rb', line 271 def pretty_print #:nodoc: slots = to_raw.except('meta') s = is_a?(ImmutableDocument) ? "#<^" : "#<" Util.catch_circular_reference(self) do if self[:meta] && name = [:name] s << "#{name} " else s << "Doc " end slots.keys.sort.each do |k| if %w(version previous_version).member?(k) && v = self[k] s << "#{k}: #{v[0,4]}..., " else if k.match(/^#{UUID_RE}$/) s << "[#{store.find(k).name}]: #{self[k].inspect}, " rescue s << "#{k}: #{self[k].inspect}, " else s << "#{k}: #{self[k].inspect}, " end end end s.chomp!(', ') s.chomp!(' ') s << ">" end s rescue Util::CircularReferenceCondition "#(#{(self[:meta] ? "#{}" : "Doc")} #{('@#'+uuid)[0,5]}...)" end |
#previous_version ⇒ Object
Returns document’s previous version (which is stored in previous_version
slot)
537 538 539 |
# File 'lib/strokedb/document.rb', line 537 def previous_version self[:previous_version] end |
#raw_uuid ⇒ Object
:nodoc:
530 531 532 |
# File 'lib/strokedb/document.rb', line 530 def raw_uuid #:nodoc: @raw_uuid ||= uuid.to_raw_uuid end |
#reload ⇒ Object
Reloads head of the same document from store. All unsaved changes will be lost!
396 397 398 |
# File 'lib/strokedb/document.rb', line 396 def reload new? ? self : store.find(uuid) end |
#remove_slot!(slotname) ⇒ Object
Removes slot
document.remove_slot!(:slotname)
244 245 246 247 248 249 250 251 |
# File 'lib/strokedb/document.rb', line 244 def remove_slot!(slotname) slotname = slotname.to_s @slots.delete slotname update_version! slotname nil end |
#reverse_update_slots(hash) ⇒ Object
Updates nil/false slots with a specified hash
and returns itself. Already set slots are not modified (||=
is used). Acts like hash1.reverse_merge(hash2)
(hash2.merge(hash1)
).
456 457 458 459 460 461 |
# File 'lib/strokedb/document.rb', line 456 def reverse_update_slots(hash) hash.each do |k, v| self[k] ||= v end self end |
#reverse_update_slots!(hash) ⇒ Object
Same as reverse_update_slots, but also saves the document.
464 465 466 |
# File 'lib/strokedb/document.rb', line 464 def reverse_update_slots!(hash) reverse_update_slots(hash).save! end |
#save!(perform_validation = true) ⇒ Object
Saves the document. If validations do not pass, InvalidDocumentError exception is raised.
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 |
# File 'lib/strokedb/document.rb', line 420 def save!(perform_validation = true) execute_callbacks :before_save if perform_validation raise InvalidDocumentError.new(self) unless valid? end execute_callbacks :after_validation store.save!(self) @new = false @saved = true execute_callbacks :after_save self end |
#slotnames ⇒ Object
Returns an Array
of explicitely defined slots
document.slotnames #=> ["version","name","language","authors"]
258 259 260 |
# File 'lib/strokedb/document.rb', line 258 def slotnames @slots.keys end |
#store ⇒ Object
77 78 79 80 81 82 83 |
# File 'lib/strokedb/document.rb', line 77 def store if (txns = Thread.current[:strokedb_transactions]) && !txns.nil? && !txns.empty? txns.last else @store end end |
#to_json ⇒ Object
Returns string with Document’s JSON representation
311 312 313 |
# File 'lib/strokedb/document.rb', line 311 def to_json to_raw.to_json end |
#to_optimized_raw ⇒ Object
:nodoc:
335 336 337 |
# File 'lib/strokedb/document.rb', line 335 def to_optimized_raw #:nodoc: __reference__ end |
#to_raw ⇒ Object
Primary serialization
325 326 327 328 329 330 331 332 333 |
# File 'lib/strokedb/document.rb', line 325 def to_raw #:nodoc: raw_slots = {} @slots.each_pair do |k,v| raw_slots[k.to_s] = v.to_raw end raw_slots.to_raw end |
#to_xml(opts = {}) ⇒ Object
Returns string with Document’s XML representation
318 319 320 |
# File 'lib/strokedb/document.rb', line 318 def to_xml(opts = {}) to_raw.to_xml({ :root => 'document', :dasherize => true }.merge(opts)) end |
#update_slots(hash) ⇒ Object
Updates slots with a specified hash
and returns itself.
439 440 441 442 443 444 |
# File 'lib/strokedb/document.rb', line 439 def update_slots(hash) hash.each do |k, v| send("#{k}=", v) unless self[k] == v end self end |
#update_slots!(hash) ⇒ Object
Same as update_slots, but also saves the document.
447 448 449 |
# File 'lib/strokedb/document.rb', line 447 def update_slots!(hash) update_slots(hash).save! end |
#uuid ⇒ Object
Return document’s uuid
526 527 528 |
# File 'lib/strokedb/document.rb', line 526 def uuid @uuid ||= self[:uuid] end |
#version ⇒ Object
Returns document’s version (which is stored in version
slot)
519 520 521 |
# File 'lib/strokedb/document.rb', line 519 def version self[:version] end |
#version=(v) ⇒ Object
:nodoc:
541 542 543 |
# File 'lib/strokedb/document.rb', line 541 def version=(v) #:nodoc: self[:version] = v end |