# CouchPillow

CouchPillow is a document integrity tool for Couchbase Documents to make sure
that all current and existing documents can work nicely with the current code.

CouchPillow separates itself from the database drivers, making it light and
independent from the implementation.  Although it is initially designed to work
with Couchbase Server, it can be easily extended to other NoSQL databases, by
creating a driver that `respond_to?` the `set`, `delete`, `replace`, and `get`
methods.


## Features

- Automatic id generation.
- Automatic timestamp.
- Built-in and custom data validations.


## Installation

    gem install couchpillow


## Quick Start

    require 'couchpillow'

    class MyDocument < CouchPillow::Document
      type :my_document
      attribute :stuff
    end

    CouchPillow.db = Couchbase.connect( bucket: 'default', host: 'localhost' )
    doc = CouchPillow::Document.new( { :stuff => 'hello' } )
    doc.save!
    
    # {
    #   '_id': 'my_document::fb579b265cc005c47ff420a5c2a15d2b',
    #   '_type': 'my_document',
    #   '_created_at': '2014-07-04 00:00:00 UTC'
    #   '_updated_at': '2014-07-04 00:00:00 UTC'
    #   'stuff': 'hello',
    # }


Retrieving Documents:

    doc = MyDocument.get('my_document::fb579b265cc005c47ff420a5c2a15d2b')
    doc.stuff # 'hello'

Specifying custom id:

    class User < CouchPillow::Document
      type :user
      attribute :email
    end

    CouchPillow.db = Couchbase.connect( bucket: 'default', host: 'localhost' )
    doc = User.new( { :email => 'john@email.com' }, '123' )
    doc.email # 'john@email.com'
    doc.save!

    # {
    #   '_id': 'user::123',
    #   '_type': 'user',
    #   '_created_at': '2014-07-04 00:00:00 UTC'
    #   '_updated_at': '2014-07-04 00:00:00 UTC'
    #   'email': 'john@email.com',
    # }


### Document-Level Directives

The following are directives that can be used to trigger specific behaviors
at the Document level.

* `type(T)`

  Set the type of the Document.

* `type_prefix(true|false)`

  Default to `true`. If set to false, it removes prefixing document id with
  the document type. Leaving this to true is the recommended behavior to avoid
  id conflicts between different types of documents, especially when custom
  ids are being used.

* `attribute(name, &block)`

  Declares an attribute for this Document. You can specify additional
  directives for each attribute. See Attributes section.

* `db(connection)`

  Sets the database connections. If set once, it will set that connection as
  the primary connection.  Any subsequent calls to this directive will set
  those connections as secondary connections.  See Multiple DB Connections
  section.



### Attributes

Using Attribute Directive:

    class User < CouchPillow::Document
      type :user

      attribute :email do
        required
      end

      attribute :first_name do
        type String
      end
    end

    CouchPillow.db = Couchbase.connect( bucket: 'default', host: 'localhost' )
    doc = User.new( { :first_name => 'John' } )
    doc.save! # raises ValidationError "Attribute 'email' is missing"
    doc.email = 'john@email.com'
    doc.save! # Success!

List of Attribute Directives:

* `required`

  Sets this attribute as required. Will raise an error if attribute is missing
  upon `save!` or `update!`

* `type(T)`

   Sets the type of this attribute. Will perform type check if specified.

* `auto_convert`

  Enables auto-conversion to the specified type. This gets ignored if `type`
  directive is not specified.

* `default(&block)`

  Runs the block to set the default value for this attribute, if it's missing
  or nil. This is triggered on document creation and save.

* `content(&block)`

  Custom validation method to check the value of the attribute. This is useful
  in cases where you only want certain values to be stored (e.g a number
  between 1-10 only)


### TTL Support

TTL is supported by passing options when saving the document. Using the above
example:

    doc = User.new( { :email => 'john@email.com' } )
    doc.save! ttl: 3600


### Multiple DB Connections Support

If you have a model that's accessing a different Couchbase bucket, or a
different Couchbase DB cluster entirely, you can specify the connections
via the `db` directive.  Example:

    CouchPillow.db = Couchbase.connect( bucket: 'default', host: 'localhost' )
    memcached = Couchbase.connect( bucket: 'mymemcache_bucket', host: '128.128.128.128' )

    class User < CouchPillow::Document
      type :user

      attribute :first_name do
        type String
      end
    end

    class Token < CouchPillow::Document
      type :token

      db memcached

      attribute :token do
        type String
      end
    end

    doc = User.new( { :first_name => 'John' } )
    doc.save! # This gets saved to the localhost/bucket

    token = Token.new( token: SecureRandom.uuid )
    doc.save! # This gets saved to the 128.128.128.128/mymemcache_bucket


You can also specify multiple `db` directives. The first time the `db`
directive is called, it sets that connection as the primary connection, as the
above example shows. Any subsequent calls will insert those DB connection as
secondary connections, which will only trigger on write (`save!`, `update!`,
and `delete!`).


    class Token < CouchPillow::Document
      type :token

      db primary_connection
      db migration
      db backup

      attribute :token do
        type String
      end
    end


This can be useful as part of a migration process where you want to save
incoming data to another cluster while keeping the old one active.



### Migration

Using `rename` to rename keys. Useful to maintain document integrity
after a migration.

    class User < CouchPillow::Document
      rename :username, :nickname
      attribute :nickname
    end
    u = User.new( { :username => 'jdoe' } )
    u.nickname # 'jdoe'

Rename triggers per-document basis. You can use this on a separate script
that queries each document in the database and updates them, or you can
simply use this inside your application code, so it only migrates the document
as it reads them.



## Design Docs and Views

Design Docs and Views are outside the scope of CouchPillow.
However, given a design doc named `my_design_doc` and a View named `by_email`,
that returns documents as values, you can easily use it like this:

    CouchPillow.db.design_docs['my_design_doc'].
      by_email(:key => 'john@email.com').map do |v|
        User.new(v.doc, v.id)
      end