<commit_before>= ODB: A Ruby Object Database

== Synopsis

ODB attempts to be a transparent persistence layer in the Ruby interpreter
inspired by Object Oriented Databases like Python's Zope Object Database and
more recently, MagLev. The goal is to make object persistence as invisible
as possible in Ruby, though full transparency (as MagLev attempts) can
never really be possible.

== Features

ODB supports:

* Transparent Persistent Objects
* ACID Transactions
* Implicit Key Names
* Lazy Loading of Attributes

== Quick Start

ODB looks very similar to a standard key-value store database such as 
Memcached, Redis, or Tokyo Tyrant. For instance, we create a database
and access objects by key names as follows:

  require 'odb'
  
  db = ODB.new
  db[:key] = "Hello"
  # and now...
  db[:key] == "Hello" # => true
  
This is very basic usage. Unlike key-value stores, however, it is possible
to store and access objects implicitly, without directly assigning them
a key. We will see this more with persistent objects
  
== Transparent Persistent Objects

Persistent objects allow a more transparent form of storing objects. To
mark a method as persistent, simply include the +Persistent+ module:

  class Post
    include ODB::Persistent
    
    attr_accessor :title, :body
    
    def initialize(title, body)
      super() # needed for Persistent's constructor
      @title, @body = title, body
    end
  end
  
You can now benefit from transparent saves in transactions.

== ACID Transactions

ODB supports very basic transactions right now. A simple example of a
transaction is:

  db = ODB.new
  db.transaction do
    post = Post.new("foo", "bar")
  end
  
At this point, the +Post+ object will now be persisted either in memory or
on disk (whichever datastore you use). This implicit storage only works for
newly created objects, not modified ones. If post were to be modified, it
would need to be marked for a save. Hopefully this API can also become more
transparent, though it seems unlikely with Ruby's object model. To save
a modified object, queue it:

  db.transaction do
    post.title = "something else"
    post.__queue__
  end
  
Transactions occur in an all-or-nothing fashion, so if an exception is raised
in the middle of a save, nothing will be persisted:

  db.transaction do
    db[:post1] = Post.new
    raise "exception!"
    db[:post2] = Post.new
  end
  
  db[:post1] # => nil
  db[:post2] # => nil
  
== Implict Key Names

You may be wondering how to read the post back out after it's been persisted
to disk. This is why symbolic key-names are used in key-value stores. We can
use the following to save the object to a specific symbolic name as we
did in the first example:

  db[:key] = post
  
However we can create generalized key names for any new post object, to
implicitly key any saved post object by a symbolic determinate name. All
we need to do is override +__serialize_key__+ to return a Symbol:

  class Post
    def __serialize_key__; title.to_sym end
  end

Here we return the title, since we assume it is unique for the purpose of
this example. Now when we do:

  posts = []
  db.transaction do
    posts << Post.new("title1", "body1")
    posts << Post.new("title2", "body2")
  end
  
We can access both objects as:

  db[:title1] # => #<Post:0x000001011dfe30 @title="title1", @body="body1">
  db[:title2] # => #<Post:0x00000101379550 @title="title2", @body="body2">
  
== Lazy Loading of Attributes

ODB can perform lazy-loading on any standard Ruby attribute. When an object
is deserialized, each instance variable is checked to see if there is a 
zero-argument method by the same name. If so, it (temporarily) replaces the
method with a stub that will lazily load the object when the attribute is
read. Consider this example:

  class Post
    include ODB::Persistent
    attr_accessor :title, :comments

    def initialize(title) 
      super()
      @comments = [] 
      @title = title
    end
  
    def __serialize_key__; @title.to_sym end
  end

  # Create a post with 100 comments
  db = ODB.new
  db.transaction do
    post = Post.new("hello")
    100.times {|i| post.comments << "This is comment ##{i}" }
  end

  db.clear_cache # force de-serialization 
  p db[:hello]
  # => #<Post:0x0b149008 @comments=(lazyload value), @title=(lazyload value)>
  p db[:hello].title 
  # => "hello"
  p db[:hello].comments
  # => ["This is comment #0", "This is comment #1", ...]

== The Supported Data Stores

Currently ODB has support for in-memory, on-disk and JSON (also on-disk)
data stores. One benefit is that it is possible to write a wrapper for any
key-value data store such as memcached, redis, mongo, etc.. To implement
a data store, you just need to implement the +read_object+ and +write_object+
methods in the +Database+ class.

== Copyright & License

Copyright <NAME> © 2009, licensed under the {file:LICENSE MIT License}<commit_msg>Update documentation on lazy loading<commit_after>= ODB: A Ruby Object Database

== Synopsis

ODB attempts to be a transparent persistence layer in the Ruby interpreter
inspired by Object Oriented Databases like Python's Zope Object Database and
more recently, MagLev. The goal is to make object persistence as invisible
as possible in Ruby, though full transparency (as MagLev attempts) can
never really be possible.

== Features

ODB supports:

* Transparent Persistent Objects
* ACID Transactions
* Implicit Key Names
* Lazy Loading of Attributes

== Quick Start

ODB looks very similar to a standard key-value store database such as 
Memcached, Redis, or Tokyo Tyrant. For instance, we create a database
and access objects by key names as follows:

  require 'odb'
  
  db = ODB.new
  db[:key] = "Hello"
  # and now...
  db[:key] == "Hello" # => true
  
This is very basic usage. Unlike key-value stores, however, it is possible
to store and access objects implicitly, without directly assigning them
a key. We will see this more with persistent objects
  
== Transparent Persistent Objects

Persistent objects allow a more transparent form of storing objects. To
mark a method as persistent, simply include the +Persistent+ module:

  class Post
    include ODB::Persistent
    
    attr_accessor :title, :body
    
    def initialize(title, body)
      super() # needed for Persistent's constructor
      @title, @body = title, body
    end
  end
  
You can now benefit from transparent saves in transactions.

== ACID Transactions

ODB supports very basic transactions right now. A simple example of a
transaction is:

  db = ODB.new
  db.transaction do
    post = Post.new("foo", "bar")
  end
  
At this point, the +Post+ object will now be persisted either in memory or
on disk (whichever datastore you use). This implicit storage only works for
newly created objects, not modified ones. If post were to be modified, it
would need to be marked for a save. Hopefully this API can also become more
transparent, though it seems unlikely with Ruby's object model. To save
a modified object, queue it:

  db.transaction do
    post.title = "something else"
    post.__queue__
  end
  
Transactions occur in an all-or-nothing fashion, so if an exception is raised
in the middle of a save, nothing will be persisted:

  db.transaction do
    db[:post1] = Post.new
    raise "exception!"
    db[:post2] = Post.new
  end
  
  db[:post1] # => nil
  db[:post2] # => nil
  
== Implict Key Names

You may be wondering how to read the post back out after it's been persisted
to disk. This is why symbolic key-names are used in key-value stores. We can
use the following to save the object to a specific symbolic name as we
did in the first example:

  db[:key] = post
  
However we can create generalized key names for any new post object, to
implicitly key any saved post object by a symbolic determinate name. All
we need to do is override +__serialize_key__+ to return a Symbol:

  class Post
    def __serialize_key__; title.to_sym end
  end

Here we return the title, since we assume it is unique for the purpose of
this example. Now when we do:

  posts = []
  db.transaction do
    posts << Post.new("title1", "body1")
    posts << Post.new("title2", "body2")
  end
  
We can access both objects as:

  db[:title1] # => #<Post:0x000001011dfe30 @title="title1", @body="body1">
  db[:title2] # => #<Post:0x00000101379550 @title="title2", @body="body2">
  
== Lazy Loading of Attributes

ODB can perform lazy-loading on any standard Ruby attribute. When an object
is deserialized, each instance variable is checked to see if there is a 
zero-argument method by the same name. If so, it (temporarily) replaces the
method with a stub that will lazily load the object when the attribute is
read. Consider this example:

  class Post
    include ODB::Persistent
    attr_accessor :title, :comments

    def initialize(title) 
      super()
      @comments = [] 
      @title = title
    end
  
    def __serialize_key__; @title.to_sym end
  end

  # Create a post with 100 comments
  db = ODB.new
  db.transaction do
    post = Post.new("hello")
    100.times {|i| post.comments << "This is comment ##{i}" }
  end

  db.clear_cache # force de-serialization 
  p db[:hello]
  # => #<Post:0x0b149008 @comments=(lazyload value), @title=(lazyload value)>
  p db[:hello].title 
  # => "hello"
  p db[:hello].comments
  # => ["This is comment #0", "This is comment #1", ...]
  
One of the benefits of shallow reads (a.k.a. lazy loading) is that you're not
immediately deserializing associations. If you had a complex object structure
that held cyclic associations or associations to a root object in your tree,
you could end up de-serializing the entire database in one read, which would
obviously not be a good thing. ODB's lazy attributes allow reads on an object
to be very fast while maintaining associations transparently.

== The Supported Data Stores

Currently ODB has support for in-memory, on-disk and JSON (also on-disk)
data stores. One benefit is that it is possible to write a wrapper for any
key-value data store such as memcached, redis, mongo, etc.. To implement
a data store, you just need to implement the +read_object+ and +write_object+
methods in the +Database+ class.

== Copyright & License

Copyright <NAME> © 2009, licensed under the {file:LICENSE MIT License}
{'fraction_non_alphanumeric': 0.07426513529297256, 'fraction_numerical': 0.01187463500097333, 'mean_word_length': 3.6725784447476126, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6217704', 'n_tokens_mistral': 3060, 'n_tokens_neox': 2894, 'n_words': 1415}