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 => '[email protected]' }, '123' )
doc.email # '[email protected]'
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': '[email protected]',
# }

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.

  • rename

Rename keys. See Migration section.

  • migrate

Migrate the value of a key. This gets triggered when Document is initialized. See Migration 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 = '[email protected]'
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 => '[email protected]' } )
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.

You can also migrate the values. The migrate directive is trigger when Document is initialized, but after the rename. This is useful if you have Documents whose values are in the old format and you want to convert them.

class User < CouchPillow::Document
  rename :nickname => :nicknames

  attribute :nicknames

  migrate :nicknames do |v|
    v.class == String ? [v] : v
  end
end
u = User.new( { :nickname => 'jdoe' } )
u.nicknames # ['jdoe']

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 => '[email protected]').map do |v|
    User.new(v.doc, v.id)
  end