dispaaro-couch 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,11 @@
1
+ coverage
2
+ test.sqlite3*
3
+ *.pid
4
+ *.lck
5
+ pkg
6
+ test/tmp/*
7
+ test/log
8
+ *.gemspec
9
+
10
+
11
+ .DS_Store
@@ -0,0 +1,15 @@
1
+ load "#{File.dirname __FILE__}/vex/gem.rake"
2
+
3
+ task :default => :test
4
+
5
+ task :test do
6
+ sh "ruby test/test.rb"
7
+ end
8
+
9
+ task :rcov do
10
+ sh "cd test; rcov -o ../coverage -x gems -x ^test.rb test.rb"
11
+ end
12
+
13
+ task :rdoc do
14
+ sh "rdoc -o doc/rdoc"
15
+ end
data/gem.yml ADDED
@@ -0,0 +1,13 @@
1
+ name: "dispaaro-couch"
2
+ version: "0.1"
3
+ summary: "A CouchDB handler"
4
+ description: "Yet another CouchDB handler"
5
+ homepage: http://github.com/radiospiel/dispaaro-couch
6
+ author: radiospiel
7
+ email: eno@open-lab.org
8
+ dependencies:
9
+ - etest
10
+ # - nokogiri
11
+ # - sanitize
12
+ # - htmlentities
13
+ # - json
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ require 'dlog'
3
+ require 'vex'
4
+
5
+ module Couch
6
+ def self.database
7
+ Thread.current[:database]
8
+ end
9
+
10
+ def self.database=(database)
11
+ Thread.current[:database] = database
12
+ end
13
+
14
+ def self.connect(host, port, db, recreate=false)
15
+ self.database = Database.new host, port, db, recreate
16
+ end
17
+
18
+ def self.disconnect
19
+ Thread.current[:database] = nil
20
+ end
21
+ end
22
+
23
+ load "#{File.dirname(__FILE__)}/couch/extras.rb"
24
+
25
+ load "#{File.dirname(__FILE__)}/couch/log.rb"
26
+ load "#{File.dirname(__FILE__)}/couch/database.rb"
27
+ load "#{File.dirname(__FILE__)}/couch/document.rb"
28
+ load "#{File.dirname(__FILE__)}/couch/server.rb"
29
+ load "#{File.dirname(__FILE__)}/couch/error.rb"
30
+ load "#{File.dirname(__FILE__)}/couch/view.rb"
31
+ load "#{File.dirname(__FILE__)}/couch/doc_view.rb"
@@ -0,0 +1,137 @@
1
+ require 'uuid'
2
+
3
+ class Couch::Database
4
+ include Couch::Log
5
+
6
+ def memoized_views
7
+ @memoized_views ||= {}
8
+ end
9
+
10
+ def initialize(host, port, db, recreate = false)
11
+ @server = Couch::Server.new host, port
12
+ @db = db
13
+
14
+ if recreate
15
+ Couch::Error.ignore { @server.delete("/#{db}") }
16
+ @server.put("/#{db}/", "")
17
+ else
18
+ Couch::Error.ignore { @server.put("/#{db}/", "") }
19
+ end
20
+ end
21
+
22
+ # -- forward HTTP methods to server object.
23
+
24
+ def get(url)
25
+ @server.get("/#{@db}/#{url}")
26
+ end
27
+
28
+ def post(url, data)
29
+ @server.post("/#{@db}/#{url}", data)
30
+ end
31
+
32
+ def put(url, data)
33
+ @server.put("/#{@db}/#{url}", data)
34
+ end
35
+
36
+ def delete(url)
37
+ @server.delete("/#{@db}/#{url}")
38
+ end
39
+
40
+ # -- document CRUD methods
41
+
42
+ module Base
43
+ def create(doc)
44
+ raise unless doc.new_record?
45
+
46
+ doc.id ||= UUID.new.generate
47
+
48
+ r = put(doc.id, doc)
49
+ doc.send :update_rev, r[:rev]
50
+ end
51
+
52
+ #
53
+ # read couch document from server
54
+ def read(id)
55
+ r = get(id)
56
+ Couch::Document.instantiate r
57
+ end
58
+
59
+ def update(doc)
60
+ r = put(doc.id, doc)
61
+ doc.send :update_rev, r[:rev]
62
+ end
63
+
64
+ def destroy(doc)
65
+ delete("#{doc.id}?rev=#{doc.rev}")
66
+ end
67
+ end
68
+
69
+ include Base
70
+
71
+ # -- document bulk methods
72
+
73
+ module Bulk
74
+ def self.included(klass)
75
+ klass.send :protected, :do_bulked
76
+ end
77
+
78
+ def create(doc)
79
+ return super unless @bulk
80
+
81
+ raise unless doc.new_record?
82
+ doc.id ||= UUID.new.generate
83
+
84
+ @bulk << doc
85
+ end
86
+
87
+ def read(id)
88
+ flush
89
+ super
90
+ end
91
+
92
+ def update(doc)
93
+ flush
94
+ super
95
+ end
96
+
97
+ def destroy(doc)
98
+ flush
99
+ super
100
+ end
101
+
102
+ def async(&block)
103
+ if @bulk
104
+ yield
105
+ else
106
+ do_bulked(&block)
107
+ end
108
+ end
109
+
110
+ def do_bulked(&block)
111
+ @bulk = []
112
+ yield
113
+ flush
114
+ ensure
115
+ @bulk = nil
116
+ end
117
+
118
+ def flush
119
+ return unless @bulk
120
+ bulk = @bulk
121
+ @bulk = nil
122
+
123
+ docs_by_id = {}
124
+ bulk.each do |doc|
125
+ docs_by_id[doc.id] = doc
126
+ end
127
+
128
+ post("_bulk_docs", :docs => bulk).
129
+ each do |rev_data|
130
+ id, rev = rev_data[:id], rev_data[:rev]
131
+ docs_by_id[id].send :update_rev, rev
132
+ end
133
+ end
134
+ end
135
+
136
+ include Bulk
137
+ end
@@ -0,0 +1,77 @@
1
+ class Couch::DocView < Couch::View
2
+ def self.find_or_create(klass, opts)
3
+ name = name_for(klass, opts)
4
+
5
+ database.memoized_views[name] ||= begin
6
+ Couch::DocView.find(name) ||
7
+ Couch::DocView.create(klass, name, opts)
8
+ end
9
+ end
10
+
11
+ def self.create(klass, name, opts = {})
12
+ map = <<-JS
13
+ function(doc) {
14
+ if(doc.type == #{klass.name.to_json}) { emit(#{key_expression(opts[:keys])}, 1); }
15
+ }
16
+ JS
17
+
18
+ reduce = "_sum" if opts[:mode] == :count
19
+
20
+ super name, :map => map, :reduce => reduce
21
+ end
22
+
23
+ private
24
+
25
+ def self.name_for(klass, opts)
26
+ mode = opts[:mode]
27
+ keys = opts[:keys]
28
+
29
+ name = klass.to_s + ":#{mode}"
30
+ return name if keys.empty?
31
+
32
+ "#{name}_by_" + keys.map(&:to_s).join("_")
33
+ end
34
+
35
+ def self.key_expression(keys)
36
+ case keys.length
37
+ when 0 then 1
38
+ when 1 then "doc.#{keys.first}"
39
+ else "[" + keys.map {|k| "doc.#{k}"}.join(", ") + "]"
40
+ end
41
+ end
42
+ end
43
+
44
+ module Couch::DocExtensions
45
+ def view(mode, *keys)
46
+ Couch::DocView.find_or_create(self, :mode => mode, :keys => keys)
47
+ end
48
+
49
+ #
50
+ # conditions might be empty, or might contain
51
+ # :key => required value(s) entries
52
+ def count(conditions = {})
53
+ case conditions.length
54
+ when 0
55
+ r = view(:count, *conditions.keys).all
56
+ r.inject(0) do |sum, row| sum + row[:value] end
57
+ when 1
58
+ startkey = endkey = conditions.values.first
59
+ r = view(:count, *conditions.keys).all(Couch::DocExtensions.condition_keys(conditions.values.first))
60
+ r.inject(0) do |sum, row| sum + row[:value] end
61
+ else
62
+ raise "More than one key is currently not supported"
63
+ end
64
+ end
65
+
66
+ def self.condition_keys(v)
67
+ case v
68
+ when Range
69
+ { :startkey => v.first, :endkey => v.last }
70
+ else
71
+ { :startkey => v, :endkey => v }
72
+ end
73
+ end
74
+ end
75
+
76
+
77
+ Couch::Document.extend Couch::DocExtensions
@@ -0,0 +1,140 @@
1
+ class Couch::Document
2
+ include Couch::Log
3
+
4
+ def self.database
5
+ Couch.database
6
+ end
7
+
8
+ def database
9
+ self.class.database
10
+ end
11
+
12
+ def self.async(&block)
13
+ database.async(&block)
14
+ end
15
+
16
+ def self.create(*args)
17
+ new(*args).save
18
+ end
19
+
20
+ # instantiate a Couch::Document from a Hash.
21
+ def self.instantiate(data, target = nil)
22
+ target_type_name = data[:type] || raise("Missing document type in #{data.inspect}")
23
+
24
+ if target.nil?
25
+ target_klass = target_type_name.constantize
26
+ target = target_klass.new(data)
27
+ elsif target.class.name != target_type_name
28
+ raise "Type mismatch #{target.class.name} vs #{target_type_name}"
29
+ target.update data
30
+ end
31
+ target
32
+ end
33
+
34
+ def self.find(id)
35
+ database.read(id)
36
+ rescue Couch::Error404
37
+ nil
38
+ end
39
+
40
+ attr :attributes
41
+
42
+ def initialize(data = {}, dont_change = true)
43
+ @attributes = data.dup
44
+ self.id = @attributes.delete(:id) if @attributes[:id]
45
+
46
+ @attributes[:type] = self.class.name
47
+ end
48
+
49
+ def dup
50
+ other = super
51
+ other.instance_variable_set "@attributes", attributes.dup
52
+ other.instance_variable_set "@original", @original.dup
53
+ other
54
+ end
55
+
56
+ def type; attributes[:type]; end
57
+
58
+ def id; @attributes[:_id]; end
59
+ def id=(id); @attributes[:_id] = id; end
60
+
61
+ def rev; @attributes[:_rev]; end
62
+
63
+ def to_hash
64
+ r = @attributes.dup
65
+
66
+ r.delete :_id
67
+ r.delete :_rev
68
+ r.delete :type
69
+ r
70
+ end
71
+
72
+ #
73
+ #
74
+
75
+ def new_record?
76
+ rev.nil?
77
+ end
78
+
79
+ def reload
80
+ raise if new_record?
81
+
82
+ @attributes = database.read(self.id).attributes
83
+ @original = attributes.dup
84
+ self
85
+ end
86
+
87
+ def save
88
+ if new_record?
89
+ database.create self
90
+ elsif changed?
91
+ database.update self
92
+ end
93
+
94
+ @original = attributes.dup
95
+ self
96
+ end
97
+
98
+ def changed?
99
+ @original != @attributes
100
+ end
101
+
102
+ def destroy
103
+ database.destroy self
104
+ self
105
+ end
106
+
107
+ def inspect
108
+ "<#{self.class.name}[#{self.id}] #{attributes.inspect}>"
109
+ end
110
+
111
+ private
112
+
113
+ def update_rev(rev)
114
+ attributes[:_rev] = rev
115
+ end
116
+
117
+ public
118
+
119
+ def to_json
120
+ attributes.to_json
121
+ end
122
+
123
+ def update(attributes)
124
+ self.attributes.update attributes
125
+ end
126
+
127
+ private
128
+
129
+ def method_missing(sym, *args)
130
+ if block_given?
131
+ super
132
+ elsif args.length == 0 && attributes.key?(sym)
133
+ attributes[sym]
134
+ elsif args.length == 1 && sym.to_s =~ /(.+)=$/
135
+ attributes[$1.to_sym] = args.first
136
+ else
137
+ super
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,27 @@
1
+ class Couch::Error < RuntimeError
2
+ def initialize(options); @options = options; end
3
+
4
+ def to_s; @options.inspect; end
5
+ def inspect; @options.inspect; end
6
+
7
+ def self.ignore(&block)
8
+ yield
9
+ rescue Couch::Error
10
+ dlog.warn $!
11
+ nil
12
+ end
13
+ end
14
+
15
+ class Couch::Error404 < Couch::Error
16
+ end
17
+
18
+ class Couch::Error
19
+ def self.raise(options)
20
+ case options[:code].to_s
21
+ when "404"
22
+ super Couch::Error404, options
23
+ else
24
+ super Couch::Error, options
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ class Object
2
+ def define_instance_method(sym, *args, &block)
3
+ if args.empty?
4
+ singleton_class.send(:define_method, sym, &block)
5
+ else
6
+ invalid_argument! if block_given?
7
+ r = args.first
8
+ singleton_class.send(:define_method, sym) do r end
9
+ end
10
+ self
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ module Couch::Log
2
+ QUIET = false
3
+ # QUIET = true
4
+
5
+ if QUIET
6
+ # def dlog(*args); end
7
+ end
8
+ end
@@ -0,0 +1,40 @@
1
+ require 'yajl'
2
+ require 'yajl/json_gem'
3
+ require 'net/http'
4
+
5
+ class Couch::Server
6
+ include Couch::Log
7
+
8
+ def initialize(host, port)
9
+ @host, @port = host, port
10
+ end
11
+
12
+ def delete(uri); request Net::HTTP::Delete.new(uri); end
13
+ def get(uri); request Net::HTTP::Get.new(uri); end
14
+ def put(uri, json); request Net::HTTP::Put.new(uri), json; end
15
+ def post(uri, json); request Net::HTTP::Post.new(uri), json; end
16
+
17
+ private
18
+
19
+ def request(request, body=nil)
20
+ if body
21
+ request["content-type"] = "application/json"
22
+ request.body = body.to_json
23
+ end
24
+
25
+ r = benchslow "[#{request.method}] #{request.path}" do
26
+ Net::HTTP.start(@host, @port) { |http| http.request(request) }
27
+ end
28
+
29
+ body = Yajl::Parser.new(:symbolize_keys => true).parse(r.body)
30
+ # dlog "-->", body
31
+
32
+ return body if r.kind_of?(Net::HTTPSuccess)
33
+
34
+ Couch::Error.raise :code => r.code,
35
+ :message => r.message,
36
+ :method => request.method,
37
+ :uri => request.path,
38
+ :body => body
39
+ end
40
+ end
@@ -0,0 +1,72 @@
1
+ class Couch::View < Couch::Document
2
+ DUMMY_NAME = "foo"
3
+
4
+ def self.find(name)
5
+ super("_design/#{name}")
6
+ end
7
+
8
+ # -- create a view --------------------------------------------------
9
+
10
+ def self.create(name, opts)
11
+ map = opts[:map] || raise("Missing map function")
12
+ reduce = opts[:reduce]
13
+
14
+ super :id => "_design/#{name}",
15
+ :views => { DUMMY_NAME => {
16
+ :map => map,
17
+ :reduce => reduce
18
+ } }
19
+ end
20
+
21
+ def map
22
+ view[:map]
23
+ end
24
+
25
+ def map=(map)
26
+ update :views => { DUMMY_NAME => {
27
+ :map => map,
28
+ :reduce => reduce
29
+ } }
30
+ end
31
+
32
+ def reduce
33
+ view[:reduce]
34
+ end
35
+
36
+ def reduce=(reduce)
37
+ update :views => { DUMMY_NAME => {
38
+ :map => map,
39
+ :reduce => reduce
40
+ } }
41
+ end
42
+
43
+ private
44
+
45
+ def view
46
+ views[DUMMY_NAME]
47
+ end
48
+
49
+ # -- query the view -------------------------------------------------
50
+
51
+ public
52
+
53
+ def all(opts = {})
54
+ opts = opts.to_a.inject({}) do |h, (k,v)|
55
+ h.update k => Yajl::Encoder.encode(v)
56
+ end
57
+
58
+ r = Couch.database.get CGI.url_for("#{id}/_view/#{DUMMY_NAME}", opts)
59
+ r[:rows].
60
+ define_instance_method(:offset, r[:offset]).
61
+ define_instance_method(:total_rows, r[:total_rows])
62
+ end
63
+
64
+ def total_rows(opts = nil)
65
+ if opts.nil?
66
+ r = Couch.database.get CGI.url_for("#{id}/_view/#{DUMMY_NAME}")
67
+ r[:total_rows]
68
+ else
69
+ all(opts).length
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Start a console that initializes the gem
4
+ #
5
+ require "irb"
6
+ require "rubygems"
7
+
8
+ begin
9
+ require 'wirble'
10
+ Wirble.init
11
+ Wirble.colorize
12
+ rescue LoadError
13
+ STDERR.puts "To enable colorized and tab completed run 'gem install wirble'"
14
+ end
15
+
16
+ $: << "#{File.dirname(__FILE__)}/../lib"
17
+ require "#{File.dirname(__FILE__)}/../init.rb"
18
+
19
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+ rake default build_gemspec package || exit 1
3
+ echo "============================================="
4
+ echo "I built the gem for you. To upload, run "
5
+ echo
6
+ echo "gem push $(ls -d pkg/*.gem | tail -n 1)"
7
+
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'fsevent' # This comes from ruby-fsevent
4
+ require 'yaml'
5
+
6
+ case ARGV.length
7
+ when 0
8
+ if File.exists?(".watchr.yml")
9
+ STDERR.puts "Reloading config"
10
+ args = YAML.load File.read(".watchr.yml")
11
+ end
12
+ when 1
13
+ nil
14
+ else
15
+ File.open(".watchr.yml", "w") do |f|
16
+ f.write ARGV.to_yaml
17
+ end
18
+ args = ARGV
19
+ end
20
+
21
+ if !args
22
+ STDERR.puts "watchr dir ruby-parameter ruby-parameter ruby-parameter ..."
23
+ abort
24
+ end
25
+
26
+ dirs = args.shift.split(",")
27
+ $args = args.dup
28
+
29
+ STDERR.puts "Watching #{dirs.join(",")}"
30
+
31
+ class PrintChange < FSEvent
32
+ def on_change(directories)
33
+ if !directories
34
+ STDERR.print "Initial run: "
35
+ else
36
+ STDERR.print "\n\n\nDetected change in: #{directories.first}: "
37
+ end
38
+
39
+ STDERR.puts $args.join(" ")
40
+ STDERR.puts "=" * 80
41
+ system *$args
42
+ end
43
+ end
44
+
45
+ printer = PrintChange.new
46
+ printer.on_change nil
47
+ printer.watch_directories dirs
48
+ printer.start
@@ -0,0 +1,5 @@
1
+ {
2
+ "host": "localhost",
3
+ "port": "5984",
4
+ "database": "fooxy"
5
+ }
@@ -0,0 +1,67 @@
1
+ load File.dirname(__FILE__) + "/../couch_test_helper.rb"
2
+
3
+ class BasicTest < Couch::TestCase
4
+
5
+ def test_create
6
+ post = Post.create(:body => "Post #1")
7
+ assert_equal({:body => "Post #1"}, post.to_hash)
8
+ assert !post.new_record?
9
+ end
10
+
11
+ def test_attributes
12
+ post = Post.new(:body => "Post #1")
13
+
14
+ assert_equal("Post #1", post.body)
15
+ post.body = "body"
16
+ assert_equal("body", post.body)
17
+ end
18
+
19
+ def test_basic
20
+ post = Post.new(:body => "Post #1")
21
+ assert_equal({:body => "Post #1"}, post.to_hash)
22
+ assert post.new_record?
23
+
24
+ post.save
25
+ assert_equal("Post #1", post.body)
26
+
27
+ assert_not_nil(post.rev)
28
+ assert !post.new_record?
29
+
30
+ dup = post.dup
31
+
32
+ rev1 = post.rev
33
+ post.save
34
+ assert_equal(rev1, post.rev)
35
+
36
+ post.update(:body => "Post #2")
37
+ assert !post.new_record?
38
+ post.save
39
+ assert_not_equal(rev1, post.rev)
40
+
41
+ post.reload
42
+ assert_equal(post.to_hash, { :body => "Post #2" })
43
+
44
+ assert_equal(dup.to_hash, { :body => "Post #1" })
45
+ assert_equal(rev1, dup.rev)
46
+
47
+ assert_equal({ :body => "Post #1" }, dup.to_hash)
48
+ dup.reload
49
+ assert_equal({ :body => "Post #2" }, dup.to_hash)
50
+ assert_equal(post.rev, dup.rev)
51
+ end
52
+
53
+ def test_named_object
54
+ post = Post.new(:id => "test", :body => "view")
55
+ assert_equal("test", post.id)
56
+ post.save
57
+ assert_equal("test", post.id)
58
+ end
59
+
60
+ def test_named_object_w_create
61
+ post = Post.create(:id => "test", :body => "view")
62
+ assert_equal("test", post.id)
63
+ post.save
64
+ assert_equal("test", post.id)
65
+ end
66
+
67
+ end
@@ -0,0 +1,51 @@
1
+ load File.dirname(__FILE__) + "/../couch_test_helper.rb"
2
+
3
+ class BulkTest < Couch::TestCase
4
+ attr :view
5
+
6
+ def setup
7
+ super
8
+
9
+ @view = Couch::View.create "types", :map => MAP_ONE
10
+
11
+ assert_equal(0, view.total_rows)
12
+ assert_equal(0, view.all.offset)
13
+ end
14
+
15
+ def do_test_cnt(cnt)
16
+ post = nil
17
+
18
+ benchmark "=== writing #{cnt} objects" do
19
+ Post.async do
20
+ (1..cnt).each do |i| post = Post.create(:body => "Post ##{i}") end
21
+ end
22
+ end
23
+
24
+ assert !post.new_record?
25
+
26
+ benchmark("Querying view") do assert_equal(cnt, view.total_rows) end
27
+ benchmark("Requerying view") do assert_equal(cnt, view.total_rows) end
28
+
29
+ view.map = MAP_TWICE
30
+ view.save
31
+
32
+ benchmark("Querying view") do assert_equal(2 * cnt, view.total_rows) end
33
+ benchmark("Requerying view") do assert_equal(2 * cnt, view.total_rows) end
34
+
35
+ post.destroy
36
+
37
+ benchmark("Requerying view") do assert_equal(2 * (cnt - 1), view.total_rows) end
38
+
39
+ benchmark("Count via Post.count") do assert_equal(cnt - 1, Post.count) end
40
+ benchmark("Recount via Post.count") do assert_equal(cnt - 1, Post.count) end
41
+ end
42
+
43
+ COUNTS = [ 10, 100, 1000, 10000, 100000 ]
44
+ COUNTS = [ 10, 100 ]
45
+
46
+ COUNTS.each do |i|
47
+ define_method "test_view_#{i}" do
48
+ do_test_cnt i
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,55 @@
1
+ load File.dirname(__FILE__) + "/../couch_test_helper.rb"
2
+
3
+ class DocViewTest < Couch::TestCase
4
+ def test_wo_args
5
+ p1 = Post.create(:body => "Post ##{1}")
6
+ assert_equal(1, Post.count)
7
+
8
+ p2 = Post.create(:body => "Post ##{2}")
9
+ assert_equal(2, Post.count)
10
+
11
+ p3 = Post.create(:body => "Post ##{3}")
12
+ assert_equal(3, Post.count)
13
+
14
+ p3.destroy
15
+ assert_equal(2, Post.count)
16
+ end
17
+
18
+ def test_with_one_arg
19
+ Post.create(:body => "Post ##{1}", :number => 1)
20
+ assert_equal(0, Post.count(:number => 0))
21
+
22
+ Post.create(:body => "Post ##{1}", :number => 1)
23
+
24
+ assert_equal(0, Post.count(:number => 0))
25
+ assert_equal(2, Post.count(:number => 1))
26
+ assert_equal(0, Post.count(:number => 2))
27
+
28
+
29
+ p2 = Post.create(:body => "Post ##{2}", :number => 2)
30
+
31
+ assert_equal(0, Post.count(:number => 0))
32
+ assert_equal(2, Post.count(:number => 1))
33
+ assert_equal(1, Post.count(:number => 2))
34
+
35
+ p3 = Post.create(:body => "Post ##{3}", :number => 1)
36
+ assert_equal(0, Post.count(:number => 0))
37
+ assert_equal(3, Post.count(:number => 1))
38
+ assert_equal(1, Post.count(:number => 2))
39
+
40
+ p2.destroy
41
+ assert_equal(0, Post.count(:number => 0))
42
+ assert_equal(3, Post.count(:number => 1))
43
+ assert_equal(0, Post.count(:number => 2))
44
+ end
45
+
46
+ def test_with_range_condition
47
+ Post.create(:body => "Post ##{1}", :number => 1)
48
+ Post.create(:body => "Post ##{1}", :number => 1)
49
+ Post.create(:body => "Post ##{1}", :number => 2)
50
+
51
+ assert_equal(0, Post.count(:number => 0..0.5))
52
+ assert_equal(2, Post.count(:number => -1..1))
53
+ assert_equal(3, Post.count(:number => -3..100))
54
+ end
55
+ end
@@ -0,0 +1,3 @@
1
+
2
+ class Post < Couch::Document
3
+ end
@@ -0,0 +1,60 @@
1
+ load File.dirname(__FILE__) + "/../couch_test_helper.rb"
2
+
3
+ class ViewTest < Couch::TestCase
4
+ def test_none
5
+ view = Couch::View.create "none", :map => MAP_NONE
6
+ assert_equal(0, view.total_rows)
7
+ Post.create(:body => "Post ##{1}")
8
+ assert_equal(0, view.total_rows)
9
+
10
+ view.map = MAP_ONE
11
+ view.save
12
+ assert_equal(1, view.total_rows)
13
+ end
14
+
15
+ def test_one
16
+ view = Couch::View.create "to_self", :map => MAP_ONE
17
+ assert_equal(0, view.total_rows)
18
+ Post.create(:body => "Post ##{1}")
19
+ assert_equal(1, view.total_rows)
20
+ end
21
+
22
+ def test_start_end_key
23
+ view = Couch::View.create "to_self", :map => MAP_ONE
24
+ assert_equal(0, view.total_rows)
25
+ Post.create(:body => "Post ##{1}")
26
+ assert_equal(1, view.total_rows)
27
+
28
+ assert_equal(1, view.total_rows(:startkey => "p", :endkey => "q"))
29
+ assert_equal(0, view.total_rows(:startkey => "q", :endkey => "r"))
30
+ end
31
+
32
+ def test_view
33
+ view = Couch::View.create "types", :map => MAP_ONE
34
+
35
+ assert_equal(0, view.total_rows)
36
+ assert_equal(0, view.all.offset)
37
+
38
+ post = nil
39
+
40
+ benchmark "writing #{COUNT} objects" do
41
+ (1..COUNT).each do |i|
42
+ post = Post.create(:body => "Post ##{i}")
43
+ assert !post.new_record?
44
+ end
45
+ end
46
+
47
+ assert_equal(COUNT, view.total_rows)
48
+ assert_equal(COUNT, view.total_rows)
49
+
50
+ view.map = MAP_TWICE
51
+ view.save
52
+
53
+ assert_equal(2 * COUNT, view.total_rows)
54
+ assert_equal(2 * COUNT, view.total_rows)
55
+
56
+ post.destroy
57
+
58
+ assert_equal(2 * (COUNT - 1), view.total_rows)
59
+ end
60
+ end
@@ -0,0 +1,31 @@
1
+ unless defined?(Couch::TestCase)
2
+
3
+ require File.dirname(__FILE__) + "/test_helper"
4
+ require File.dirname(__FILE__) + "/../lib/couch"
5
+
6
+ require File.dirname(__FILE__) + "/couch/post"
7
+
8
+ CONFIG = JSON.parse(File.read(File.dirname(__FILE__) + "/couch.json"))
9
+ dlog "Using couchdb config", CONFIG
10
+
11
+ class Couch::TestCase < BaseTestCase
12
+ MAP_ONE = "function(post) { if(post.type) { emit(post.type); } }"
13
+ MAP_TWICE = "function(post) { if(post.type) { emit(1); emit(2); } }"
14
+ MAP_NONE = "function(post) { }"
15
+
16
+ COUNT = 10
17
+
18
+ def setup
19
+ host = CONFIG["host"]
20
+ port = CONFIG["port"] || 5984
21
+ database = CONFIG["database"]
22
+
23
+ Couch.connect(host, port, database, true)
24
+ end
25
+
26
+ def teardown
27
+ Couch.disconnect
28
+ end
29
+ end
30
+
31
+ end
@@ -0,0 +1,14 @@
1
+ #require "rubygems"
2
+ #require "etest"
3
+
4
+ #$: << File.dirname(__FILE__) + "/../lib"
5
+ #require 'couch'
6
+
7
+ Dir.glob(File.dirname(__FILE__) + "/couch/*_test.rb").each do |file|
8
+ load file
9
+ end
10
+
11
+ LOGFILE = "log/test.log"
12
+
13
+ #Etest.autorun
14
+
@@ -0,0 +1,30 @@
1
+ unless defined?(BaseTestCase)
2
+
3
+ require "rubygems"
4
+ require "ruby-debug"
5
+
6
+ # Use test/unit
7
+ #
8
+ # require "test/unit"
9
+ #
10
+ # class BaseTestCase < Test::Unit::TestCase
11
+ # def test_dummy
12
+ # # or else test unit cries out loud-
13
+ # end
14
+ # end
15
+
16
+ require "minitest/unit"
17
+ require 'minitest/autorun'
18
+
19
+ class BaseTestCase < MiniTest::Unit::TestCase
20
+ def assert_not_nil(object, message = nil)
21
+ assert !object.nil?, message || "Should not be nil"
22
+ end
23
+
24
+ def assert_not_equal(unexpected, actual, message = nil)
25
+ assert unexpected != actual, message || "#{actual.inspect} should not equal #{unexpected.inspect}"
26
+ end
27
+ end
28
+
29
+ end
30
+
@@ -0,0 +1,36 @@
1
+ def sys(*args)
2
+ STDERR.puts "#{args.join(" ")}"
3
+ system *args
4
+ end
5
+
6
+ namespace :gem do
7
+ task :build => :spec do
8
+ options = []
9
+ sys "gem build .gemspec #{options.join(" ")} && mkdir -p pkg && mv *.gem pkg"
10
+ end
11
+
12
+ task :spec do
13
+ File.open ".gemspec", "w" do |file|
14
+ file.write <<-TXT
15
+ require "vex/gem"
16
+ Gem.spec File.dirname(__FILE__)
17
+ TXT
18
+ end
19
+ end
20
+
21
+ task :install => :build do
22
+ file = Dir.glob("pkg/*.gem").sort.last
23
+ sys "gem install #{file}"
24
+ end
25
+
26
+ task :push => :build do
27
+ file = Dir.glob("pkg/*.gem").sort.last
28
+ puts "To push the gem to gemcutter please run"
29
+ puts
30
+ puts "\tgem push #{file}"
31
+ end
32
+ end
33
+
34
+ desc "Build gem"
35
+ # task :gem => %w(test gem:install)
36
+ task :gem => %w(gem:install gem:push)
@@ -0,0 +1,95 @@
1
+ module Gem
2
+ (class << self; self; end).class_eval do
3
+ def default_attributes
4
+ %w(name version date files executables)
5
+ end
6
+
7
+ def set_root(root)
8
+ @root = File.expand_path(root)
9
+ @name = File.basename(@root)
10
+ @conf ||= YAML.load File.read("#{@root}/gem.yml")
11
+ @attributes ||= (default_attributes + @conf.keys.map(&:to_s)).uniq
12
+ end
13
+
14
+ # attr_reader :root, :conf, :attributes,
15
+ attr_reader :name
16
+
17
+ def attribute(name)
18
+ if @conf.key?(name) && !%w(dependencies).include?(name)
19
+ @conf[name]
20
+ else
21
+ self.send(name)
22
+ end
23
+ end
24
+
25
+ #
26
+ # create a new Gem::Specification object for this gem.
27
+ def spec(root)
28
+ self.set_root root
29
+
30
+ Gem::Specification.new do |s|
31
+ @attributes.each do |attr|
32
+ v = attribute(attr)
33
+ next if v.nil?
34
+
35
+ log attr, v
36
+
37
+ s.send attr + "=", v
38
+ end
39
+
40
+ %w(pre_uninstall post_install).each do |hook|
41
+ next unless File.exists?("#{root}/hooks/#{hook}.rb")
42
+ log hook, "yes"
43
+ Gem.send(hook) {
44
+ load "hooks/#{hook}.rb"
45
+ }
46
+ end
47
+ end
48
+ end
49
+
50
+ def log(attr, v)
51
+ v = case attr
52
+ when "files" then "#{v.length} files"
53
+ else v.inspect
54
+ end
55
+
56
+ STDERR.puts "#{"%20s" % attr}:\t#{v}"
57
+ end
58
+
59
+ def dependencies
60
+ return nil unless @conf["dependencies"]
61
+
62
+ @conf["dependencies"].map do |d|
63
+ Gem::Dependency.new d, ">= 0"
64
+ end
65
+ end
66
+
67
+ def head(file)
68
+ File.open(file) do |f|
69
+ f.readline
70
+ end
71
+ end
72
+
73
+ def executables
74
+ r = Dir.glob("#{@root}/bin/**/*").map do |file|
75
+ next unless head(file) =~ /^#!/
76
+ file[@root.length + 5 .. -1]
77
+ end.compact
78
+
79
+ return nil if r.empty?
80
+ r
81
+ end
82
+
83
+ #
84
+ # get files from git
85
+ def files
86
+ r = `git ls-files`.split("\n")
87
+ end
88
+
89
+ #
90
+ # return the date
91
+ def date
92
+ Date.today.to_s
93
+ end
94
+ end
95
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dispaaro-couch
3
+ version: !ruby/object:Gem::Version
4
+ hash: 9
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ version: "0.1"
10
+ platform: ruby
11
+ authors:
12
+ - radiospiel
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-12-08 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: etest
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ description: Yet another CouchDB handler
35
+ email: eno@open-lab.org
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files: []
41
+
42
+ files:
43
+ - .gitignore
44
+ - Rakefile
45
+ - gem.yml
46
+ - lib/couch.rb
47
+ - lib/couch/database.rb
48
+ - lib/couch/doc_view.rb
49
+ - lib/couch/document.rb
50
+ - lib/couch/error.rb
51
+ - lib/couch/extras.rb
52
+ - lib/couch/log.rb
53
+ - lib/couch/server.rb
54
+ - lib/couch/view.rb
55
+ - script/console
56
+ - script/rebuild
57
+ - script/watchr
58
+ - test/couch.json
59
+ - test/couch/basic_test.rb
60
+ - test/couch/bulk_test.rb
61
+ - test/couch/doc_view_test.rb
62
+ - test/couch/post.rb
63
+ - test/couch/view_test.rb
64
+ - test/couch_test_helper.rb
65
+ - test/test.rb
66
+ - test/test_helper.rb
67
+ - vex/gem.rake
68
+ - vex/gem.rb
69
+ has_rdoc: true
70
+ homepage: http://github.com/radiospiel/dispaaro-couch
71
+ licenses: []
72
+
73
+ post_install_message:
74
+ rdoc_options: []
75
+
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ hash: 3
84
+ segments:
85
+ - 0
86
+ version: "0"
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 3
93
+ segments:
94
+ - 0
95
+ version: "0"
96
+ requirements: []
97
+
98
+ rubyforge_project:
99
+ rubygems_version: 1.3.7
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: A CouchDB handler
103
+ test_files: []
104
+