dispaaro-couch 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+