Class: Gitgo::Document
- Inherits:
-
Object
- Object
- Gitgo::Document
- Defined in:
- lib/gitgo/document.rb,
lib/gitgo/document/invalid_document_error.rb
Overview
Document represents the data model of Gitgo, and provides high(er)-level access to documents stored in a Repo. Content and data consistency constraints are enforced on Document and not on Repo. As such, Document should be the only way casual users enter data into a Repo.
Usage
For the most part Document behaves like a standard ORM model. The primary gotcha revolves around setting documents into the git repository and exists to prevent the creation of unnecessary git objects.
Unlike you would expect, two method calls are required to store a document:
a = Document.new(:content => 'a')
a.save
a.create
The save method sets the document data into the git repo as a blob and records the blob sha as a unique identifier for the document. The create method is what indicates the document is the head of a new document graph. Simply calling save is not enough (indeed the result of save is a hanging blob that can be gc’d by git).
The link and update methods are used instead of create to associate new documents into an existing graph. For example:
b = Document.new(:content => 'b')
b.save
a.link(b)
Calling create prevents a document from being linked into another graph and vice-versa; the intent is that a given document only belongs to one document graph. This constraint is only enforced at the Document level and represents the main reason why using repo directy is a no-no.
Additionally, as in the command-line git workflow, newly added documents are not actually committed to a repo until commit is called.
Direct Known Subclasses
Defined Under Namespace
Classes: InvalidDocumentError
Constant Summary collapse
Class Attribute Summary collapse
-
.types ⇒ Object
readonly
Returns a hash registry mapping a type string to a Document class.
-
.validators ⇒ Object
readonly
A hash of (key, validator) pairs mapping attribute keys to a validation method.
Instance Attribute Summary collapse
-
#attrs ⇒ Object
readonly
A hash of the document attributes, corresponding to what is stored in the repo.
-
#repo ⇒ Object
readonly
The repo this document belongs to.
-
#sha ⇒ Object
readonly
The document sha, unset until the document is saved.
Class Method Summary collapse
-
.[](sha) ⇒ Object
Reads the specified document from the repo cache and casts it into an instance as per cast.
-
.cast(attrs, sha) ⇒ Object
Casts the attributes hash into a document instance.
-
.create(attrs = {}) ⇒ Object
Creates a new document with the attrs.
- .delete(sha) ⇒ Object
-
.find(all = {}, any = nil) ⇒ Object
Finds all documents matching the any and all criteria.
-
.inherited(base) ⇒ Object
:nodoc:.
-
.read(sha) ⇒ Object
Reads the specified document and casts it into an instance as per cast.
-
.repo ⇒ Object
Returns the Repo currently in scope (see Repo.current).
-
.save(attrs = {}) ⇒ Object
Creates a new document with the attributes and saves.
-
.type ⇒ Object
Returns the type string for self.
-
.update(old_doc, attrs = {}) ⇒ Object
Updates and indexes the old document with new attributes.
-
.update_index(reindex = false) ⇒ Object
Performs a partial update of the document index.
Instance Method Summary collapse
-
#==(another) ⇒ Object
This is a thin equality – use with caution.
-
#[](key) ⇒ Object
Gets the specified attribute.
-
#[]=(key, value) ⇒ Object
Sets the specified attribute.
- #active?(commit = nil) ⇒ Boolean
- #author(cast = true) ⇒ Object
- #author=(author) ⇒ Object
- #commit! ⇒ Object
-
#create ⇒ Object
Stores self as a new graph head.
- #date(cast = true) ⇒ Object
- #date=(date) ⇒ Object
-
#delete ⇒ Object
Deletes self.
- #each_index ⇒ Object
- #errors ⇒ Object
- #graph ⇒ Object
- #graph_head ⇒ Object
- #graph_head? ⇒ Boolean
- #graph_head_idx ⇒ Object
- #idx ⇒ Object
-
#index ⇒ Object
Returns the repo index.
- #indexes ⇒ Object
-
#initialize(attrs = {}, repo = nil, sha = nil) ⇒ Document
constructor
A new instance of Document.
- #initialize_copy(orig) ⇒ Object
- #inspect ⇒ Object
-
#link(child) ⇒ Object
Links the child document to self.
- #link_to(*parents) ⇒ Object
- #merge(attrs) ⇒ Object
- #merge!(attrs) ⇒ Object
- #node ⇒ Object
- #normalize ⇒ Object
- #normalize! ⇒ Object
- #reindex ⇒ Object
- #reset(new_sha = sha) ⇒ Object
-
#save ⇒ Object
Validates and saves attrs into the repo, then resets self with the resulting sha.
-
#saved? ⇒ Boolean
Returns true if sha is set.
- #summary ⇒ Object
- #tags ⇒ Object
-
#update(new_doc) ⇒ Object
Updates self with the new document.
- #update_to(*old_docs) ⇒ Object
- #validate(normalize = true) ⇒ Object
Constructor Details
Class Attribute Details
.types ⇒ Object (readonly)
Returns a hash registry mapping a type string to a Document class. Document itself is registered as the nil type. Types also includes reverse mappings for a Document class to it’s type string.
50 51 52 |
# File 'lib/gitgo/document.rb', line 50 def types @types end |
.validators ⇒ Object (readonly)
A hash of (key, validator) pairs mapping attribute keys to a validation method. Not all attributes will have a validator whereas some attributes share the same validation method.
55 56 57 |
# File 'lib/gitgo/document.rb', line 55 def validators @validators end |
Instance Attribute Details
#attrs ⇒ Object (readonly)
A hash of the document attributes, corresponding to what is stored in the repo.
301 302 303 |
# File 'lib/gitgo/document.rb', line 301 def attrs @attrs end |
#repo ⇒ Object (readonly)
The repo this document belongs to.
297 298 299 |
# File 'lib/gitgo/document.rb', line 297 def repo @repo end |
#sha ⇒ Object (readonly)
The document sha, unset until the document is saved.
304 305 306 |
# File 'lib/gitgo/document.rb', line 304 def sha @sha end |
Class Method Details
.[](sha) ⇒ Object
Reads the specified document from the repo cache and casts it into an instance as per cast. Returns nil if the document doesn’t exist.
110 111 112 113 114 115 |
# File 'lib/gitgo/document.rb', line 110 def [](sha) sha = repo.resolve(sha) attrs = repo[sha] attrs ? cast(attrs, sha) : nil end |
.cast(attrs, sha) ⇒ Object
Casts the attributes hash into a document instance. The document class is determined by resolving the ‘type’ attribute against the types registry.
120 121 122 123 124 |
# File 'lib/gitgo/document.rb', line 120 def cast(attrs, sha) type = attrs['type'] klass = types[type] or raise "unknown type: #{type}" klass.new(attrs, repo, sha) end |
.create(attrs = {}) ⇒ Object
Creates a new document with the attrs. The document is saved, created, and indexed before being returned.
86 87 88 89 90 91 |
# File 'lib/gitgo/document.rb', line 86 def create(attrs={}) update_index doc = save(attrs) doc.create doc end |
.delete(sha) ⇒ Object
178 179 180 181 182 |
# File 'lib/gitgo/document.rb', line 178 def delete(sha) doc = self[sha] doc.delete doc end |
.find(all = {}, any = nil) ⇒ Object
Finds all documents matching the any and all criteria. The any and all inputs are hashes of index values used to filter all possible documents. They consist of (key, value) or (key, [values]) pairs. At least one of pair must match in the any case. All pairs must match in the all case. Specify nil for either array to prevent filtering using that criteria.
See basis for more detail regarding the scope of ‘all documents’ that can be found via find.
167 168 169 170 171 172 173 174 175 176 |
# File 'lib/gitgo/document.rb', line 167 def find(all={}, any=nil) update_index repo.index.select( :basis => basis, :all => all, :any => any, :shas => true ).collect! {|sha| self[sha] } end |
.inherited(base) ⇒ Object
:nodoc:
57 58 59 60 61 |
# File 'lib/gitgo/document.rb', line 57 def inherited(base) # :nodoc: base.instance_variable_set(:@validators, validators.dup) base.instance_variable_set(:@types, types) base.register_as base.to_s.split('::').last.downcase end |
.read(sha) ⇒ Object
Reads the specified document and casts it into an instance as per cast. Returns nil if the document doesn’t exist.
Usage Note
Read will re-read the document directly from the git repository every time it is called. For better performance, use the AGET method which performs the same read but uses the Repo cache if possible.
101 102 103 104 105 106 |
# File 'lib/gitgo/document.rb', line 101 def read(sha) sha = repo.resolve(sha) attrs = repo.read(sha) attrs ? cast(attrs, sha) : nil end |
.repo ⇒ Object
Returns the Repo currently in scope (see Repo.current)
64 65 66 |
# File 'lib/gitgo/document.rb', line 64 def repo Repo.current end |
.save(attrs = {}) ⇒ Object
Creates a new document with the attributes and saves. Saved documents are not automatically associated with a document graph and must be associated with one via create/update/link to be permanently stored in the repo.
77 78 79 80 81 82 |
# File 'lib/gitgo/document.rb', line 77 def save(attrs={}) doc = new(attrs, repo) doc.save doc.reindex doc end |
.type ⇒ Object
Returns the type string for self.
69 70 71 |
# File 'lib/gitgo/document.rb', line 69 def type types[self] end |
.update(old_doc, attrs = {}) ⇒ Object
Updates and indexes the old document with new attributes. The new attributes are merged with the current doc attributes. Returns the new document.
The new document can be used to update other documents, if necessary, as when resolving forks in an update graph:
a = Document.create(:content => 'a')
b = Document.update(a, :content => 'b')
c = Document.update(a, :content => 'c')
d = Document.update(b, :content => 'd')
c.update(d)
a.reset
a.node.versions.uniq # => [d.sha]
143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/gitgo/document.rb', line 143 def update(old_doc, attrs={}) update_index unless old_doc.kind_of?(Document) old_doc = Document[old_doc] end new_doc = old_doc.merge(attrs) new_doc.save new_doc.reindex old_doc.update(new_doc) new_doc end |
.update_index(reindex = false) ⇒ Object
Performs a partial update of the document index. All documents added between the index-head and the repo-head are updated using this method.
Specify reindex to clobber and completely rebuild the index.
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/gitgo/document.rb', line 189 def update_index(reindex=false) index = repo.index index.clear if reindex git_head, index_head = repo.git.head, index.head # if the index is up-to-date save the work of doing diff if git_head.nil? || git_head == index_head return [] end shas = repo.diff(index_head, git_head) shas.each do |source| doc = self[source] doc.reindex repo.each_assoc(source) do |target, type| index.assoc(source, target, type) end end index.write(git_head) shas end |
Instance Method Details
#==(another) ⇒ Object
This is a thin equality – use with caution.
635 636 637 |
# File 'lib/gitgo/document.rb', line 635 def ==(another) saved? && another.kind_of?(Document) ? sha == another.sha : super end |
#[](key) ⇒ Object
Gets the specified attribute.
352 353 354 |
# File 'lib/gitgo/document.rb', line 352 def [](key) attrs[key] end |
#[]=(key, value) ⇒ Object
Sets the specified attribute.
357 358 359 |
# File 'lib/gitgo/document.rb', line 357 def []=(key, value) attrs[key] = value end |
#active?(commit = nil) ⇒ Boolean
392 393 394 395 |
# File 'lib/gitgo/document.rb', line 392 def active?(commit=nil) return true if at.nil? || commit.nil? repo.rev_list(commit).include?(at) end |
#author(cast = true) ⇒ Object
369 370 371 372 373 374 375 |
# File 'lib/gitgo/document.rb', line 369 def (cast=true) = attrs['author'] if cast && .kind_of?(String) = Grit::Actor.from_string() end end |
#author=(author) ⇒ Object
361 362 363 364 365 366 367 |
# File 'lib/gitgo/document.rb', line 361 def () if .kind_of?(Grit::Actor) email = .email = blank?(email) ? .name : "#{.name} <#{email}>".lstrip end self['author'] = end |
#commit! ⇒ Object
619 620 621 622 |
# File 'lib/gitgo/document.rb', line 619 def commit! repo.commit! self end |
#create ⇒ Object
Stores self as a new graph head. Returns self.
509 510 511 512 513 514 515 516 517 |
# File 'lib/gitgo/document.rb', line 509 def create unless saved? raise "cannot create unless saved" end index.create(sha) repo.create(sha) self end |
#date(cast = true) ⇒ Object
384 385 386 387 388 389 390 |
# File 'lib/gitgo/document.rb', line 384 def date(cast=true) date = attrs['date'] if cast && date.kind_of?(String) date = Time.parse(date) end date end |
#date=(date) ⇒ Object
377 378 379 380 381 382 |
# File 'lib/gitgo/document.rb', line 377 def date=(date) if date.respond_to?(:iso8601) date = date.iso8601 end self['date'] = date end |
#delete ⇒ Object
Deletes self. Delete raises an error if unsaved. Returns self.
592 593 594 595 596 597 598 599 600 |
# File 'lib/gitgo/document.rb', line 592 def delete unless saved? raise "cannot delete unless saved" end index.delete(sha) repo.delete(sha) self end |
#each_index ⇒ Object
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 |
# File 'lib/gitgo/document.rb', line 468 def each_index if = attrs['author'] email = Grit::Actor.from_string().email yield('email', blank?(email) ? 'unknown' : email) end if date = attrs['date'] # reformats iso8601 as YYYYMMDD yield('date', "#{date[0,4]}#{date[5,2]}#{date[8,2]}") end if at = attrs['at'] yield('at', at) end if = attrs['tags'] .each do |tag| yield('tags', tag) end end if type = attrs['type'] yield('type', type) end self end |
#errors ⇒ Object
440 441 442 443 444 445 446 447 448 449 450 |
# File 'lib/gitgo/document.rb', line 440 def errors errors = {} self.class.validators.each_pair do |key, validator| begin send(validator, attrs[key]) rescue errors[key] = $! end end errors end |
#graph ⇒ Object
343 344 345 |
# File 'lib/gitgo/document.rb', line 343 def graph @graph ||= repo.graph(graph_head) end |
#graph_head ⇒ Object
338 339 340 341 |
# File 'lib/gitgo/document.rb', line 338 def graph_head idx = graph_head_idx idx ? index.list[idx] : nil end |
#graph_head? ⇒ Boolean
334 335 336 |
# File 'lib/gitgo/document.rb', line 334 def graph_head? graph_head_idx == idx end |
#graph_head_idx ⇒ Object
330 331 332 |
# File 'lib/gitgo/document.rb', line 330 def graph_head_idx index.graph_head_idx(idx) end |
#idx ⇒ Object
326 327 328 |
# File 'lib/gitgo/document.rb', line 326 def idx sha ? index.idx(sha) : nil end |
#index ⇒ Object
Returns the repo index.
322 323 324 |
# File 'lib/gitgo/document.rb', line 322 def index repo.index end |
#indexes ⇒ Object
462 463 464 465 466 |
# File 'lib/gitgo/document.rb', line 462 def indexes indexes = [] each_index {|key, value| indexes << [key, value] } indexes end |
#initialize_copy(orig) ⇒ Object
624 625 626 627 628 |
# File 'lib/gitgo/document.rb', line 624 def initialize_copy(orig) super reset(nil) @attrs = orig.attrs.dup end |
#inspect ⇒ Object
630 631 632 |
# File 'lib/gitgo/document.rb', line 630 def inspect "#<#{self.class}:#{object_id} sha=#{sha.inspect}>" end |
#link(child) ⇒ Object
Links the child document to self. Returns self.
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 |
# File 'lib/gitgo/document.rb', line 556 def link(child) unless saved? raise "cannot link unless saved" end unless child.saved? raise "cannot link to an unsaved document: #{child.inspect}" end child_sha = child.sha if repo.assoc_type(sha, child_sha) == :update raise "cannot link to an update of self: #{sha} -> #{child_sha}" end index.link(sha, child_sha) repo.link(sha, child_sha) child.reset reset end |
#link_to(*parents) ⇒ Object
577 578 579 580 581 582 583 584 585 586 587 588 589 |
# File 'lib/gitgo/document.rb', line 577 def link_to(*parents) graph_heads = parents.collect {|parent| parent.graph_head }.uniq unless graph_heads.length == 1 parents.collect! {|parent| parent.sha } raise "cannot link to unrelated documents: #{parents.inspect}" end parents.each do |parent| parent.link(self) end self end |
#merge(attrs) ⇒ Object
405 406 407 |
# File 'lib/gitgo/document.rb', line 405 def merge(attrs) dup.merge!(attrs) end |
#merge!(attrs) ⇒ Object
409 410 411 412 |
# File 'lib/gitgo/document.rb', line 409 def merge!(attrs) self.attrs.merge!(attrs) self end |
#node ⇒ Object
347 348 349 |
# File 'lib/gitgo/document.rb', line 347 def node graph[sha] end |
#normalize ⇒ Object
414 415 416 |
# File 'lib/gitgo/document.rb', line 414 def normalize dup.normalize! end |
#normalize! ⇒ Object
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 |
# File 'lib/gitgo/document.rb', line 418 def normalize! self. ||= repo.git. self.date ||= Time.now if at = attrs['at'] attrs['at'] = repo.resolve(at) end if = attrs['tags'] = arrayify() .delete_if {|tag| tag.to_s.empty? } attrs['tags'] = end unless type = attrs['type'] default_type = self.class.type attrs['type'] = default_type if default_type end self end |
#reindex ⇒ Object
602 603 604 605 606 607 608 609 610 611 |
# File 'lib/gitgo/document.rb', line 602 def reindex raise "cannot reindex unless saved" unless saved? idx = self.idx each_index do |key, value| index[key][value] << idx end self end |
#reset(new_sha = sha) ⇒ Object
613 614 615 616 617 |
# File 'lib/gitgo/document.rb', line 613 def reset(new_sha=sha) @sha = new_sha @graph = nil self end |
#save ⇒ Object
Validates and saves attrs into the repo, then resets self with the resulting sha. Returns self.
498 499 500 501 |
# File 'lib/gitgo/document.rb', line 498 def save validate reset repo.save(attrs) end |
#saved? ⇒ Boolean
Returns true if sha is set.
504 505 506 |
# File 'lib/gitgo/document.rb', line 504 def saved? sha.nil? ? false : true end |
#summary ⇒ Object
401 402 403 |
# File 'lib/gitgo/document.rb', line 401 def summary sha end |
#tags ⇒ Object
397 398 399 |
# File 'lib/gitgo/document.rb', line 397 def self['tags'] ||= [] end |
#update(new_doc) ⇒ Object
Updates self with the new document. Returns self.
520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 |
# File 'lib/gitgo/document.rb', line 520 def update(new_doc) unless saved? raise "cannot update unless saved" end unless new_doc.saved? raise "cannot update with an unsaved document: #{new_doc.inspect}" end new_sha = new_doc.sha if repo.assoc_type(sha, new_sha) == :link raise "cannot update with a child of self: #{sha} -> #{new_sha}" end index.update(sha, new_sha) repo.update(sha, new_sha) new_doc.reset reset end |
#update_to(*old_docs) ⇒ Object
541 542 543 544 545 546 547 548 549 550 551 552 553 |
# File 'lib/gitgo/document.rb', line 541 def update_to(*old_docs) originals = old_docs.collect {|old_doc| old_doc.node.original }.uniq unless originals.length == 1 old_docs.collect! {|old_doc| old_doc.sha } raise "cannot update unrelated documents: #{old_docs.inspect}" end old_docs.each do |old_doc| old_doc.update(self) end self end |
#validate(normalize = true) ⇒ Object
452 453 454 455 456 457 458 459 460 |
# File 'lib/gitgo/document.rb', line 452 def validate(normalize=true) normalize! if normalize errors = self.errors unless errors.empty? raise InvalidDocumentError.new(self, errors) end self end |