couch_docs 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,14 @@
1
+ == 1.0.0 / 2009-08-09
2
+
3
+ * Update the couch-docs script to be able to dump a CouchDB database
4
+ to a local directory as well as uploading a local directory into a
5
+ CouchDB database.
6
+
7
+ * CouchDB revision numbers are stripped when dumping (to prevent
8
+ conflicts when re-loading)
9
+
10
+ * Attachments are dumped as well.
11
+
12
+ == 0.9.0 / 2009-08-08
13
+
14
+ * Import from couch_design_docs (name change to reflect increased functionality)
data/README.rdoc ADDED
@@ -0,0 +1,93 @@
1
+ couch_docs
2
+ by Chris Strom
3
+ http://github.com/eee-c/couch_docs
4
+ (used to be couch_design_docs)
5
+
6
+ == DESCRIPTION:
7
+
8
+ Manage CouchDB views and documents.
9
+
10
+ == FEATURES/PROBLEMS:
11
+
12
+ * Upload JSON documents stored on the filesystem into a CouchDB
13
+ database.
14
+
15
+ * Map <tt>.js</tt> files stored on the filesystem
16
+ (e.g. <tt>_design/recipes/count_by_month/map.js</tt>) into CouchDB
17
+ design documents.
18
+
19
+ * Dump documents stored in CouchDB to the filesystem.
20
+
21
+ * Script (couch-docs) to restore / backup CouchDB database.
22
+
23
+ * The couch-docs scipts does not work with design documents.
24
+
25
+ * A progress bar would be helpful.
26
+
27
+ * Unit testing of view javascript would be very nice.
28
+
29
+ == SYNOPSIS:
30
+
31
+ From the command line:
32
+
33
+ # For dumping the contents of a CouchDB database to the filesystem
34
+ couch-docs dump "http://localhost:5984/db" path/to/dump_dir/
35
+
36
+ # For loading documents from the filesystem into CouchDB
37
+ couch-docs load path/to/dump_dir/ "http://localhost:5984/db"
38
+
39
+
40
+ In code:
41
+
42
+ DB_URL = "http://localhost:5984/db"
43
+ DIRECTORY = "/repos/db/couchdb/"
44
+
45
+ # /repos/db/couchdb/_design/lucene/transform.js
46
+ # /repos/db/couchdb/foo.json
47
+
48
+ CouchDocs.put_dir(DB_URL, DIRECTORY)
49
+
50
+ # => lucene design document with a "transform" function containing
51
+ # the contents of transform.js
52
+ # - AND -
53
+ # a document named "foo" with the JSON contents from the foo.json
54
+ # file
55
+
56
+ CouchDocs.dump(DB_URL, "/repos/db/bak")
57
+
58
+ # => JSON dump of every document at DB_URL
59
+
60
+ == REQUIREMENTS:
61
+
62
+ * CouchDB
63
+ * JSON
64
+ * RestClient
65
+
66
+ == INSTALL:
67
+
68
+ * sudo gem install eee-c-couch_docs
69
+
70
+ == LICENSE:
71
+
72
+ (The MIT License)
73
+
74
+ Copyright (c) 2009
75
+
76
+ Permission is hereby granted, free of charge, to any person obtaining
77
+ a copy of this software and associated documentation files (the
78
+ 'Software'), to deal in the Software without restriction, including
79
+ without limitation the rights to use, copy, modify, merge, publish,
80
+ distribute, sublicense, and/or sell copies of the Software, and to
81
+ permit persons to whom the Software is furnished to do so, subject to
82
+ the following conditions:
83
+
84
+ The above copyright notice and this permission notice shall be
85
+ included in all copies or substantial portions of the Software.
86
+
87
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
88
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
89
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
90
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
91
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
92
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
93
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ # Look in the tasks/setup.rb file for the various options that can be
2
+ # configured in this Rakefile. The .rake files in the tasks directory
3
+ # are where the options are used.
4
+
5
+ begin
6
+ require 'bones'
7
+ Bones.setup
8
+ rescue LoadError
9
+ begin
10
+ load 'tasks/setup.rb'
11
+ rescue LoadError
12
+ raise RuntimeError, '### please install the "bones" gem ###'
13
+ end
14
+ end
15
+
16
+ ensure_in_path 'lib'
17
+ require 'couch_docs'
18
+
19
+ task :default => 'spec:run'
20
+
21
+ PROJ.name = 'couch_docs'
22
+ PROJ.authors = 'Chris Strom'
23
+ PROJ.email = 'chris@eeecooks.com'
24
+ PROJ.url = 'http://github.com/eee-c/couch_docs'
25
+ PROJ.version = CouchDocs::VERSION
26
+ PROJ.rubyforge.name = 'couch_docs'
27
+
28
+ PROJ.spec.opts << '--color'
29
+
30
+ PROJ.gem.dependencies = %w{json rest-client}
31
+
32
+ PROJ.readme_file = 'README.rdoc'
33
+
34
+ depend_on 'rest-client'
35
+ depend_on 'json'
36
+
37
+ # EOF
data/bin/couch-docs ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(
4
+ File.join(File.dirname(__FILE__), %w[.. lib couch_docs]))
5
+
6
+ # Put your code here
7
+
8
+ CouchDocs::CommandLine.run ARGV
9
+
10
+ # EOF
@@ -0,0 +1,49 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{couch_docs}
5
+ s.version = "1.0.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Chris Strom"]
9
+ s.date = %q{2009-08-09}
10
+ s.default_executable = %q{couch-docs}
11
+ s.description = %q{Manage CouchDB views and documents.}
12
+ s.email = %q{chris@eeecooks.com}
13
+ s.executables = ["couch-docs"]
14
+ s.extra_rdoc_files = ["History.txt", "README.rdoc", "bin/couch-docs"]
15
+ s.files = ["History.txt", "README.rdoc", "Rakefile", "bin/couch-docs", "couch_docs.gemspec", "fixtures/_design/a/b/c.js", "fixtures/_design/a/b/d.js", "fixtures/bar.json", "fixtures/foo.json", "lib/couch_docs.rb", "lib/couch_docs/command_line.rb", "lib/couch_docs/design_directory.rb", "lib/couch_docs/document_directory.rb", "lib/couch_docs/store.rb", "spec/couch_docs_spec.rb", "spec/spec_helper.rb", "test/test_couch_docs.rb"]
16
+ s.has_rdoc = true
17
+ s.homepage = %q{http://github.com/eee-c/couch_docs}
18
+ s.rdoc_options = ["--main", "README.rdoc"]
19
+ s.require_paths = ["lib"]
20
+ s.rubyforge_project = %q{couch_docs}
21
+ s.rubygems_version = %q{1.3.1}
22
+ s.summary = %q{Manage CouchDB views and documents}
23
+ s.test_files = ["test/test_couch_docs.rb"]
24
+
25
+ if s.respond_to? :specification_version then
26
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
27
+ s.specification_version = 2
28
+
29
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
30
+ s.add_runtime_dependency(%q<json>, [">= 0"])
31
+ s.add_runtime_dependency(%q<rest-client>, [">= 0"])
32
+ s.add_runtime_dependency(%q<rest-client>, [">= 1.0.3"])
33
+ s.add_runtime_dependency(%q<json>, [">= 1.1.6"])
34
+ s.add_development_dependency(%q<bones>, [">= 2.5.1"])
35
+ else
36
+ s.add_dependency(%q<json>, [">= 0"])
37
+ s.add_dependency(%q<rest-client>, [">= 0"])
38
+ s.add_dependency(%q<rest-client>, [">= 1.0.3"])
39
+ s.add_dependency(%q<json>, [">= 1.1.6"])
40
+ s.add_dependency(%q<bones>, [">= 2.5.1"])
41
+ end
42
+ else
43
+ s.add_dependency(%q<json>, [">= 0"])
44
+ s.add_dependency(%q<rest-client>, [">= 0"])
45
+ s.add_dependency(%q<rest-client>, [">= 1.0.3"])
46
+ s.add_dependency(%q<json>, [">= 1.1.6"])
47
+ s.add_dependency(%q<bones>, [">= 2.5.1"])
48
+ end
49
+ end
@@ -0,0 +1 @@
1
+ function(doc) { return true; }
@@ -0,0 +1 @@
1
+ function(doc) { return true; }
data/fixtures/bar.json ADDED
@@ -0,0 +1 @@
1
+ {"bar":"2"}
data/fixtures/foo.json ADDED
@@ -0,0 +1 @@
1
+ {"foo":"1"}
@@ -0,0 +1,28 @@
1
+ module CouchDocs
2
+ class CommandLine
3
+ def self.run(*args)
4
+ CommandLine.new(*args).run
5
+ end
6
+
7
+ attr_accessor :command, :options
8
+
9
+ def initialize(args)
10
+ @command = args.shift
11
+ @options = args
12
+ end
13
+
14
+ def run
15
+ case command
16
+ when "dump"
17
+ CouchDocs.dump(*options)
18
+ when "load"
19
+ CouchDocs.put_document_dir(*options.reverse)
20
+ when "help", "--help", "-h"
21
+ puts "#{$0} load dir couchdb_uri"
22
+ puts "#{$0} dump couchdb_uri dir"
23
+ else
24
+ raise ArgumentError.new("Unknown command #{command}")
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,46 @@
1
+ class Hash
2
+ def deep_merge(other)
3
+ self.merge(other) do |key, oldval, newval|
4
+ oldval.deep_merge(newval)
5
+ end
6
+ end
7
+ end
8
+
9
+ module CouchDocs
10
+ class DesignDirectory
11
+
12
+ attr_accessor :couch_view_dir
13
+
14
+ def self.a_to_hash(a)
15
+ key = a.first
16
+ if (a.length > 2)
17
+ { key => a_to_hash(a[1,a.length]) }
18
+ else
19
+ { key => a.last }
20
+ end
21
+ end
22
+
23
+ def initialize(path)
24
+ Dir.new(path) # Just checkin'
25
+ @couch_view_dir = path
26
+ end
27
+
28
+ def to_hash
29
+ Dir["#{couch_view_dir}/**/*.js"].inject({}) do |memo, filename|
30
+ DesignDirectory.
31
+ a_to_hash(expand_file(filename)).
32
+ deep_merge(memo)
33
+ end
34
+ end
35
+
36
+ def expand_file(filename)
37
+ File.dirname(filename).
38
+ gsub(/#{couch_view_dir}\/?/, '').
39
+ split(/\//) +
40
+ [
41
+ File.basename(filename, '.js'),
42
+ File.new(filename).read
43
+ ]
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,25 @@
1
+ module CouchDocs
2
+ class DocumentDirectory
3
+
4
+ attr_accessor :couch_doc_dir
5
+
6
+ def initialize(path)
7
+ Dir.new(path)
8
+ @couch_doc_dir = path
9
+ end
10
+
11
+ def each_document
12
+ Dir["#{couch_doc_dir}/*.json"].each do |filename|
13
+ yield [ File.basename(filename, '.json'),
14
+ JSON.parse(File.new(filename).read) ]
15
+
16
+ end
17
+ end
18
+
19
+ def store_document(doc)
20
+ file = File.new("#{couch_doc_dir}/#{doc['_id']}.json", "w+")
21
+ file.write(doc.to_json)
22
+ file.close
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,64 @@
1
+ require 'rubygems'
2
+ require 'restclient'
3
+ require 'json'
4
+
5
+ module CouchDocs
6
+ class Store
7
+ include Enumerable
8
+
9
+ attr_accessor :url
10
+
11
+ # Initialize a CouchDB store object. Requires a URL for the
12
+ # target CouchDB database.
13
+ #
14
+ def initialize(url)
15
+ @url = url
16
+ end
17
+
18
+ # Loads all supplied design documents in the current store.
19
+ # Given a hash <tt>h</tt>, the keys being the CouchDB document
20
+ # name and values of design documents
21
+ #
22
+ def put_design_documents(h)
23
+ h.each_pair do |document_name, doc|
24
+ Store.put!("#{url}/_design/#{document_name}", doc)
25
+ end
26
+ end
27
+
28
+ # Create or replace the document located at <tt>path</tt> with the
29
+ # Hash document <tt>doc</tt>
30
+ #
31
+ def self.put!(path, doc)
32
+ self.put(path, doc)
33
+ rescue RestClient::RequestFailed
34
+ self.delete_and_put(path, doc)
35
+ end
36
+
37
+ def self.delete_and_put(path, doc)
38
+ self.delete(path)
39
+ self.put(path, doc)
40
+ end
41
+
42
+ def self.put(path, doc)
43
+ RestClient.put path,
44
+ doc.to_json,
45
+ :content_type => 'application/json'
46
+ end
47
+
48
+ def self.delete(path)
49
+ # retrieve existing to obtain the revision
50
+ old = self.get(path)
51
+ RestClient.delete(path + "?rev=#{old['_rev']}")
52
+ end
53
+
54
+ def self.get(path)
55
+ JSON.parse(RestClient.get(path))
56
+ end
57
+
58
+ def each
59
+ Store.get("#{url}/_all_docs")['rows'].each do |rec|
60
+ yield Store.get("#{url}/#{rec['id']}?attachments=true")
61
+ end
62
+ end
63
+ end
64
+ end
data/lib/couch_docs.rb ADDED
@@ -0,0 +1,94 @@
1
+ module CouchDocs
2
+
3
+ # :stopdoc:
4
+ VERSION = '1.0.0'
5
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
6
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
7
+ # :startdoc:
8
+
9
+ # Returns the version string for the library.
10
+ #
11
+ def self.version
12
+ VERSION
13
+ end
14
+
15
+ # For a CouchDB database described by <tt>db_uri</tt> and a
16
+ # directory, <tt>dir</tt> containing design documents, creates
17
+ # design documents in the CouchDB database
18
+ #
19
+ def self.put_dir(db_uri, dir)
20
+ self.put_design_dir(db_uri, "#{dir}/_design")
21
+ self.put_document_dir(db_uri, dir)
22
+ end
23
+
24
+ # Alias for <tt>put_dir</tt>
25
+ def self.upload_dir(db_uri, dir)
26
+ self.put_dir(db_uri, dir)
27
+ end
28
+
29
+ # Upload design documents from <tt>dir</tt> to the CouchDB database
30
+ # located at <tt>db_uri</tt>
31
+ #
32
+ def self.put_design_dir(db_uri, dir)
33
+ store = Store.new(db_uri)
34
+ dir = DesignDirectory.new(dir)
35
+ store.put_design_documents(dir.to_hash)
36
+ end
37
+
38
+ # Upload documents from <tt>dir</tt> to the CouchDB database
39
+ # located at <tt>db_uri</tt>
40
+ #
41
+ def self.put_document_dir(db_uri, dir)
42
+ store = Store.new(db_uri)
43
+ dir = DocumentDirectory.new(dir)
44
+ dir.each_document do |name, contents|
45
+ Store.put!("#{db_uri}/#{name}", contents)
46
+ end
47
+ end
48
+
49
+ # Dump all documents located at <tt>db_uri</tt> into the directory
50
+ # <tt>dir</tt>
51
+ #
52
+ def self.dump(db_uri, dir)
53
+ store = Store.new(db_uri)
54
+ dir = DocumentDirectory.new(dir)
55
+ store.
56
+ map.
57
+ reject { |doc| doc['_id'] =~ /^_design/ }.
58
+ each { |doc| doc.delete('_rev'); dir.store_document(doc) }
59
+ end
60
+
61
+ # Returns the library path for the module. If any arguments are given,
62
+ # they will be joined to the end of the libray path using
63
+ # <tt>File.join</tt>.
64
+ #
65
+ def self.libpath( *args )
66
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
67
+ end
68
+
69
+ # Returns the lpath for the module. If any arguments are given,
70
+ # they will be joined to the end of the path using
71
+ # <tt>File.join</tt>.
72
+ #
73
+ def self.path( *args )
74
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
75
+ end
76
+
77
+ # Utility method used to require all files ending in .rb that lie in the
78
+ # directory below this file that has the same name as the filename passed
79
+ # in. Optionally, a specific _directory_ name can be passed in such that
80
+ # the _filename_ does not have to be equivalent to the directory.
81
+ #
82
+ def self.require_all_libs_relative_to( fname, dir = nil )
83
+ dir ||= ::File.basename(fname, '.*')
84
+ search_me = ::File.expand_path(
85
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
86
+
87
+ Dir.glob(search_me).sort.each {|rb| require rb}
88
+ end
89
+
90
+ end # module CouchDocs
91
+
92
+ CouchDocs.require_all_libs_relative_to(__FILE__)
93
+
94
+ # EOF
@@ -0,0 +1,329 @@
1
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
2
+
3
+ describe CouchDocs do
4
+ it "should be able to load design and normal documents" do
5
+ CouchDocs.
6
+ should_receive(:put_design_dir).
7
+ with("uri", "fixtures/_design")
8
+
9
+ CouchDocs.
10
+ should_receive(:put_document_dir).
11
+ with("uri", "fixtures")
12
+
13
+ CouchDocs.put_dir("uri", "fixtures")
14
+ end
15
+
16
+ it "should be able to load directory/JS files into CouchDB as design docs" do
17
+ store = mock("Store")
18
+ Store.stub!(:new).and_return(store)
19
+
20
+ dir = mock("Design Directory")
21
+ dir.stub!(:to_hash).and_return({ "foo" => "bar" })
22
+ DesignDirectory.stub!(:new).and_return(dir)
23
+
24
+ store.
25
+ should_receive(:put_design_documents).
26
+ with({ "foo" => "bar" })
27
+
28
+ CouchDocs.put_design_dir("uri", "fixtures")
29
+ end
30
+
31
+ it "should be able to load documents into CouchDB" do
32
+ store = mock("Store")
33
+ Store.stub!(:new).and_return(store)
34
+
35
+ dir = mock("Document Directory")
36
+ dir.
37
+ stub!(:each_document).
38
+ and_yield('foo', {"foo" => "1"})
39
+
40
+ DocumentDirectory.stub!(:new).and_return(dir)
41
+
42
+ Store.
43
+ should_receive(:put!).
44
+ with('uri/foo', {"foo" => "1"})
45
+
46
+ CouchDocs.put_document_dir("uri", "fixtures")
47
+ end
48
+
49
+ context "dumping CouchDB documents to a directory" do
50
+ before(:each) do
51
+ @store = mock("Store")
52
+ Store.stub!(:new).and_return(@store)
53
+
54
+ @dir = mock("Document Directory")
55
+ DocumentDirectory.stub!(:new).and_return(@dir)
56
+ end
57
+ it "should be able to store all CouchDB documents on the filesystem" do
58
+ @store.stub!(:map).and_return([{'_id' => 'foo'}])
59
+ @dir.
60
+ should_receive(:store_document).
61
+ with({'_id' => 'foo'})
62
+
63
+ CouchDocs.dump("uri", "fixtures")
64
+ end
65
+ it "should ignore design documents" do
66
+ @store.stub!(:map).and_return([{'_id' => '_design/foo'}])
67
+ @dir.
68
+ should_not_receive(:store_document)
69
+
70
+ CouchDocs.dump("uri", "fixtures")
71
+ end
72
+ it "should strip revision numbers" do
73
+ @store.stub!(:map).
74
+ and_return([{'_id' => 'foo', '_rev' => '1-1234'}])
75
+ @dir.
76
+ should_receive(:store_document).
77
+ with({'_id' => 'foo'})
78
+
79
+ CouchDocs.dump("uri", "fixtures")
80
+ end
81
+ end
82
+ end
83
+
84
+
85
+ describe Store do
86
+ it "should require a CouchDB URL Root for instantiation" do
87
+ lambda { Store.new }.
88
+ should raise_error
89
+
90
+ lambda { Store.new("uri") }.
91
+ should_not raise_error
92
+ end
93
+
94
+ context "a valid store" do
95
+ before(:each) do
96
+ @it = Store.new("uri")
97
+
98
+ @hash = {
99
+ 'a' => {
100
+ 'b' => {
101
+ 'c' => 'function(doc) { return true; }'
102
+ }
103
+ }
104
+ }
105
+ end
106
+
107
+ it "should be able to put a new document" do
108
+ Store.
109
+ should_receive(:put).
110
+ with("uri", { })
111
+
112
+ Store.put!("uri", { })
113
+ end
114
+
115
+ it "should delete existing docs if first put fails" do
116
+ Store.
117
+ stub!(:put).
118
+ and_raise(RestClient::RequestFailed)
119
+
120
+ Store.
121
+ should_receive(:delete_and_put).
122
+ with("uri", { })
123
+
124
+ Store.put!("uri", { })
125
+ end
126
+
127
+ it "should be able to delete and put" do
128
+ Store.
129
+ should_receive(:delete).
130
+ with("uri")
131
+
132
+ Store.
133
+ should_receive(:put).
134
+ with("uri", { })
135
+
136
+ Store.delete_and_put("uri", { })
137
+ end
138
+
139
+ it "should be able to load a hash into design docs" do
140
+ RestClient.
141
+ should_receive(:put).
142
+ with("uri/_design/a",
143
+ '{"b":{"c":"function(doc) { return true; }"}}',
144
+ :content_type => 'application/json')
145
+ @it.put_design_documents(@hash)
146
+ end
147
+
148
+ it "should be able to retrieve an existing document" do
149
+ RestClient.
150
+ stub!(:get).
151
+ and_return('{"_rev":"1234"}')
152
+
153
+ Store.get("uri").should == { '_rev' => "1234" }
154
+ end
155
+
156
+ it "should be able to delete an existing document" do
157
+ Store.stub!(:get).and_return({ '_rev' => '1234' })
158
+
159
+ RestClient.
160
+ should_receive(:delete).
161
+ with("uri?rev=1234")
162
+
163
+ Store.delete("uri")
164
+ end
165
+
166
+ it "should be able to load each document" do
167
+ Store.stub!(:get).
168
+ with("uri/_all_docs").
169
+ and_return({ "total_rows" => 2,
170
+ "offset" => 0,
171
+ "rows" => [{"id"=>"1", "value"=>{}, "key"=>"1"},
172
+ {"id"=>"2", "value"=>{}, "key"=>"2"}]})
173
+
174
+ Store.stub!(:get).with("uri/1?attachments=true")
175
+ Store.should_receive(:get).with("uri/2?attachments=true")
176
+
177
+ @it.each { }
178
+ end
179
+ end
180
+ end
181
+
182
+ describe DocumentDirectory do
183
+ it "should require a root directory for instantiation" do
184
+ lambda { DocumentDirectory.new }.
185
+ should raise_error
186
+
187
+ lambda { DocumentDirectory.new("foo") }.
188
+ should raise_error
189
+
190
+ lambda { DocumentDirectory.new("fixtures")}.
191
+ should_not raise_error
192
+ end
193
+
194
+ context "a valid directory" do
195
+ before(:each) do
196
+ @it = DocumentDirectory.new("fixtures")
197
+ end
198
+
199
+ it "should be able to iterate over the documents" do
200
+ everything = []
201
+ @it.each_document do |name, contents|
202
+ everything << [name, contents]
203
+ end
204
+ everything.
205
+ should == [['bar', {"bar" => "2"}],
206
+ ['foo', {"foo" => "1"}]]
207
+ end
208
+
209
+ it "should be able to store a document" do
210
+ file = mock("File", :write => 42, :close => true)
211
+ File.
212
+ should_receive(:new).
213
+ with("fixtures/foo.json", "w+").
214
+ and_return(file)
215
+
216
+ @it.store_document({'_id' => 'foo'})
217
+ end
218
+
219
+ it "should be able to save a document as JSON" do
220
+ file = mock("File", :close => true)
221
+ File.stub!(:new).and_return(file)
222
+
223
+ file.should_receive(:write).with(%Q|{"_id":"foo"}|)
224
+
225
+ @it.store_document({'_id' => 'foo'})
226
+ end
227
+ end
228
+ end
229
+
230
+ describe DesignDirectory do
231
+ it "should require a root directory for instantiation" do
232
+ lambda { DesignDirectory.new }.
233
+ should raise_error
234
+
235
+ lambda { DesignDirectory.new("foo") }.
236
+ should raise_error
237
+
238
+ lambda { DesignDirectory.new("fixtures/_design")}.
239
+ should_not raise_error
240
+ end
241
+
242
+ it "should convert arrays into deep hashes" do
243
+ DesignDirectory.
244
+ a_to_hash(%w{a b c d}).
245
+ should == {
246
+ 'a' => {
247
+ 'b' => {
248
+ 'c' => 'd'
249
+ }
250
+ }
251
+ }
252
+ end
253
+
254
+ context "a valid directory" do
255
+ before(:each) do
256
+ @it = DesignDirectory.new("fixtures/_design")
257
+ end
258
+
259
+ it "should list dirs, basename and contents of a file" do
260
+ @it.expand_file("fixtures/_design/a/b/c.js").
261
+ should == ['a', 'b', 'c', 'function(doc) { return true; }']
262
+ end
263
+
264
+ it "should assemble all documents into a single docs structure" do
265
+ @it.to_hash.
266
+ should == {
267
+ 'a' => {
268
+ 'b' => {
269
+ 'c' => 'function(doc) { return true; }',
270
+ 'd' => 'function(doc) { return true; }'
271
+ }
272
+ }
273
+
274
+ }
275
+ end
276
+ end
277
+ end
278
+
279
+ describe CommandLine do
280
+ it "should be able to run a single instance of a command line" do
281
+ CommandLine.
282
+ should_receive(:new).
283
+ with('foo', 'bar').
284
+ and_return(mock("Command Line").as_null_object)
285
+
286
+ CommandLine.run('foo', 'bar')
287
+ end
288
+
289
+ it "should run the command line instance" do
290
+ command_line = mock("Command Line").as_null_object
291
+ command_line.
292
+ should_receive(:run)
293
+
294
+ CommandLine.stub!(:new).and_return(command_line)
295
+
296
+ CommandLine.run('foo', 'bar')
297
+ end
298
+
299
+ context "an instance that dumps a CouchDB database" do
300
+ before(:each) do
301
+ @it = CommandLine.new(['dump', 'uri', 'dir'])
302
+ end
303
+
304
+ it "should dump CouchDB documents from uri to dir when run" do
305
+ CouchDocs.
306
+ should_receive(:dump).
307
+ with("uri", "dir")
308
+
309
+ @it.run
310
+ end
311
+ end
312
+
313
+ context "an instance that uploads to a CouchDB database" do
314
+ before(:each) do
315
+ @it = CommandLine.new(['load', 'dir', 'uri'])
316
+ end
317
+
318
+ it "should dump CouchDB documents from uri to dir when run" do
319
+ CouchDocs.
320
+ should_receive(:put_document_dir).
321
+ with("uri", "dir")
322
+
323
+ @it.run
324
+ end
325
+ end
326
+
327
+ end
328
+
329
+ # EOF
@@ -0,0 +1,18 @@
1
+
2
+ require File.expand_path(
3
+ File.join(File.dirname(__FILE__), %w[.. lib couch_docs]))
4
+
5
+ include CouchDocs
6
+
7
+ Spec::Runner.configure do |config|
8
+ # == Mock Framework
9
+ #
10
+ # RSpec uses it's own mocking framework by default. If you prefer to
11
+ # use mocha, flexmock or RR, uncomment the appropriate line:
12
+ #
13
+ # config.mock_with :mocha
14
+ # config.mock_with :flexmock
15
+ # config.mock_with :rr
16
+ end
17
+
18
+ # EOF
File without changes
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: couch_docs
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Strom
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-09 00:00:00 -04:00
13
+ default_executable: couch-docs
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: json
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rest-client
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: rest-client
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 1.0.3
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: json
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 1.1.6
54
+ version:
55
+ - !ruby/object:Gem::Dependency
56
+ name: bones
57
+ type: :development
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 2.5.1
64
+ version:
65
+ description: Manage CouchDB views and documents.
66
+ email: chris@eeecooks.com
67
+ executables:
68
+ - couch-docs
69
+ extensions: []
70
+
71
+ extra_rdoc_files:
72
+ - History.txt
73
+ - README.rdoc
74
+ - bin/couch-docs
75
+ files:
76
+ - History.txt
77
+ - README.rdoc
78
+ - Rakefile
79
+ - bin/couch-docs
80
+ - couch_docs.gemspec
81
+ - fixtures/_design/a/b/c.js
82
+ - fixtures/_design/a/b/d.js
83
+ - fixtures/bar.json
84
+ - fixtures/foo.json
85
+ - lib/couch_docs.rb
86
+ - lib/couch_docs/command_line.rb
87
+ - lib/couch_docs/design_directory.rb
88
+ - lib/couch_docs/document_directory.rb
89
+ - lib/couch_docs/store.rb
90
+ - spec/couch_docs_spec.rb
91
+ - spec/spec_helper.rb
92
+ - test/test_couch_docs.rb
93
+ has_rdoc: true
94
+ homepage: http://github.com/eee-c/couch_docs
95
+ licenses: []
96
+
97
+ post_install_message:
98
+ rdoc_options:
99
+ - --main
100
+ - README.rdoc
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: "0"
108
+ version:
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: "0"
114
+ version:
115
+ requirements: []
116
+
117
+ rubyforge_project: couch_docs
118
+ rubygems_version: 1.3.5
119
+ signing_key:
120
+ specification_version: 2
121
+ summary: Manage CouchDB views and documents
122
+ test_files:
123
+ - test/test_couch_docs.rb