SimplyCouch

Gem Version License

Simple CouchDB ORM for Rails — ActiveModel-compliant, driver-agnostic.

Zero driver dependencies. Your app brings its own CouchDB client (couchrest, couchbase, etc.).

History

SimplyCouch is based on simply_stored (140 ★), created by Mathias Meyer and Jonathan Weiss at Peritor Consulting in Berlin (~2010). simply_stored was a convenience layer on top of CouchPotato.

This fork evolved over a decade with substantial additions — pagination, ancestry trees, embedded documents, include relations, multi-database support, and a migration toward ActiveModel. In 2026, the CouchPotato dependency was fully removed (~930 lines of view system + persistence ported directly into the gem), couchrest was removed from the gemspec, and the gem was renamed to SimplyCouch as its own project.

Where are the original authors? Jonathan Weiss (jweiss) co-founded Scalarium and is active in the Berlin tech scene. Mathias Meyer (roidrage) moved to Amazon Web Services Germany. Peritor's webistrano (868 ★) was widely used in the Capistrano era.

Installation

gem 'simply_couch'

Or from git:

gem 'simply_couch', git: 'https://github.com/bterkuile/simply_couch.git'

You must also add a CouchDB client to your Gemfile (the gem doesn't force one):

gem 'couchrest'  # or couchbase, or any CouchDB HTTP client

Quick Start

class User
  include SimplyCouch::Model

  property :name
  property :email
  property :active, type: Boolean
  property :last_login, type: Time
  property :tags, type: Array

  has_many :posts
  belongs_to :company

  view :by_name, key: :name
  view :active_by_created, key: :created_at, conditions: 'doc.active == true'
end

class Post
  include SimplyCouch::Model

  property :title
  property :body

  belongs_to :user
end

# CRUD
user = User.create(name: 'Alice', email: 'alice@example.com', active: true)
user.update(name: 'Alice B.')
user.destroy

# Queries
User.find_by_name('Alice')
User.find_all_by_active(true)
User.active_by_created(descending: true)
User.all(page: 1, per_page: 40)

# Associations
user.posts                     # => [Post, ...]
user.posts(limit: 5, order: :desc)
user.post_count                # => 42

Features

Properties & Type Casting

property :name
property :age,        type: Integer
property :price,      type: Float
property :active,     type: Boolean   # stored as true/false
property :last_login, type: Time
property :tags,       type: Array
property :metadata,   type: Hash

Associations

belongs_to, has_many, has_many_embedded, has_and_belongs_to_many, has_one, embedded_in.

class Post
  include SimplyCouch::Model
  has_many :comments, dependent: :destroy
  has_many :authors, through: :comments, source: :user
  belongs_to :category
end

class User
  include SimplyCouch::Model
  has_and_belongs_to_many :networks, storing_keys: true
end

Validations

Standard ActiveModel validations plus a containment validator for array properties:

class Page
  include SimplyCouch::Model
  property :categories
  validates_containment_of :categories, in: %w[news blog docs]
end

Callbacks

before_save, after_save, before_create, after_create, before_destroy, after_destroy — all standard ActiveModel callbacks.

Views

CouchDB views are auto-generated from your model's property declarations. JavaScript only (Erlang dropped in 2026 port).

view :by_status, key: :status
view :published, key: :created_at, conditions: 'doc.status == "published"'
view :by_tags, key: :tags  # array properties work too

Custom views and raw map/reduce are supported via view spec classes.

Pagination

Post.all(page: 2, per_page: 25)
User.active_by_created(page: 1, per_page: 50, descending: true)

Soft Delete

class Document
  include SimplyCouch::Model
  enable_soft_delete  # defaults to :deleted_at
end

doc = Document.create(title: 'draft')
doc.destroy
Document.all                          # => [] (soft-deleted filtered out)
Document.all(with_deleted: true)      # => [doc] (recoverable)

Ancestry (Tree Structures)

class Page
  include SimplyCouch::Model
  property :title
  has_ancestry
end

# Build trees
parent = Page.create(title: 'Products')
child = Page.create(title: 'Widgets', parent: parent)

# Query trees
Page.roots                    # pages with no parent
Page.full_tree                # entire tree loaded in one query
parent.children               # direct children
parent.descendants            # all descendants (flattened)
child.ancestors               # path to root

# Scoped trees (different trees per property)
has_ancestry by_property: :locale

Dynamic Finders

User.find_by_name('Alice')
User.find_all_by_active(true)
User.count_by_active(true)

Include Relations

Eager-load associations to avoid N+1 queries on CouchDB:

Post.all(include: :user)              # loads users with posts
Post.all(include: [:user, :comments]) # multiple associations

Conflict Resolution

Auto-merge on CouchDB conflicts by default (can be disabled):

User.auto_conflict_resolution_on_save = false  # disable

Multi-Database Support

class ArchivedPost
  include SimplyCouch::Model
  use_database 'http://couchdb:5984/archive'
end

Design Document Splitting

Prevent full view reindexing when adding a new view:

class Post
  include SimplyCouch::Model
  split_design_documents_per_view  # each view → own _design doc

  view :by_user_id, key: :user_id   # → _design/Post_view_by_user_id
  view :by_status,  key: :status    # → _design/Post_view_by_status
end

Without splitting, changing any view reindexes all views. On large databases, this can take hours. With splitting, only the new/changed view reindexes.

Attachments

SimplyCouch supports two attachment strategies — use the one that fits your use case:

1. CouchDB Native Inline Attachments

class Invoice
  include SimplyCouch::Model
  include SimplyCouch::Model::Attachments
end

invoice.put_attachment('receipt.pdf', file, content_type: 'application/pdf')
invoice.fetch_attachment('receipt.pdf')
invoice.delete_attachment('receipt.pdf')
invoice.attachment_names  # => ['receipt.pdf', 'logo.png']

Attachments are stored inline in the CouchDB document — atomic, no extra storage, replicable.

2. ActiveStorage Compatibility

Coming soon — has_one_attached / has_many_attached backed by CouchDB attachments.

See docs/attachments.md for a detailed comparison of approaches.

Legacy: S3 Attachments

The has_s3_attachment method (using RightAws) exists but is unmaintained and untested. It was part of the original simply_stored and has not been verified in years. Use CouchDB native attachments or ActiveStorage instead.

Dependencies

Gem Version Notes
activemodel >= 6.0 Validations, callbacks, dirty tracking
activesupport >= 6.0 Inflections, callbacks, concern

No CouchDB driver dependency. Add couchrest or couchbase to your app's Gemfile.

Testing

Uses RSpec with an in-memory CouchDB via RockingChair.

bundle exec rspec

License

BSD 2-Clause — see LICENSE.txt

Credits