├── .gitignore ├── .rubocop.yml ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── lib └── rom │ ├── couchdb.rb │ └── couchdb │ ├── commands.rb │ ├── dataset.rb │ ├── gateway.rb │ ├── relation.rb │ ├── transproc_functions.rb │ └── version.rb ├── rom-couchdb.gemspec └── test ├── cases ├── commands_test.rb ├── dataset_test.rb ├── datastore_lint_test.rb ├── gateway_test.rb ├── repository_lint_test.rb └── version_test.rb ├── support └── user.rb └── test_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - 'rom-couchdb.gemspec' 4 | - 'bin/*' 5 | 6 | Style/Documentation: 7 | Exclude: 8 | - 'test/cases/*' 9 | - 'test/support/*' 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.2.2 4 | services: 5 | - couchdb 6 | before_script: 7 | - curl -X PUT localhost:5984/testdb 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in rom-couchdb.gemspec 4 | gemspec 5 | 6 | group :test do 7 | gem 'activesupport' 8 | gem 'byebug' 9 | gem 'rom', git: 'https://github.com/rom-rb/rom.git', branch: 'master' 10 | gem 'rubocop' 11 | end 12 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Hunter Madison 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rom::CouchDB 2 | 3 | [![Build Status](https://travis-ci.org/rom-rb/rom-couchdb.svg)](https://travis-ci.org/hmadison/rom-couchdb) [![Gem Version](https://badge.fury.io/rb/rom-couchdb.svg)](http://badge.fury.io/rb/rom-couchdb) 4 | 5 | Experimental CouchDB support for ROM. Uses [couchrest](https://github.com/couchrest/couchrest) as the connection adapter. 6 | 7 | ## Installing 8 | 9 | Add `gem 'rom-couchdb'` to your Gemfile. 10 | 11 | ## Example 12 | 13 | 14 | ```ruby 15 | class Color 16 | attr_reader :name, :code, :id, :rev 17 | 18 | def initialize(attributes) 19 | puts attributes 20 | @name, @code, @id, @rev = attributes.values_at(:name, :code, :id, :rev) 21 | end 22 | end 23 | 24 | ROM.setup(:couchdb, 'testdb') 25 | 26 | class Colors < ROM::Relation[:couchdb] 27 | def find_id(id) 28 | find_by_id(id) 29 | end 30 | 31 | def find_view(name, params) 32 | find_by_view(name, params) 33 | end 34 | end 35 | 36 | class ColorMapper < ROM::Mapper 37 | relation :colors 38 | register_as :entity 39 | 40 | model Color 41 | 42 | attribute :id, from: :'_id' 43 | attribute :rev, from: :'_rev' 44 | attribute :name 45 | attribute :code 46 | end 47 | 48 | class CreateColor < ROM::Commands::Create[:couchdb] 49 | register_as :create 50 | relation :colors 51 | result :one 52 | end 53 | 54 | class UpdateColor < ROM::Commands::Update[:couchdb] 55 | register_as :update 56 | relation :colors 57 | result :one 58 | end 59 | 60 | rom = ROM.finalize.env 61 | color_commands = rom.command(:colors) 62 | color_relations = rom.relation(:colors) 63 | 64 | 65 | color = color_commands.as(:entity).create.call(name: "Red", code: 8) 66 | update = color_commands.as(:entity).create.call(name: "Blue", code: 9, _id: color.id, _rev: color.rev) 67 | 68 | find = color_relations.as(:entity).find_id(color.id).one 69 | all_docs = color_relations.find_view('_all_docs', {}) 70 | ``` 71 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rake/testtask' 3 | require 'rubocop/rake_task' 4 | 5 | task default: [:test, :rubocop] 6 | 7 | RuboCop::RakeTask.new 8 | 9 | Rake::TestTask.new do |t| 10 | t.libs << 'test' 11 | t.test_files = FileList['test/cases/*_test.rb'] 12 | t.verbose = true 13 | end 14 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'rom/couchdb' 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require 'pry' 11 | # Pry.start 12 | 13 | require 'irb' 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | bundle install 6 | 7 | # Do any other automated setup that you need to do here 8 | -------------------------------------------------------------------------------- /lib/rom/couchdb.rb: -------------------------------------------------------------------------------- 1 | require 'rom' 2 | 3 | require 'rom/couchdb/version' 4 | require 'rom/couchdb/gateway' 5 | require 'rom/couchdb/dataset' 6 | require 'rom/couchdb/relation' 7 | require 'rom/couchdb/commands' 8 | require 'rom/couchdb/transproc_functions' 9 | 10 | module ROM 11 | # CouchDB support for ROM 12 | module CouchDB 13 | end 14 | end 15 | 16 | ROM.register_adapter(:couchdb, ROM::CouchDB) 17 | -------------------------------------------------------------------------------- /lib/rom/couchdb/commands.rb: -------------------------------------------------------------------------------- 1 | require 'rom/commands' 2 | 3 | module ROM 4 | module CouchDB 5 | module Commands 6 | # CouchDB create command 7 | class Create < ROM::Commands::Create 8 | def collection 9 | relation.dataset 10 | end 11 | 12 | def execute(object) 13 | document = object.dup 14 | collection << document 15 | [document] 16 | end 17 | end 18 | 19 | # CouchDB update command 20 | class Update < ROM::Commands::Update 21 | def collection 22 | relation.dataset 23 | end 24 | 25 | def execute(object) 26 | document = object.dup 27 | collection << document 28 | [document] 29 | end 30 | end 31 | 32 | # CouchDB delete command 33 | class Delete < ROM::Commands::Delete 34 | def collection 35 | relation.dataset 36 | end 37 | 38 | def execute(object) 39 | collection.delete(object) 40 | [object] 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/rom/couchdb/dataset.rb: -------------------------------------------------------------------------------- 1 | require 'couchrest' 2 | 3 | require 'rom/initializer' 4 | require 'rom/data_proxy' 5 | 6 | module ROM 7 | module CouchDB 8 | # CouchDB Dataset 9 | class Dataset 10 | extend Initializer 11 | include DataProxy 12 | 13 | attr_reader :results 14 | 15 | param :data 16 | option :connection, reader: true 17 | 18 | def initialize(data, options = {}) 19 | @data = data 20 | super 21 | end 22 | 23 | def insert(object) 24 | input = stringify_proc.call(object.dup) 25 | resp = @connection.save_doc(input) 26 | # send back the id and revision of the document 27 | object[:_id] = resp['id'] 28 | object[:_rev] = resp['rev'] 29 | self 30 | end 31 | alias << insert 32 | 33 | def delete(object) 34 | input = stringify_proc.call(object.dup) 35 | @connection.delete_doc(input) 36 | self 37 | end 38 | 39 | def count 40 | @data.count 41 | end 42 | 43 | def to_a 44 | @data 45 | end 46 | 47 | def find_by_id(id, params = {}) 48 | document = @connection.get(id, params).to_hash 49 | self.class.new([document], connection: @connection) 50 | end 51 | 52 | def find_by_view(name, params = {}) 53 | results = @connection.view(name, params) 54 | self.class.new([results], connection: @connection) 55 | end 56 | 57 | def stringify_proc 58 | TransprocFunctions[:stringify_keys] 59 | end 60 | 61 | def self.row_proc 62 | TransprocFunctions[:symbolize_keys] 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/rom/couchdb/gateway.rb: -------------------------------------------------------------------------------- 1 | require 'couchrest' 2 | 3 | module ROM 4 | module CouchDB 5 | # CouchDB gateway for ROM 6 | class Gateway < ROM::Gateway 7 | attr_reader :connection, :sets 8 | 9 | def initialize(uri) 10 | @connection = CouchRest.database(uri) 11 | @sets = {} 12 | end 13 | 14 | def dataset(name) 15 | @sets[name] = Dataset.new([], connection: @connection) 16 | end 17 | 18 | def [](name) 19 | @sets.fetch(name) 20 | end 21 | 22 | def dataset?(name) 23 | @sets.include?(name) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/rom/couchdb/relation.rb: -------------------------------------------------------------------------------- 1 | require 'rom/relation' 2 | 3 | module ROM 4 | module CouchDB 5 | # CouchDB relation support 6 | class Relation < ROM::Relation 7 | adapter :couchdb 8 | forward :insert, :find_by_id, :find_by_view 9 | 10 | def count 11 | dataset.count 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/rom/couchdb/transproc_functions.rb: -------------------------------------------------------------------------------- 1 | module ROM 2 | module CouchDB 3 | # Collection of Transproc functions used by the adapter 4 | module TransprocFunctions 5 | extend Transproc::Registry 6 | import Transproc::HashTransformations 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/rom/couchdb/version.rb: -------------------------------------------------------------------------------- 1 | module ROM 2 | # CouchDB support for ROM 3 | module CouchDB 4 | VERSION = '3.0.0'.freeze 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /rom-couchdb.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'rom/couchdb/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'rom-couchdb' 8 | spec.version = ROM::CouchDB::VERSION 9 | spec.authors = ['Hunter Madison'] 10 | spec.email = ['hunterglenmadison@icloud.com'] 11 | 12 | spec.summary = 'CouchDB support for ROM' 13 | spec.homepage = 'https://github.com/hmadison' 14 | spec.license = 'MIT' 15 | 16 | spec.files = `git ls-files -z`.split("\x0").reject {|f| f.match(%r{^(test)/})} 17 | spec.bindir = 'exe' 18 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 19 | spec.require_paths = ['lib'] 20 | 21 | spec.add_runtime_dependency 'rom', '~> 3.0' 22 | spec.add_runtime_dependency 'couchrest', '~> 2.0.0' 23 | 24 | spec.add_development_dependency 'bundler', '~> 1.9' 25 | spec.add_development_dependency 'rake', '~> 10.0' 26 | end 27 | -------------------------------------------------------------------------------- /test/cases/commands_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CommandsTest < Minitest::Test 4 | def setup 5 | @database_name = 'testdb' 6 | @rom = build_container 7 | @user_commands = @rom.command(:users) 8 | end 9 | 10 | def test_create_command 11 | user_data = { name: 'test_user_name', age: 12 } 12 | 13 | data = @user_commands.create.call(user_data) 14 | document = data.first 15 | 16 | refute_nil document[:_id] 17 | refute_nil document[:_rev] 18 | 19 | # Ensure the data is the same 20 | assert_equal ['test_user_name', 12], document.values_at(:name, :age) 21 | end 22 | 23 | def test_update_command 24 | # Set up a raw couchdb connection and save a document 25 | connection = CouchRest.database(@database_name) 26 | document = { key: 'value' } 27 | connection.save_doc(document) 28 | 29 | assert_in_couch document 30 | 31 | # Update the document 32 | document[:key] = 'new_value' 33 | 34 | updated_document = @user_commands.update.call(document).first 35 | 36 | # Make sure the id is the same but the rev changed 37 | assert_equal document['_id'], updated_document[:_id] 38 | refute_equal document['_rev'], updated_document[:_rev] 39 | end 40 | 41 | def test_delete_command 42 | # Set up a raw couchdb connection and save a document 43 | connection = CouchRest.database(@database_name) 44 | document = { key: 'value' } 45 | connection.save_doc(document) 46 | 47 | assert_in_couch document 48 | 49 | @user_commands.delete.call(document) 50 | 51 | # Ensure the document was deleted 52 | refute connection.get(document['_id']) 53 | end 54 | 55 | private 56 | 57 | def assert_in_couch(document) 58 | # Make sure we have a id and rev from couchdb 59 | refute_nil document['_id'] 60 | refute_nil document['_rev'] 61 | end 62 | 63 | def build_container 64 | ROM.container(:couchdb, @database_name) do |config| 65 | config.relation(:users) 66 | 67 | config.commands(:users) do 68 | define(:create) 69 | define(:update) 70 | define(:delete) 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /test/cases/dataset_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class DatasetTest < Minitest::Test 4 | def setup 5 | @sample_data = [{ test_key: 'test_value' }] 6 | @connection = CouchRest.database('testdb') 7 | 8 | @dataset = ROM::CouchDB::Dataset.new(@sample_data, connection: @connection) 9 | end 10 | 11 | def test_insert 12 | dataset = @dataset.insert(test_key: 'test_value') 13 | 14 | assert_equal dataset, @dataset 15 | end 16 | 17 | def test_each 18 | @dataset.each do |data| 19 | @sample_data.include?(data) 20 | end 21 | end 22 | 23 | def test_find_by_id 24 | @sample_doc = { key: 'value' } 25 | @connection.save_doc(@sample_doc) 26 | 27 | dataset = @dataset.find_by_id(@sample_doc['_id']) 28 | 29 | refute_equal dataset, @dataset 30 | assert_equal @sample_doc['_id'], dataset.to_a.first['_id'] 31 | end 32 | 33 | def test_find_by_view 34 | dataset = @dataset.find_by_view('_all_docs', {}) 35 | 36 | refute_equal dataset, @dataset 37 | assert_equal %w(total_rows offset rows), dataset.to_a.first.keys 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/cases/datastore_lint_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rom/lint/test' 3 | 4 | class DatastoreLintTest < Minitest::Test 5 | include ROM::Lint::TestEnumerableDataset 6 | 7 | def setup 8 | @data = [{ flavor: 'Peach', rank: 17 }, { flavor: 'Apple', rank: 29 }] 9 | @dataset = ROM::CouchDB::Dataset.new(@data, connection: nil) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/cases/gateway_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class GatewayTest < Minitest::Test 4 | def setup 5 | @registry = ROM::CouchDB::Gateway.new('testdb') 6 | end 7 | 8 | def test_connection 9 | assert_equal CouchRest::Database, @registry.connection.class 10 | assert_equal 'http://127.0.0.1:5984/testdb', @registry.connection.root.to_s 11 | end 12 | 13 | def test_dataset 14 | dataset = @registry.dataset('test') 15 | 16 | assert_equal ROM::CouchDB::Dataset, dataset.class 17 | assert_equal dataset, @registry['test'] 18 | assert @registry.dataset?('test') 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/cases/repository_lint_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rom/lint/test' 3 | 4 | class GatewayLintTest < Minitest::Test 5 | include ROM::Lint::TestGateway 6 | 7 | def setup 8 | @gateway = ROM::CouchDB::Gateway 9 | @identifier = :couchdb 10 | @uri = 'testdb' 11 | end 12 | 13 | def gateway_instance 14 | ROM::CouchDB::Gateway.new 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/cases/version_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class VersionTest < Minitest::Test 4 | def test_that_it_has_a_version_number 5 | refute_nil ::ROM::CouchDB::VERSION 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/support/user.rb: -------------------------------------------------------------------------------- 1 | # :nodoc: 2 | class User 3 | attr_reader :name, :age 4 | 5 | def initialize(attributes) 6 | @name, @age = attributes.values_at(:name, :age) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.require 3 | 4 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 5 | require 'rom/couchdb' 6 | 7 | require 'minitest/autorun' 8 | --------------------------------------------------------------------------------