{"id":13463038,"url":"https://github.com/langalex/couch_potato","last_synced_at":"2025-05-14T05:10:48.888Z","repository":{"id":438051,"uuid":"59787","full_name":"langalex/couch_potato","owner":"langalex","description":"Ruby persistence layer for CouchDB.","archived":false,"fork":false,"pushed_at":"2025-01-15T13:36:46.000Z","size":1087,"stargazers_count":323,"open_issues_count":8,"forks_count":64,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-04-14T06:48:20.725Z","etag":null,"topics":["couchdb","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/langalex.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"MIT-LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2008-10-05T18:10:11.000Z","updated_at":"2024-09-21T18:18:39.000Z","dependencies_parsed_at":"2024-01-13T17:49:14.245Z","dependency_job_id":"a20afbf8-3ff2-4ff0-9363-73c1c15ee755","html_url":"https://github.com/langalex/couch_potato","commit_stats":{"total_commits":651,"total_committers":45,"mean_commits":"14.466666666666667","dds":0.6651305683563749,"last_synced_commit":"1ca5bfa39ff6d88f12f97c9ec2cae9d8cb5c2fe1"},"previous_names":[],"tags_count":52,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/langalex%2Fcouch_potato","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/langalex%2Fcouch_potato/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/langalex%2Fcouch_potato/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/langalex%2Fcouch_potato/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/langalex","download_url":"https://codeload.github.com/langalex/couch_potato/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254076850,"owners_count":22010611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["couchdb","ruby"],"created_at":"2024-07-31T13:00:44.661Z","updated_at":"2025-05-14T05:10:48.753Z","avatar_url":"https://github.com/langalex.png","language":"Ruby","readme":"## Couch Potato\n\n… is a persistence layer written in ruby for CouchDB.\n\n![CI](https://github.com/langalex/couch_potato/actions/workflows/ruby.yml/badge.svg)\n\n[![Code Climate](https://codeclimate.com/github/langalex/couch_potato.png)](https://codeclimate.com/github/langalex/couch_potato)\n\n### Upgrading from 1.x to 2.x\n\nVersion 1.x monkey patched Date#to_json (=\u003e \"2015/01/01\") and Time#to_json (=\u003e \"2016/01/01 12:00:00 +0000\"), 2.x does not (Date =\u003e \"\\\"2015-01-01\\\"\",\nTime =\u003e \"\\\"2016-01-01 12:00:00 +0100\\\"\").\n\nIn order to keep the old behavior, add the following to your code:\n\n    require 'couch_potato/core_ext/time'\n    require 'couch_potato/core_ext/date'\n\nThis will again apply the monkey patches. It is highly recommended to add the require statements if you are upgrading from 1.x. Otherwise,\nthe format of Date and Time objects written to your database and used to query it will change, which means data written before the update won't\nbe returned. To avoid the monkey patching, you have to re-write all your documents, so that dates/times are stored in the new format.\n\n### Mission\n\nThe goal of Couch Potato is to create a minimal framework in order to store and retrieve Ruby objects to/from CouchDB and create and query views.\n\nIt follows the document/view/querying semantics established by CouchDB and won't try to mimic ActiveRecord behavior in any way as that IS BAD.\n\nCode that uses Couch Potato should be easy to test.\n\nLastly Couch Potato aims to provide a seamless integration with Ruby on Rails, e.g. routing, form helpers etc.\n\n### Core Features\n\n- persisting objects by including the CouchPotato::Persistence module\n- declarative views with either custom or generated map/reduce functions\n- extensive spec suite\n\n### Supported Environments\n\n- CouchDB 1.6.0+\n- see .github/workflows/ruby.yml\n\n(Supported means I run the specs against those before releasing a new gem.)\n\n### Installation\n\nCouch Potato is hosted as a gem which you can install like this:\n\n```bash\ngem install couch_potato\n```\n\n#### Using with your ruby application:\n\n```ruby\nrequire 'rubygems'\nrequire 'couch_potato'\n```\n\nAfter that you configure the name of the database:\n\n```ruby\nCouchPotato::Config.database_name = 'name_of_the_db'\n```\n\nThe server URL will default to `http://localhost:5984/` unless specified:\n\n```ruby\nCouchPotato::Config.database_name = \"http://example.com:5984/name_of_the_db\"\n```\n\nBut you can also specify the database host separately from the database name:\n\n```ruby\nCouchPotato::Config.database_host = \"http://example.com:5984\"\nCouchPotato::Config.database_name = \"name_of_the_db\"\n```\n\nOr with authentication\n\n```ruby\nCouchPotato::Config.database_name = \"http://username:password@example.com:5984/name_of_the_db\"\n```\n\nOptionally you can configure the default language for design documents (`:javascript` (default) or `:erlang`).\n\n```ruby\nCouchPotato::Config.default_language = :javascript | :erlang\n```\n\nAnother switch allows you to store each CouchDB view in its own design document. Otherwise views are grouped by model.\n\n```ruby\nCouchPotato::Config.split_design_documents_per_view = true\n```\n\nIf you are using more than one database from your app, you can create aliases:\n\n```ruby\nCouchPotato::Config.additional_databases = {'db1' =\u003e 'db1_production', 'db2' =\u003e 'https://db2.example.com/db'}\ndb1 = CouchPotato.use 'db1'\n```\n\n#### Using with Rails\n\nCreate a `config/couchdb.yml`:\n\n```yml\ndefault: \u0026default\n  split_design_documents_per_view: true # optional, default is false\n  digest_view_names: true # optional, default is false\n  default_language: :erlang # optional, default is javascript\n  database_host: \"http://127.0.0.1:5984\"\n\ndevelopment:\n  \u003c\u003c: *default\n  database: development_db_name\ntest:\n  \u003c\u003c: *default\n  database: test_db_name\nproduction:\n  \u003c\u003c: *default\n  database: \u003c%= ENV['DB_NAME'] %\u003e\n  additional_databases:\n    db1: db1_production\n    db2: https://db2.example.com/db\n```\n\nAdd to your `Gemfile`:\n\n```ruby\n# gem 'rails' # we don't want to load activerecord so we can't require rails\ngem 'railties'\ngem 'actionpack'\ngem 'actionmailer'\ngem 'activemodel'\ngem \"couch_potato\"\ngem 'tzinfo'\n```\n\n### Introduction\n\nThis is a basic tutorial on how to use Couch Potato. If you want to know all the details feel free to read the specs and the [rdocs](http://rdoc.info/projects/langalex/couch_potato).\n\n#### Save, load objects\n\nFirst you need a class.\n\n```ruby\nclass User\nend\n```\n\nTo make instances of this class persistent include the persistence module:\n\n```ruby\nclass User\n  include CouchPotato::Persistence\nend\n```\n\nIf you want to store any properties you have to declare them:\n\n```ruby\nclass User\n  include CouchPotato::Persistence\n\n  property :name\nend\n```\n\nProperties can be typed:\n\n```ruby\nclass User\n  include CouchPotato::Persistence\n\n  property :address, :type =\u003e Address\nend\n```\n\nIn this case `Address` also implements `CouchPotato::Persistence` which means its JSON representation will be added to the user document.\nCouch Potato also has support for the basic types (right now `Integer`, `Date`, `Time` and `:boolean` are supported):\n\n```ruby\nclass User\n  include CouchPotato::Persistence\n\n  property :age, :type =\u003e Integer\n  property :receive_newsletter, :type =\u003e :boolean\nend\n```\n\nWith this in place when you set the user's age as a String (e.g. using an HTML form) it will be converted into a `Integer` automatically.\n\nProperties can have a default value:\n\n```ruby\nclass User\n  include CouchPotato::Persistence\n\n  property :active, :default =\u003e true\n  property :signed_up, :default =\u003e Proc.new { Time.now }\nend\n```\n\nNow you can save your objects. All database operations are encapsulated in the `CouchPotato::Database` class. This separates your domain logic from the database access logic which makes it easier to write tests and also keeps your models smaller and cleaner.\n\n```ruby\nuser = User.new :name =\u003e 'joe'\nCouchPotato.database.save_document user # or save_document!\n```\n\nYou can of course also retrieve your instance:\n\n```ruby\nCouchPotato.database.load_document \"id_of_the_user_document\" # =\u003e \u003c#User 0x3075\u003e\n```\n\n#### Handling conflicts\n\nCouchDB uses MVCC to detect write conflicts. If a conflict occurs when trying to update a document CouchDB returns an error. To handle conflicts easily you can save documents like this:\n\n```ruby\nCouchPotato.database.save_document user do |user|\n  user.name = 'joe'\nend\n```\n\nWhen a conflict occurs Couch Potato automatically reloads the document, runs the block and tries to save it again. Note that the block is also run before initally saving the document.\n\n#### Caching load reqeusts\n\nYou can add a cache to a database instance to enable caching subsequent `#load` calls to the same id.\nAny write operation will completely clear the cache.\n\n```ruby\ndb = CouchPotato.database\ndb.cache = {}\ndb.load '1'\ndb.load '1' # goes to the cache instead of to the database\n```\n\nIn web apps, the idea is to use a per request cache, i.e. set a new cache for every request.\n\n#### Operations on multiple documents\n\nYou can also load a bunch of documents with one request.\n\n```ruby\nCouchPotato.database.load ['user1', 'user2', 'user3'] # =\u003e [\u003c#User 0x3075\u003e, \u003c#User 0x3076\u003e, \u003c#User 0x3077\u003e]\n```\n\n#### Properties\n\nYou can access the properties you declared above through normal attribute accessors.\n\n```ruby\nuser.name # =\u003e 'joe'\nuser.name = {:first =\u003e ['joe', 'joey'], :last =\u003e 'doe', :middle =\u003e 'J'} # you can set any ruby object that responds_to :to_json (includes all core objects)\nuser._id # =\u003e \"02097f33a0046123f1ebc0ebb6937269\"\nuser._rev # =\u003e \"2769180384\"\nuser.created_at # =\u003e Fri Oct 24 19:05:54 +0200 2008\nuser.updated_at # =\u003e Fri Oct 24 19:05:54 +0200 2008\nuser.new? # =\u003e false\n```\n\nIf you want to have properties that don't map to any JSON type, i.e. other than `String`, `Number`, `Boolean`, `Hash` or `Array` you have to define the type like this:\n\n```ruby\nclass User\n  property :date_of_birth, :type =\u003e Date\nend\n```\n\nThe `date_of_birth` property is now automatically serialized to JSON and back when storing/retrieving objects.\n\nIf you want to store an Array of objects, just pass the definiton as an Array of Dates:\n\n```ruby\nclass User\n  property :birthdays, :type =\u003e [Date]\nend\n```\n\n#### Dirty tracking\n\nCouchPotato tracks the dirty state of attributes in the same way ActiveRecord does:\n\n```ruby\nuser = User.create :name =\u003e 'joe'\nuser.name # =\u003e 'joe'\nuser.name_changed? # =\u003e false\nuser.name_was # =\u003e nil\n```\n\nYou can also force a dirty state:\n\n```ruby\nuser.name = 'jane'\nuser.name_changed? # =\u003e true\nuser.name_not_changed\nuser.name_changed? # =\u003e false\nCouchPotato.database.save_document user # does nothing as no attributes are dirty\n```\n\n#### Optional Deep Dirty Tracking\n\nIn addition to standard dirty tracking, you can opt-in to more advanced dirty tracking for deeply structured documents by including the `CouchPotato::DeepDirtyAttributes` module in your models. This provides two benefits:\n\n1. Dirty checking for array and embedded document properties is more reliable, such that modifying elements in an array (by any means) or changing a property of an embedded document will make the root document be `changed?`. With standard dirty checking, the `#{property}=` method must be called on the root document for it to be `changed?`.\n2. It gives more useful and detailed change tracking for embedded documents, arrays of simple values, and arrays of embedded documents.\n\nThe `#{property}_changed?` and `#{property}_was` methods work the same as basic dirty checking, and the `_was` values are always deep clones of the original/previous value. The `#{property}_change` and `changes` methods differ from basic dirty checking for embedded documents and arrays, giving richer details of the changes instead of just the previous and current values. This makes generating detailed, human friendly audit trails of documents easy.\n\nTracking changes in embedded documents gives easy access to the changes in that document:\n\n```ruby\nbook = Book.new(:cover =\u003e Cover.new(:color =\u003e \"red\"))\nbook.cover.color = \"blue\"\nbook.cover_changed? # =\u003e true\nbook.cover_was # =\u003e \u003cdeep clone of original state of book.cover\u003e\nbook.cover_change # =\u003e [\u003cdeep clone of original state of book.cover\u003e, {:color =\u003e [\"red\", \"blue\"]}]\n```\n\nTracking changes in arrays of simple properties gives easy access to added and removed items:\n\n```ruby\nbook = Book.new(:authors =\u003e [\"Sarah\", \"Jane\"])\nbook.authors.delete \"Jane\"\nbook.authors \u003c\u003c \"Sue\"\nbook.authors_changed? # =\u003e true\nbook.authors_was # =\u003e [\"Sarah\", \"Jane\"]\nbook.authors_change # =\u003e [[\"Sarah\", \"Jane\"], {:added =\u003e [\"Sue\"], :removed =\u003e [\"Jane\"]}]\n```\n\nTracking changes in an array of embedded documents also gives changed items:\n\n```ruby\nbook = Book.new(:pages =\u003e [Page.new(:number =\u003e 1), Page.new(:number =\u003e 2)]\nbook.pages[0].title = \"New title\"\nbook.pages.delete_at 1\nbook.pages \u003c\u003c Page.new(:number =\u003e 3)\nbook.pages_changed? # =\u003e true\nbook.pages_was # =\u003e \u003cdeep clone of original pages array\u003e\nbook.pages_change[0] # =\u003e \u003cdeep clone of original pages array\u003e\nbook.pages_change[1] # =\u003e {:added =\u003e [\u003cpage 3\u003e], :removed =\u003e [\u003cpage 2\u003e], :changed =\u003e [[\u003cdeep clone of original page 1\u003e, {:title =\u003e [nil, \"New title\"]}]]}\n```\n\nFor change tracking in nested documents and document arrays to work, the embedded documents **must** have unique `_id` values. This can be accomplished easily in your embedded CouchPotato models by overriding `initialize`:\n\n```ruby\ndef initialize(*args)\n  self._id = SecureRandom.uuid\n  super\nend\n```\n\n#### Object validations\n\nCouch Potato by default uses ActiveModel for validation\n\n```ruby\nclass User\n  property :name\n  validates_presence_of :name\nend\n\nuser = User.new\nuser.valid? # =\u003e false\nuser.errors[:name] # =\u003e ['can't be blank']\n```\n\n#### Finding stuff / views / lists\n\nIn order to find data in your CouchDB you have to create a [view](http://books.couchdb.org/relax/design-documents/views) first. Couch Potato offers you to create and manage those views for you. All you have to do is declare them in your classes:\n\n```ruby\nclass User\n  include CouchPotato::Persistence\n  property :name\n\n  view :all, :key =\u003e :created_at\nend\n```\n\nThis will create a view called \"all\" in the \"user\" design document with a map function that emits \"created_at\" for every user document.\n\n```ruby\nCouchPotato.database.view User.all\n```\n\nThis will load all user documents in your database sorted by `created_at`.\n\n```ruby\nCouchPotato.database.view User.all(:key =\u003e (Time.now- 10)..(Time.now), :descending =\u003e true)\n```\n\nAny options you pass in will be passed onto CouchDB.\n\nComposite keys are also possible:\n\n```ruby\nclass User\n  property :name\n\n  view :all, :key =\u003e [:created_at, :name]\nend\n```\n\nYou can let Couch Potato generate these map/reduce functions in Erlang, which reslts in much faster view generation:\n\n```ruby\nclass User\n  property :name\n\n  view :all, :key =\u003e [:created_at, :name], :language =\u003e :erlang\nend\n```\n\nSo far only very simple views like the above work with Erlang.\n\nYou can also pass conditions as a JavaScript string:\n\n```ruby\nclass User\n  property :name\n\n  view :completed, :key =\u003e :name, :conditions =\u003e 'doc.completed === true'\nend\n```\n\nThe creation of views is based on view specification classes (see [CouchPotato::View::BaseViewSpec](http://rdoc.info/rdoc/langalex/couch_potato/blob/e8f0069e5529ad08a1bd1f02637ea8f1d6d0ab5b/CouchPotato/View/BaseViewSpec.html) and its descendants for more detailed documentation). The above code uses the `ModelViewSpec` class which is used to find models by their properties. For more sophisticated searches you can use other view specifications (either use the built-in or provide your own) by passing a type parameter:\n\nIf you have larger structures and you only want to load some attributes you can use the `PropertiesViewSpec` (the full class name is automatically derived):\n\n```ruby\nclass User\n  property :name\n  property :bio\n\n  view :all, :key =\u003e :created_at, :properties =\u003e [:name], :type =\u003e :properties\nend\n\nCouchPotato.database.view(User.all).first.name # =\u003e \"joe\"\nCouchPotato.database.view(User.all).first.bio # =\u003e nil\n\nCouchPotato.database.first(User.all).name # =\u003e \"joe\" # convenience function, returns nil if nothing found\nCouchPotato.database.first!(User.all) # would raise CouchPotato::NotFound if nothing was found\n```\n\nIf you want Rails to automatically show a 404 page when `CouchPotato::NotFound` is raised add this to your `ApplicationController`:\n\n```ruby\nrescue_from CouchPotato::NotFound do\n  render(:file =\u003e 'public/404.html', :status =\u003e :not_found, :layout =\u003e false)\nend\n```\n\nYou can also pass in custom map/reduce functions with the custom view spec:\n\n```ruby\nclass User\n  view :all, :map =\u003e \"function(doc) { emit(doc.created_at, null)}\", :include_docs =\u003e true, :type =\u003e :custom\nend\n```\n\ncommonJS modules can also be used in custom views:\n\n```ruby\nclass User\n  view :all, :map =\u003e \"function(doc) { emit(null, require(\"views/lib/test\").test)}\", :lib =\u003e {:test =\u003e \"exports.test = 'test'\"}, :include_docs =\u003e true, :type =\u003e :custom\nend\n```\n\nIf you don't want the results to be converted into models the raw view is your friend:\n\n```ruby\nclass User\n  view :all, :map =\u003e \"function(doc) { emit(doc.created_at, doc.name)}\", :type =\u003e :raw\nend\n```\n\nWhen querying this view you will get the raw data returned by CouchDB which looks something like this:\n\n```json\n{\n  \"total_entries\": 2,\n  \"rows\": [\n    {\n      \"value\": \"alex\",\n      \"key\": \"2009-01-03 00:02:34 +000\",\n      \"id\": \"75976rgi7546gi02a\"\n    }\n  ]\n}\n```\n\nTo process this raw data you can also pass in a results filter:\n\n```ruby\nclass User\n  view :all, :map =\u003e \"function(doc) { emit(doc.created_at, doc.name)}\", :type =\u003e :raw, :results_filter =\u003e lambda {|results| results['rows'].map{|row| row['value']}}\nend\n```\n\nIn this case querying the view would only return the emitted value for each row.\n\nYou can pass in your own view specifications by passing in `:type =\u003e MyViewSpecClass`. Take a look at the CouchPotato::View::\\*ViewSpec classes to get an idea of how this works.\n\n##### Digest view names\n\nIf turned on, Couch Potato will append an MD5 digest of the map function to each view name. This makes sure (together with split_design_documents_per_view) that no views/design documents are ever updated. Instead, new ones are created. Since reindexing can take a long time once your database is larger, you want to avoid blocking your app while CouchDB is busy. Instead, you create a new view, warm it up, and only then start using it.\n\n##### Lists\n\nCouchPotato also supports [CouchDB lists](http://books.couchdb.org/relax/design-documents/lists). With lists you can process the result of a view query with another JavaScript function. This can be useful for example if you want to filter your results, or add some data to each document.\n\nDefining a list works similarly to views:\n\n```ruby\nclass User\n  include CouchPotato::Persistence\n\n  property :first_name\n  view :with_full_name, key: first_namne, list: :add_last_name\n  view :all, key: :first_name\n\n  list :add_last_name, \u003c\u003c-JS\n    function(head, req) {\n      var row;\n      send('{\"rows\": [');\n      while(row = getRow()) {\n        row.doc.name = row.doc.first_name + ' doe';\n        send(JSON.stringify(row));\n      };\n      send(']}');\n    }\n  JS\nend\n\nCouchPotato.database.save User.new(first_name: 'joe')\nCouchPotato.database.view(User.with_full_name).first.name # =\u003e 'joe doe'\n```\n\nYou can also pass in the list at query time:\n\n```ruby\nCouchPotato.database.view(User.all(list: :add_last_name))\n```\n\nAnd you can pass parameters to the list:\n\n```ruby\nCouchPotato.database.view(User.all(list: :add_last_name, list_params: {filter: '*'}))\n```\n\n#### Associations\n\nNot supported. Not sure if they ever will be. You can implement those yourself using views and custom methods on your models.\n\n#### Callbacks\n\nCouch Potato supports the usual lifecycle callbacks known from ActiveRecord:\n\n```ruby\nclass User\n  include CouchPotato::Persistence\n\n  before_create :do_something_before_create\n  before_update {|user| user.do_something_on_update}\nend\n```\n\nThis will call the method do_something_before_create before creating an object and run the given lambda before updating one. Lambda callbacks get passed the model as their first argument. Method callbacks don't receive any arguments.\n\nSupported callbacks are: `:before_validation`, `:before_validation_on_create`, `:before_validation_on_update`, `:before_validation_on_save`, `:before_create`, `:after_create`, `:before_update`, `:after_update`, `:before_save`, `:after_save`, `:before_destroy`, `:after_destroy`.\n\nIf you need access to the database in a callback: Couch Potato automatically assigns a database instance to the model before saving and when loading. It is available as _database_ accessor from within your model instance.\n\n#### Attachments\n\nThere is basic attachment support: if you want to store any attachments set the `_attachments` attribute of a model before saving like this:\n\n```ruby\nclass User\n  include CouchPotato::Persistence\nend\n\ndata = File.read('some_file.text') # or from upload\nuser = User.new\nuser._attachments = {'photo' =\u003e {'data' =\u003e data, 'content_type' =\u003e 'image/png'}}\n```\n\nWhen saving this object an attachment with the name _photo_ will be uploaded into CouchDB. It will be available under the url of the user object + _/photo_. When loading the user at a later time you still have access to the _content_type_ and additionally to the _length_ of the attachment:\n\n```ruby\nuser_reloaded = CouchPotato.database.load user.id\nuser_reloaded._attachments['photo'] # =\u003e {'content_type' =\u003e 'image/png', 'length' =\u003e 37861}\n```\n\n#### Multi DB Support\n\nCouch Potato supports accessing multiple CouchDBs:\n\n```ruby\nCouchPotato.with_database('couch_customer') do |couch|\n  couch.save @customer\nend\n```\n\nUnless configured otherwise this would save the customer model to `http://127.0.0.1:5984/couch_customer`.\n\nYou can also first retrieve the database instance:\n\n```ruby\ndb = CouchPotato.use('couch_customer')\ndb.save @customer\n```\n\n#### Testing\n\nTo make testing easier and faster database logic has been put into its own class, which you can replace and stub out in whatever way you want:\n\n```ruby\nclass User\n  include CouchPotato::Persistence\nend\n\n# RSpec\ndescribe 'save a user' do\n  it 'should save' do\n    couchrest_db = stub 'couchrest_db',\n    database = CouchPotato::Database.new couchrest_db\n    user = User.new\n    couchrest_db.should_receive(:save_doc).with(...)\n    database.save_document user\n  end\nend\n```\n\nBy creating you own instances of `CouchPotato::Database` and passing them a fake CouchRest database instance you can completely disconnect your unit tests/spec from the database.\n\nFor stubbing out the database couch potato offers some helpers via the `couch_potato-rspec` gem. Use version 2.x of the gem, you you are on RSpec 2, use 3.x for RSpec 3.\n\n```ruby\nclass Comment\n  view :by_commenter_id, :key =\u003e :commenter_id\nend\n\n# RSpec\nrequire 'couch_potato/rspec'\n\ndb = stub_db # stubs CouchPotato.database\ndb.stub_view(Comment, :by_commenter_id).with('23').and_return([:comment1, :comment2])\n\nCouchPotato.database.view(Comment.by_commenter_id('23)) # =\u003e [:comment1, :comment2]\nCouchPotato.database.first(Comment.by_commenter_id('23)) # =\u003e :comment1\n```\n\n##### Testing map/reduce functions\n\nCouch Potato provides custom RSpec matchers for testing the map and reduce functions of your views. For example you can do this:\n\n```ruby\nclass User\n  include CouchPotato::Persistence\n\n  property :name\n  property :age, :type =\u003e Integer\n\n  view :by_name, :key =\u003e :name\n  view :by_age,  :key =\u003e :age\n  view :oldest_by_name,\n    :map =\u003e \"function(doc) { emit(doc.name, doc.age); }\",\n    :reduce =\u003e \"function(keys, values, rereduce) { return Math.max.apply(this, values); }\"\nend\n\n#RSpec\nrequire 'couch_potato/rspec'\n\ndescribe User, 'views' do\n  it \"should map users to their name\" do\n    User.by_name.should map(User.new(:name =\u003e 'bill', :age =\u003e 23)).to(['bill', 1])\n  end\n\n  it \"should reduce the users to the sum of their age\" do\n    User.by_age.should reduce([], [23, 22]).to(45)\n  end\n\n  it \"should map/reduce users to the oldest age by name\" do\n    docs = [\n      User.new(:name =\u003e \"Andy\", :age =\u003e 30),\n      User.new(:name =\u003e \"John\", :age =\u003e 25),\n      User.new(:name =\u003e \"John\", :age =\u003e 30),\n      User.new(:name =\u003e \"Jane\", :age =\u003e 20)]\n    User.oldest_by_name.should map_reduce(docs).with_options(:group =\u003e true, :startkey =\u003e \"Jane\").to(\n      {\"key\" =\u003e \"Jane\", \"value\" =\u003e 20}, {\"key\" =\u003e \"John\", \"value\" =\u003e 30})\n  end\nend\n```\n\nThis will actually run your map/reduce functions in a JavaScript interpreter, passing the arguments as JSON and converting the results back to Ruby. `map_reduce` specs map the input documents, reduce the emitted keys/values, and rereduce the results while also respecting the `:group`, `:group_level`, `:key`, `:keys`, `:startkey`, and `:endkey` couchdb options. For more examples see the [spec](http://github.com/langalex/couch_potato/blob/master/spec/unit/rspec_matchers_spec.rb).\n\nIn order for this to work you must have the `js` executable in your PATH. This is usually part of the _spidermonkey_ package/port. (On MacPorts that's _spidemonkey_, on Linux it could be one of _libjs_, _libmozjs_ or _libspidermonkey_). When you installed CouchDB via your packet manager Spidermonkey should already be there.\n\n### Helping out\n\nPlease fix bugs, add more specs, implement new features by forking the github repo at http://github.com/langalex/couch_potato.\n\nIssues are tracked at github: http://github.com/langalex/couch_potato/issues\n\nThere is a mailing list, just write to: couchpotato@librelist.com\n\nYou can run all the specs by calling 'rake spec_unit' and 'rake spec_functional' in the root folder of Couch Potato. The specs require a running CouchDB instance at `http://localhost:5984`\n\nI will only accept patches that are covered by specs - sorry.\n\n### Contact\n\nIf you have any questions/suggestions etc. please contact me at alex at upstream-berlin.com or @langalex on twitter.\n","funding_links":[],"categories":["Data Persistence","Ruby"],"sub_categories":["CouchDB Clients"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flangalex%2Fcouch_potato","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flangalex%2Fcouch_potato","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flangalex%2Fcouch_potato/lists"}