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