SimplyCouch
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.('receipt.pdf', file, content_type: 'application/pdf')
invoice.('receipt.pdf')
invoice.('receipt.pdf')
invoice. # => ['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
- Original simply_stored: Mathias Meyer & Jonathan Weiss at Peritor Consulting
- Fork & simply_couch: Benjamin ter Kuile
- CouchPotato removal + 2026 port: BenClaw & Benjamin ter Kuile