couch_crumbs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ === 0.0.1 2009-08-04
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/Manifest.txt ADDED
@@ -0,0 +1,29 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.rdoc
4
+ Rakefile
5
+ lib/couch_crumbs.rb
6
+ lib/core_ext/array.rb
7
+ lib/couch_crumbs/server.rb
8
+ lib/couch_crumbs/database.rb
9
+ lib/couch_crumbs/query.rb
10
+ lib/couch_crumbs/design.rb
11
+ lib/couch_crumbs/document.rb
12
+ lib/couch_crumbs/view.rb
13
+ lib/couch_crumbs/json/all.json
14
+ lib/couch_crumbs/json/children.json
15
+ lib/couch_crumbs/json/design.json
16
+ lib/couch_crumbs/json/simple.json
17
+ script/console
18
+ script/destroy
19
+ script/generate
20
+ spec/couch_crumbs_spec.rb
21
+ spec/core_ext/array_spec.rb
22
+ spec/couch_crumbs/database_spec.rb
23
+ spec/couch_crumbs/design_spec.rb
24
+ spec/couch_crumbs/document_spec.rb
25
+ spec/couch_crumbs/server_spec.rb
26
+ spec/couch_crumbs/view_spec.rb
27
+ spec/spec.opts
28
+ spec/spec_helper.rb
29
+ tasks/rspec.rake
data/README.rdoc ADDED
@@ -0,0 +1,131 @@
1
+ = couch_crumbs
2
+
3
+ * http://github.com/lmlo/couch_crumbs
4
+
5
+ == DESCRIPTION:
6
+
7
+ A small library for basic CouchDB document-based apps.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ - objects are JSON derived hashes
12
+ (meaning properties support arrays, other hashes, dates, etc.)
13
+ - named properties
14
+ - timestamps (created_at/updated_at)
15
+ - life-cycle callbacks
16
+ - per-class database settings
17
+ - simple "finder" views
18
+ - advanced JavaScript views (in .json files)
19
+ - document relationships
20
+
21
+ == SYNOPSIS:
22
+
23
+ # Connect to a CouchDB server and set the default database
24
+ # :default_server is optional and defaults to http://couchdb.local/
25
+ # :default_database is also optional, and is used for classes
26
+ # that don't otherwise specify a database
27
+ CouchCrumbs::connect(:default_database => "example_database")
28
+
29
+ @server = CouchCrumbs.default_server
30
+
31
+ # Make sure we're running
32
+ @server.status
33
+
34
+ # Create or retrieve a database
35
+ @database = Database.new(:name => "example_database")
36
+
37
+
38
+ # Create a class
39
+ class Person
40
+
41
+ # Mixin document goodness
42
+ include CouchCrumbs::Document
43
+
44
+ # Declare some properties
45
+ property :name
46
+ property :title
47
+ property :status
48
+
49
+ # Add :created_at and :updated_at properties
50
+ timestamps!
51
+
52
+ child_document :address
53
+
54
+ end
55
+
56
+ class Address
57
+
58
+ include CouchCrumbs::Document
59
+
60
+ property :location
61
+
62
+ timestamps!
63
+
64
+ parent_document :person
65
+
66
+ end
67
+
68
+ # Instantiate a new Person document with properties
69
+ @person = Person.new(:name => "Steve")
70
+ # Manually set a property
71
+ @person.title = "CEO"
72
+ # Save the document (with #before_save and #after_save callbacks)
73
+ @person.save!
74
+
75
+
76
+ # Get a single document from the database
77
+ @steve = Person.get!(@person.id)
78
+
79
+
80
+ # Instantiate and save a new document in a single call (w/ callbacks)
81
+ @bill = Person.create!(:name => "Bill", :status => "Retired")
82
+
83
+ # Destroy a document
84
+ @bill.destroy!
85
+
86
+
87
+ # Add an address to a person
88
+ @address = Address.create!(:location => "123 Main, Anytown USA")
89
+
90
+ @steve.address = @address
91
+
92
+
93
+ == LIMITATIONS:
94
+
95
+ - lacks support for attachments
96
+ - lacks support for show functions
97
+ - lacks support for many other aspects of CouchDB
98
+ - both sides of an association must be saved before being set
99
+
100
+ == REQUIREMENTS:
101
+
102
+ sudo gem install rest_client, json, english
103
+
104
+ == INSTALL:
105
+
106
+ sudo gem install couch_crumbs
107
+
108
+ == LICENSE:
109
+
110
+ (The MIT License)
111
+
112
+ Copyright (c) 2009 CouchCrumbs project.
113
+
114
+ Permission is hereby granted, free of charge, to any person obtaining
115
+ a copy of this software and associated documentation files (the
116
+ 'Software'), to deal in the Software without restriction, including
117
+ without limitation the rights to use, copy, modify, merge, publish,
118
+ distribute, sublicense, and/or sell copies of the Software, and to
119
+ permit persons to whom the Software is furnished to do so, subject to
120
+ the following conditions:
121
+
122
+ The above copyright notice and this permission notice shall be
123
+ included in all copies or substantial portions of the Software.
124
+
125
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
126
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
127
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
128
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
129
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
130
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
131
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ require 'rubygems'
2
+ gem 'hoe', '>= 2.1.0'
3
+ require 'hoe'
4
+ require 'fileutils'
5
+
6
+ Hoe.plugin :newgem
7
+ # Hoe.plugin :website
8
+ # Hoe.plugin :cucumberfeatures
9
+
10
+ # Generate all the Rake tasks
11
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
12
+ $hoe = Hoe.spec 'couch_crumbs' do
13
+ self.developer 'Often Void, Inc.', 'admin@oftenvoid.com'
14
+ self.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
15
+ self.rubyforge_name = self.name # TODO this is default value
16
+ self.extra_deps = [['english']]
17
+ self.version = "0.0.1"
18
+ end
19
+
20
+ #require 'newgem/tasks'
21
+ Dir['tasks/**/*.rake'].each { |t| load t }
22
+
23
+ # TODO - want other tests/tasks run by default? Add them to the list
24
+ # remove_task :default
25
+ # task :default => [:spec, :features]
26
+
27
+ # Re-add the standard install task
28
+ task :install => [:install_gem]
29
+
30
+ # See: http://rspec.info/documentation/tools/rcov.html
31
+ desc "Run all examples with RCov"
32
+ Spec::Rake::SpecTask.new('coverage') do |t|
33
+ t.spec_opts = ['--format', 'specdoc', '-c']
34
+ t.spec_files = FileList['spec/**/*_spec.rb']
35
+ t.rcov = true
36
+ t.rcov_opts = ['--exclude', '/gems/,spec/']
37
+ end
@@ -0,0 +1,9 @@
1
+ class Array
2
+
3
+ # Add a destroy all method to arrays
4
+ #
5
+ def destroy!
6
+ each { |o| o.destroy! }
7
+ end
8
+
9
+ end
@@ -0,0 +1,68 @@
1
+ module CouchCrumbs
2
+
3
+ # Direct representation of a CouchDB database (contains documents).
4
+ #
5
+ class Database
6
+
7
+ include CouchCrumbs::Query
8
+
9
+ DEFAULT_NAME = :couch_crumbs_database.freeze
10
+
11
+ attr_accessor :server, :uri, :name, :status
12
+
13
+ # Get or create a database
14
+ #
15
+ def initialize(opts = {})
16
+ self.server = opts[:server] || CouchCrumbs::default_server
17
+ self.name = (opts[:name] || DEFAULT_NAME).to_s
18
+ self.uri = File.join(server.uri, self.name)
19
+
20
+ begin
21
+ self.status = RestClient.get(uri)
22
+ rescue RestClient::ResourceNotFound
23
+ RestClient.put(uri, "{}")
24
+ retry
25
+ end
26
+ end
27
+
28
+ # Return an array of all documents
29
+ #
30
+ def documents(opts = {})
31
+ # Query the special built-in _all_docs view
32
+ query_docs(File.join(uri, "_all_docs"), opts).collect do |doc|
33
+ # Regular documents
34
+ if doc["crumb_type"]
35
+ # Eval the class (with basic filtering, i.e. trusting your database)
36
+ eval(doc["crumb_type"].gsub(/\W/i, '').capitalize!).get!(doc["_id"])
37
+
38
+ elsif doc["_id"] =~ /^\_design\//
39
+ # Design docs
40
+ Design.get!(self, :id => doc["_id"])
41
+ else
42
+ # Ignore any other docs
43
+ warn "skipping unknown document: #{ doc }"
44
+
45
+ nil
46
+ end
47
+ end
48
+ end
49
+
50
+ # Return an array of only design documents
51
+ #
52
+ def design_documents
53
+ documents(:startkey => "_design/")
54
+ end
55
+
56
+ # Delete database from the server
57
+ #
58
+ def destroy!
59
+ freeze
60
+
61
+ result = JSON.parse(RestClient.delete(uri))
62
+
63
+ result["ok"]
64
+ end
65
+
66
+ end
67
+
68
+ end
@@ -0,0 +1,129 @@
1
+ module CouchCrumbs
2
+
3
+ # Represents "special" design documents
4
+ #
5
+ class Design
6
+
7
+ attr_accessor :raw, :uri
8
+
9
+ # Return a single design doc (from a specific database)
10
+ #
11
+ # ==== Parameters
12
+ # database<String>:: database instance
13
+ # opts => name<String>:: design doc name (i.e. "person")
14
+ # opts => id<String:: id of an existing design doc (i.e. "_design/person")
15
+ #
16
+ def self.get!(database, opts = {})
17
+ raise "opts must contain an :id or :name" unless (opts.has_key?(:id) || opts.has_key?(:name))
18
+
19
+ # Munge the URI from an :id or :name
20
+ uri = File.join(database.uri, (opts[:id] || "_design/#{ opts[:name] }"))
21
+
22
+ begin
23
+ # Try for an existing doc
24
+ result = RestClient.get(uri)
25
+
26
+ design = Design.new(database, :json => result)
27
+ rescue RestClient::ResourceNotFound
28
+ # Or create a new one
29
+ design = Design.new(database, :name => File.basename(uri))
30
+
31
+ design.save!
32
+ end
33
+
34
+ design
35
+ end
36
+
37
+ # Instantiate a new design document
38
+ #
39
+ # ==== Parameters
40
+ # database<String>:: database instance
41
+ # opts => json<String>:: raw json to init from
42
+ # opts => name<String>:: design doc name (i.e. "person")
43
+ #
44
+ def initialize(database, opts = {})
45
+ if opts.has_key?(:json)
46
+ self.raw = JSON.parse(opts[:json])
47
+ elsif opts.has_key?(:name)
48
+ # Read the design doc template
49
+ template = File.read(File.join(File.dirname(__FILE__), "json", "design.json"))
50
+
51
+ # Make our substitutions
52
+ template.gsub!(/\#design_id/, "_design/#{ opts[:name] }")
53
+
54
+ # Init the raw hash
55
+ self.raw = JSON.parse(template)
56
+ else
57
+ raise "#new must have :json or a :name supplied"
58
+ end
59
+
60
+ # Set out unique URI
61
+ self.uri = File.join(database.uri, id)
62
+ end
63
+
64
+ # Return design doc id (typically "_design/resource")
65
+ #
66
+ def id
67
+ raw["_id"]
68
+ end
69
+
70
+ # Return the revision
71
+ #
72
+ def rev
73
+ raw["_rev"]
74
+ end
75
+
76
+ # Return the design name (id - prefix, i.e. "resource")
77
+ #
78
+ def name
79
+ raw["_id"].split("/").last
80
+ end
81
+
82
+ # Save the design to the database
83
+ #
84
+ def save!
85
+ result = JSON.parse(RestClient.put(uri, raw.to_json))
86
+
87
+ # update our stats
88
+ raw["_id"] = result["id"]
89
+ raw["_rev"] = result["rev"]
90
+
91
+ result["ok"]
92
+ end
93
+
94
+ # Remove a design from the database
95
+ #
96
+ def destroy!
97
+ freeze
98
+
99
+ result = JSON.parse(RestClient.delete(File.join(uri, "?rev=#{ rev }")))
100
+
101
+ result["ok"]
102
+ end
103
+
104
+ # Return all views of this design doc
105
+ # opts => supply a name to select a specific view
106
+ #
107
+ def views(opts = {})
108
+ if opts.has_key?(:name)
109
+ View.new(self, opts[:name].to_s, {:name => raw["views"][opts[:name].to_s]}.to_json)
110
+ else
111
+ raw["views"].collect do |key, value|
112
+ View.new(self, key.to_s, {key => value}.to_json)
113
+ end
114
+ end
115
+ end
116
+
117
+ # Append a view to the view list
118
+ #
119
+ def add_view(view)
120
+ raise ArgumentError.new("view must be of kind View: #{ view }") unless view.kind_of?(View)
121
+
122
+ raw["views"].merge!(view.raw)
123
+
124
+ save!
125
+ end
126
+
127
+ end
128
+
129
+ end