0.0
The project is in a healthy, maintained state
CouchDB ORM with ActiveModel compliance, associations, validations, callbacks, views, and soft delete. No driver dependency — the host app brings its own CouchDB client.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 3.0

Runtime

 Project Readme

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.

S3 Attachments

SimplyCouch can store file attachments in AWS S3 (or any S3-compatible service like MinIO). Uses the officially maintained aws-sdk-s3 gem.

Setup

Add to your Gemfile:

gem 'aws-sdk-s3'

Configure global defaults (per environment):

# config/initializers/simply_couch.rb
SimplyCouch.s3_defaults = {
  bucket: 'myapp-development',
  access_key: ENV['S3_ACCESS_KEY'],
  secret_access_key: ENV['S3_SECRET_KEY'],
  location: :eu  # :us or :eu, defaults to :us
}

# Or load from a YAML file with ERB:
SimplyCouch.load_s3_config(Rails.root.join('config', 's3.yml'))

# Or from Rails encrypted credentials:
SimplyCouch.s3_defaults = Rails.application.credentials.s3

A typical config/s3.yml:

development:
  bucket: myapp-dev
  access_key: dev-key
  secret_access_key: dev-secret

production:
  bucket: myapp-prod
  access_key: <%= ENV['S3_ACCESS_KEY'] %>
  secret_access_key: <%= ENV['S3_SECRET_KEY'] %>
  location: eu

Usage

class Report
  include SimplyCouch::Model

  has_s3_attachment :pdf  # uses defaults from SimplyCouch.s3_defaults
  has_s3_attachment :thumbnail, bucket: 'thumbnails'  # override bucket
end

report = Report.new
report.pdf = File.read('monthly.pdf')          # assign content
report.pdf = ['line1', 'line2']                # arrays serialized to JSON
report.save                                     # uploads to S3
report.pdf_size                                 # => 24501 (bytes)
report.pdf_url                                  # presigned URL

# Read back
report.pdf                                      # fetches from S3 on first access

# Delete
report.destroy                                  # also deletes from S3 if configured

Configuration options

Option Default Description
bucket (required) S3 bucket name
access_key (required) AWS access key ID
secret_access_key (required) AWS secret access key
location :us :us (us-east-1) or :eu (eu-west-1)
permissions 'private' S3 ACL: 'private', 'public-read', etc.
after_delete :nothing :delete to remove from S3 on destroy
logger nil Custom logger for S3 operations

Historical note: The original simply_stored shipped with right_aws (unmaintained for years). In June 2026 this was replaced with aws-sdk-s3 — same API surface, maintained gem, supports S3-compatible services like MinIO.

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