Class: StrokeDB::Document
- Includes:
- Validations::InstanceMethods
- Defined in:
- lib/document/document.rb,
lib/document/delete.rb,
lib/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
Instance Attribute Summary collapse
-
#callbacks ⇒ Object
readonly
:nodoc:.
-
#store ⇒ 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 = {}) ⇒ 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). -
#reload ⇒ Object
Reloads head of the same document from store.
-
#remove_slot!(slotname) ⇒ Object
Removes slot.
-
#save!(perform_validation = true) ⇒ Object
Saves the document.
-
#slotnames ⇒ Object
Returns an
Array
of explicitely defined slots. -
#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 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!
152 153 154 155 156 157 158 159 160 161 |
# File 'lib/document/document.rb', line 152 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:
527 528 529 530 531 532 533 534 535 536 537 538 539 540 |
# File 'lib/document/document.rb', line 527 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:
67 68 69 |
# File 'lib/document/document.rb', line 67 def callbacks @callbacks end |
#store ⇒ Object (readonly)
:nodoc:
67 68 69 |
# File 'lib/document/document.rb', line 67 def store @store 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
128 129 130 |
# File 'lib/document/document.rb', line 128 def self.create!(*args, &block) new(*args, &block).save! end |
.find(*args) ⇒ Object
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 |
# File 'lib/document/document.rb', line 331 def self.find(*args) store = nil if args.empty? || args.first.is_a?(String) || args.first.is_a?(Hash) store = StrokeDB.default_store else store = args.shift end raise NoDefaultStoreError.new unless store query = args.first case query when UUID_RE store.find(query) when Hash store.search(query) else raise TypeError end end |
.from_raw(store, raw_slots, opts = {}) ⇒ Object
Creates a document from a serialized representation
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 |
# File 'lib/document/document.rb', line 301 def self.from_raw(store, raw_slots, opts = {}) #:nodoc: doc = new(store, raw_slots, true) (store, raw_slots['meta']).each do || unless doc.is_a? doc.extend() .send!(:setup_callbacks, doc) rescue nil 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
443 444 445 446 447 |
# File 'lib/document/document.rb', line 443 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:
497 498 499 500 501 502 503 504 505 506 507 |
# File 'lib/document/document.rb', line 497 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
170 171 172 |
# File 'lib/document/document.rb', line 170 def [](slotname) @slots[slotname.to_s].value rescue nil end |
#[]=(slotname, value) ⇒ Object
Set slot value by its name:
document[:slot_1] = "some value"
179 180 181 182 183 184 185 186 |
# File 'lib/document/document.rb', line 179 def []=(slotname, value) slotname = slotname.to_s (@slots[slotname] ||= Slot.new(self, slotname)).value = value update_version!(slotname) value end |
#__reference__ ⇒ Object
:nodoc:
493 494 495 |
# File 'lib/document/document.rb', line 493 def __reference__ #:nodoc: "@##{uuid}.#{version}" end |
#add_callback(cbk) ⇒ Object
:nodoc:
542 543 544 545 546 547 548 549 550 551 552 553 |
# File 'lib/document/document.rb', line 542 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/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: {}>
230 231 232 |
# File 'lib/document/document.rb', line 230 def diff(from) Diff.new(store, :from => from, :to => self) end |
#eql?(doc) ⇒ Boolean
:nodoc:
509 510 511 |
# File 'lib/document/document.rb', line 509 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)
194 195 196 197 198 199 200 |
# File 'lib/document/document.rb', line 194 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
514 515 516 |
# File 'lib/document/document.rb', line 514 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
368 369 370 371 |
# File 'lib/document/document.rb', line 368 def head? return false if new? || is_a?(VersionedDocument) store.head_version(uuid) == version end |
#make_immutable! ⇒ Object
518 519 520 521 |
# File 'lib/document/document.rb', line 518 def make_immutable! extend ImmutableDocument self end |
#marshal_dump ⇒ Object
:nodoc:
69 70 71 |
# File 'lib/document/document.rb', line 69 def marshal_dump #:nodoc: (@new ? '1' : '0') + (@saved ? '1' : '0') + to_raw.to_json end |
#marshal_load(content) ⇒ Object
:nodoc:
73 74 75 76 77 78 |
# File 'lib/document/document.rb', line 73 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
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 |
# File 'lib/document/document.rb', line 417 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
457 458 459 |
# File 'lib/document/document.rb', line 457 def Metas.new(self) end |
#mutable? ⇒ Boolean
523 524 525 |
# File 'lib/document/document.rb', line 523 def mutable? true end |
#new? ⇒ Boolean
Returns true
if this is a document that has never been saved.
360 361 362 |
# File 'lib/document/document.rb', line 360 def new? !!@new end |
#pretty_print ⇒ Object Also known as: to_s, inspect
:nodoc:
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/document/document.rb', line 234 def pretty_print #:nodoc: slots = to_raw.except('meta') s = is_a?(ImmutableDocument) ? "#<(imm)" : "#<" 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.gsub(/^(0)+/,'')[0,4]}..., " else s << "#{k}: #{self[k].inspect}, " 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)
478 479 480 |
# File 'lib/document/document.rb', line 478 def previous_version self[:previous_version] end |
#reload ⇒ Object
Reloads head of the same document from store. All unsaved changes will be lost!
353 354 355 |
# File 'lib/document/document.rb', line 353 def reload new? ? self : store.find(uuid) end |
#remove_slot!(slotname) ⇒ Object
Removes slot
document.remove_slot!(:slotname)
207 208 209 210 211 212 213 214 |
# File 'lib/document/document.rb', line 207 def remove_slot!(slotname) slotname = slotname.to_s @slots.delete slotname update_version! slotname nil end |
#save!(perform_validation = true) ⇒ Object
Saves the document. If validations do not pass, InvalidDocumentError exception is raised.
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 |
# File 'lib/document/document.rb', line 377 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"]
221 222 223 |
# File 'lib/document/document.rb', line 221 def slotnames @slots.keys end |
#to_json ⇒ Object
Returns string with Document’s JSON representation
270 271 272 |
# File 'lib/document/document.rb', line 270 def to_json to_raw.to_json end |
#to_optimized_raw ⇒ Object
:nodoc:
294 295 296 |
# File 'lib/document/document.rb', line 294 def to_optimized_raw #:nodoc: __reference__ end |
#to_raw ⇒ Object
Primary serialization
284 285 286 287 288 289 290 291 292 |
# File 'lib/document/document.rb', line 284 def to_raw #:nodoc: raw_slots = {} @slots.each_pair do |k,v| raw_slots[k.to_s] = v.to_raw end raw_slots end |
#to_xml(opts = {}) ⇒ Object
Returns string with Document’s XML representation
277 278 279 |
# File 'lib/document/document.rb', line 277 def to_xml(opts = {}) to_raw.to_xml({ :root => 'document', :dasherize => true }.merge(opts)) end |
#update_slots(hash) ⇒ Object
Updates slots with specified hash
and returns itself.
398 399 400 401 402 403 404 |
# File 'lib/document/document.rb', line 398 def update_slots(hash) hash.each do |k, v| self[k] = v end self end |
#update_slots!(hash) ⇒ Object
Same as update_slots, but also saves the document.
409 410 411 |
# File 'lib/document/document.rb', line 409 def update_slots!(hash) update_slots(hash).save! end |
#uuid ⇒ Object
Return document’s uuid
471 472 473 |
# File 'lib/document/document.rb', line 471 def uuid @uuid ||= self[:uuid] end |
#version ⇒ Object
Returns document’s version (which is stored in version
slot)
464 465 466 |
# File 'lib/document/document.rb', line 464 def version self[:version] end |
#version=(v) ⇒ Object
:nodoc:
482 483 484 |
# File 'lib/document/document.rb', line 482 def version=(v) #:nodoc: self[:version] = v end |