throne 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -19,4 +19,7 @@ rdoc
19
19
  pkg
20
20
 
21
21
  ## PROJECT::SPECIFIC
22
- smeg2.gemspec
22
+
23
+ # Docs - these are generated
24
+ .yardoc
25
+ doc/*
@@ -1,10 +1,20 @@
1
- # ♚ Throne
1
+ = ♚ Throne
2
2
 
3
3
  The king is here - on his couch, covered in rubies.
4
4
 
5
5
  Simple library for working with CouchDB
6
6
 
7
- ## Basic Usage
7
+ == Caution!
8
+
9
+ This code is in _heavy_ development, in conjunction with a few development projects.
10
+ This means the API can and probably will change substantially over the next few
11
+ releases as we work out what fits best. You have been warned.
12
+
13
+ == API documentation
14
+
15
+ can be found at http://rdoc.info/projects/lstoll/throne
16
+
17
+ == Basic Usage
8
18
 
9
19
  Create a database object to work with. Will create the DB if it doesn't exist
10
20
 
@@ -33,21 +43,28 @@ Delete a document
33
43
 
34
44
  @db.delete(document_id_or_document_object)
35
45
 
36
- Run a design document
46
+ Run a design document/function
37
47
 
38
- @db.design('DesignDoc/_view/viewname')
48
+ res = @db.function('_design/DesignDoc/_view/viewname')
49
+ res
39
50
  => An array of documents
51
+ res.offset
52
+ => couchdb offset data
40
53
 
41
54
  # with parameters
42
- @db.design('DD/_list/listname/viewname', :key => ab..fg, :xyz => 7)
55
+ @db.function('_design/DD/_list/listname/viewname', :key => ab..fg, :xyz => 7)
43
56
  => An array of documents
44
57
 
45
58
  # Iterator Method
46
- @db.design('DesignDoc/_view/viewname') do |doc|
59
+ @db.function('_design/DesignDoc/_view/viewname') do |doc|
47
60
  #invoked for each document
48
61
  p doc
49
62
  end
50
63
 
64
+ # All documents
65
+ @db.function('_all_docs')
66
+ => All docs in the database
67
+
51
68
  Delete the database
52
69
 
53
70
  @db.delete_database
@@ -56,7 +73,7 @@ Create the database
56
73
 
57
74
  @db.create_database
58
75
 
59
- ## Note on Patches/Pull Requests
76
+ == Note on Patches/Pull Requests
60
77
 
61
78
  * Fork the project.
62
79
  * Make your feature addition or bug fix.
@@ -66,6 +83,6 @@ Create the database
66
83
  (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
67
84
  * Send me a pull request. Bonus points for topic branches.
68
85
 
69
- # Copyright
86
+ == Copyright
70
87
 
71
88
  Copyright (c) 2009 Lincoln Stoll, Ben Schwarz. See LICENSE for details.
data/TODO ADDED
@@ -0,0 +1,21 @@
1
+ * Update the get method to take an existing doc, and reload it. Use ETags
2
+ * At the top level retrieved, look for a RubyType field, and cast
3
+ * vague AR methods (Create, save, hooks)
4
+
5
+ * Model Thoughts:
6
+ * Everything extends Dash.
7
+ * When a new Document is instantiated, it's RubyClass property is set to its class
8
+ * It's converted to JSON just by calling to_json on the parent 'document' class
9
+ * When it's parsed in, the overridden/delegated get method checks the type of the
10
+ parent object, and in instantiates it, passing the JSON hash in as a constuctor.
11
+ * The super constructor then loops the sub items, instantiating when it see's a
12
+ RubyClass object, otherwise just setting the value.
13
+ * There should be a different parent class for Master documents and the sub items.
14
+ sub item's don't have save/get methods - only useful when loading from a view.
15
+
16
+ * Bulk fetch, and maybe bulk update.
17
+ * Bulk fetch, get method checks for array param. If it's an array, POST with keys
18
+ * bulk put, save method checks for array param, and bundles into docs array
19
+
20
+ * Attachments
21
+ * Just a put to url/attchment with data attached. Data in as a #read object?
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.2
@@ -33,9 +33,17 @@ class Throne::Database
33
33
  end
34
34
 
35
35
  # gets a document by it's ID
36
+ #
37
+ # @param [String] docid the ID of the document to retrieve
38
+ # @param [String] rev (optional) the revision of the document to retrieve
39
+ # @return [Hash, nil] the document mapped to a hash, or nil if not found.
36
40
  def get(docid, rev=nil)
37
- revurl = rev ? "?rev=#{rev}" : ""
38
- JSON.parse(c.get(@url + '/' + docid + revurl))
41
+ begin
42
+ revurl = rev ? "?rev=#{rev}" : ""
43
+ JSON.parse(c.get(@url + '/' + docid + revurl))
44
+ rescue RestClient::ResourceNotFound
45
+ nil
46
+ end
39
47
  end
40
48
 
41
49
  # creates/updates a document from a hash/array structure
@@ -62,10 +70,11 @@ class Throne::Database
62
70
  c.delete(@url + '/' + doc + '?rev=' + rev)
63
71
  end
64
72
 
65
- # runs a design by path, with optional params passed in
66
- def design(path, params = {}, &block)
67
- url = @url + '/_design/' + path
73
+ # runs a function by path, with optional params passed in
74
+ def function(path, params = {}, &block)
75
+ url = @url + '/' + path
68
76
  res = JSON.parse(c.get(paramify_url(url, params)))
77
+ res = Throne::ArrayWithFunctionMeta.new(res['rows'], res['offset'])
69
78
  if block_given?
70
79
  # TODO - stream properly
71
80
  res.each do |i|
@@ -77,8 +86,6 @@ class Throne::Database
77
86
  end
78
87
  end
79
88
 
80
-
81
-
82
89
  private
83
90
 
84
91
  def paramify_url url, params = {}
@@ -96,7 +103,9 @@ class Throne::Database
96
103
  def c; RestClient; end
97
104
  end
98
105
 
106
+ # Extended string, to store the couch revision
99
107
  class Throne::StringWithRevision < String
108
+ # Couch revision ID
100
109
  attr_reader :revision
101
110
 
102
111
  def initialize(id, rev)
@@ -104,3 +113,15 @@ class Throne::StringWithRevision < String
104
113
  super(id)
105
114
  end
106
115
  end
116
+
117
+ # Extended array, to store the couch extra data
118
+ class Throne::ArrayWithFunctionMeta < DelegateClass(Array)
119
+ # Offset field as returned by couch
120
+ attr_reader :offset
121
+
122
+ def initialize(array, offset)
123
+ @offset = offset
124
+ super(array)
125
+ end
126
+ end
127
+
data/lib/throne/tasks.rb CHANGED
@@ -1,52 +1,254 @@
1
1
  # Defines rake tasks for managing databases and views.
2
+ #
3
+ # Requires either a SERVER_URL or DB_URL defined as an environment variable.
4
+ # If SERVER_URL is set the database path is inferred from the directory the files
5
+ # are stored in, if DB_URL is set that database is explicitly used,
6
+ # overriding the name of the folder/task.
2
7
  class Throne::Tasks
3
- def self.inject_fixtures_tasks(base_path)
8
+ # Injects rake tasks for loading and dumping data from databases.
9
+ # @param [String] base_path the path where design docs are stored
10
+ def self.inject_data_tasks(base_path)
4
11
  namespace :throne do
5
- namespace :load_fixtures do
12
+ namespace :load_data do
6
13
  dirs_in_path(base_path).each do |dir, fulldir|
7
- desc "Loads fixtures for database #{dir}"
14
+ desc "Loads data for database #{dir}"
8
15
  task dir.to_sym do
9
- @db = Throne::Database.new(url(dir))
10
- items = []
11
- # grab and parse all YML files
12
- Dir.glob(fulldir + '/**/*.yml').each do |yml|
13
- items << YAML::load(File.open(yml))
14
- end
15
- # and json
16
- Dir.glob(fulldir + '/**/*.json').each do |json|
17
- items << JSON.parse(File.open(json).read)
18
- end
19
- # load em up
20
- items.each do |item|
21
- if item.kind_of? Array
22
- item.each do |doc|
23
- @db.save(doc)
24
- end
25
- elsif item.kind_of? Hash
26
- @db.save(item)
27
- else
28
- puts "There is something funky with the data for #{dir}"
29
- end
30
- end
31
-
32
- p items[0]
16
+ load_data_for_database(url(dir), fulldir)
33
17
  end
34
18
  end
35
19
 
36
20
  desc "load all dbs"
37
21
  task :all => dirs_in_path(base_path)
38
22
  end
23
+ namespace :dump_data do
24
+ desc "Dump data for a database into a YAML file"
25
+ task :yml, :db do |t, args|
26
+ dump_docs_from_db(args.db, base_path, :yml)
27
+ end
28
+
29
+ desc "Dump data for a database into a JSON file"
30
+ task :json, :db do |t, args|
31
+ dump_docs_from_db(args.db, base_path, :json)
32
+ end
33
+ end
39
34
  end
40
35
  end
41
36
 
37
+ # This will inject Rake tasks for loading design docs into the DB
38
+ # The docs should be layed out in the following format:
39
+ # base_path/
40
+ # |-- <db name>
41
+ # `-- <design doc name>
42
+ # |-- lists
43
+ # | `-- statuses
44
+ # | `-- list.js
45
+ # `-- views
46
+ # `-- statuses
47
+ # |-- map.js
48
+ # `-- reduce.js
49
+ #
50
+ # @param [String] base_path the path where design docs are stored
42
51
  def self.inject_design_doc_tasks(base_path)
43
52
  namespace :throne do
44
- desc "Pushes all design docs into the named db url"
45
- task :push_designs do
53
+ namespace :push_design do
54
+ dirs_in_path(base_path).each do |dir, fulldir|
55
+ desc "pushes designs for database #{dir}"
56
+ task dir.to_sym do
57
+ load_design_documents(url(dir), fulldir)
58
+ end
59
+ end
60
+
61
+ desc "push all designs"
62
+ task :all => dirs_in_path(base_path)
63
+ end
64
+ end
65
+ end
66
+
67
+ # Injects tasks to create and delete databases
68
+ def self.inject_database_tasks(base_path)
69
+ namespace :throne do
70
+ desc "Creates a database if it doesn't exist"
71
+ task :createdb, :db do |t, args|
72
+ get_db(args).create_database
73
+ puts "Database at #{url(args.db)}"
74
+ end
75
+
76
+ desc "Deletes a database"
77
+ task :deletedb, :db do |t, args|
78
+ get_db(args).delete_database
79
+ puts "Database at #{url(args.db)}"
80
+ end
81
+
82
+ desc "Deletes, creates and re-loads a database"
83
+ task :rebuilddb, :db do |t, args|
84
+ db = get_db(args)
85
+ raise "you must specify the database name (task[db])" unless args.db
86
+ db.delete_database
87
+ # re-getting the object will create the DB
88
+ db = get_db(args)
89
+ # load design docs
90
+ load_design_documents(db.url, File.join(base_path, 'design', args.db))
91
+ # load data
92
+ load_data_for_database(db.url, File.join(base_path, 'data', args.db))
93
+ puts "Done."
94
+ end
95
+
96
+
97
+ def self.get_db(args)
98
+ args ? db = args.db : db = nil
99
+ unless (ENV['SERVER_URL'] && args.db) || ENV['DB_URL']
100
+ raise "You must specify DB_URL or task[db_name] and SERVER_URL"
101
+ end
102
+ Throne::Database.new(url(db))
46
103
  end
47
104
  end
48
105
  end
49
106
 
107
+ # Injects all tasks. Related data files will be stores in base_path/data, and
108
+ # design docs in base_path/design
109
+ #
110
+ # @param [String] base_path path for db related files.
111
+ def self.inject_all_tasks(base_path)
112
+ inject_data_tasks(File.join(base_path, 'data'))
113
+ inject_design_doc_tasks(File.join(base_path, 'design'))
114
+ inject_database_tasks(base_path)
115
+ end
116
+
117
+ # Loads design documents into the database url, extracted from the source path
118
+ # The docs should be layed out in the following format:
119
+ # base_path/
120
+ # |-- <db name>
121
+ # `-- <design doc name>
122
+ # |-- lists
123
+ # | `-- statuses
124
+ # | `-- list.js
125
+ # `-- views
126
+ # `-- statuses
127
+ # |-- map.js
128
+ # `-- reduce.js
129
+ # @param [String] db_url the url of the database to load the data in to
130
+ # @param [String] source_path the path to search for .yml and .json files
131
+ def self.load_design_documents(db_url, source_path)
132
+ # for each folder in base path, create a new design doc key
133
+ # create a lists key
134
+ # for each path in lists, add a key with the folder name
135
+ # inside this, there is a key called list, with the contents of the list function
136
+ # views is the same, except with a map and reduce function
137
+ Dir.glob(File.join(source_path, '*')).each do |doc_path|
138
+ doc_name = File.basename(doc_path)
139
+ doc = {'lists' => {}, 'views' => {}}
140
+ Dir.glob(File.join(doc_path, 'lists', '*')) do |list_path|
141
+ list_name = File.basename(list_path)
142
+ doc['lists'][list_name] = {}
143
+ listfn = File.join(list_path, 'list.js')
144
+ doc['lists'][list_name] =
145
+ File.read(listfn) if File.exists?(listfn)
146
+ end
147
+ Dir.glob(File.join(doc_path, 'views', '*')) do |view_path|
148
+ view_name = File.basename(view_path)
149
+ doc['views'][view_name] = {}
150
+ mapfn = File.join(view_path, 'map.js')
151
+ reducefn = File.join(view_path, 'reduce.js')
152
+ doc['views'][view_name]['map'] =
153
+ File.read(mapfn) if File.exists?(mapfn)
154
+ doc['views'][view_name]['reduce'] =
155
+ File.read(reducefn) if File.exists?(reducefn)
156
+ end
157
+ # try to get the existing doc
158
+ doc_id = "_design/#{doc_name}"
159
+ db = Throne::Database.new(db_url)
160
+ if svr_doc = db.get(doc_id)
161
+ # merge
162
+ doc = svr_doc.merge(doc)
163
+ else
164
+ doc['_id'] = doc_id
165
+ end
166
+ doc['language'] = 'javascript'
167
+ db.save(doc)
168
+ puts "Design documents from #{source_path} loaded"
169
+ end
170
+
171
+ # try and get a document with the design name
172
+ # if it's there, replace the lists and views keys with above data
173
+ # otherwise, create a new document, set language to javascript
174
+ # put document.
175
+ # WIN
176
+ end
177
+
178
+ # Loads data into the database url from the source path. Picks up .yml and .json
179
+ # @param [String] db_url the url of the database to load the data in to
180
+ # @param [String] source_path the path to search for .yml and .json files
181
+ def self.load_data_for_database(db_url, source_path)
182
+ @db = Throne::Database.new(db_url)
183
+ items = []
184
+ doccount = 0
185
+ # grab and parse all YML files
186
+ Dir.glob(source_path + '/**/*.yml').each do |yml|
187
+ items << YAML::load(File.open(yml))
188
+ end
189
+ # and json
190
+ Dir.glob(source_path + '/**/*.json').each do |json|
191
+ items << JSON.parse(File.open(json).read)
192
+ end
193
+ # load em up
194
+ items.each do |item|
195
+ if item.kind_of? Array
196
+ item.each do |doc|
197
+ begin
198
+ @db.save(doc)
199
+ rescue RestClient::RequestFailed => e
200
+ if e.message =~ /409$/
201
+ puts "Duplicate document - this data has probaby already been loaded"
202
+ doccount -= 1
203
+ else
204
+ raise e
205
+ end
206
+ end
207
+ doccount += 1
208
+ end
209
+ elsif item.kind_of? Hash
210
+ begin
211
+ @db.save(item)
212
+ rescue RestClient::RequestFailed => e
213
+ if e.message =~ /409$/
214
+ puts "Duplicate document - this data has probaby already been loaded"
215
+ doccount -= 1
216
+ else
217
+ raise e
218
+ end
219
+ end
220
+ doccount += 1
221
+ else
222
+ puts "There is something funky with the data for #{source_path}"
223
+ end
224
+ puts "#{doccount} document(s) loaded into database at #{@db.url}"
225
+ end
226
+ end
227
+
228
+ def self.dump_docs_from_db(db, base_path, format)
229
+ raise "You must specify a DB name to dump task[dbname]" unless db
230
+ outdir = File.join(base_path, db)
231
+ Dir.mkdir(outdir) unless File.exists?(outdir)
232
+ @db = Throne::Database.new(url(db))
233
+ docs = []
234
+
235
+ @db.function('_all_docs') do |res|
236
+ docs << @db.get(res['key']) unless res['key'].match(/^_/)
237
+ end
238
+
239
+ outfn = format == :json ? 'dump.json' : 'dump.yml'
240
+ File.open(File.join(outdir, outfn), 'w') do |f|
241
+ if format == :json
242
+ f.puts docs.to_json
243
+ elsif format == :yml
244
+ f.puts docs.to_yaml
245
+ else
246
+ raise("Internal Error - invalid dump format specified")
247
+ end
248
+ end
249
+ puts "Data dumped to #{base_path + '/' + db + '/' + outfn}"
250
+ end
251
+
50
252
  private
51
253
 
52
254
  def self.dirs_in_path(path)
@@ -63,7 +265,8 @@ class Throne::Tasks
63
265
  elsif svrurl = ENV['SERVER_URL']
64
266
  return svrurl + '/' + dbname
65
267
  else
66
- raise("You must provide a SERVER_URL or DATABASE_URL")
268
+ raise("You must provide a SERVER_URL or DB_URL")
67
269
  end
68
270
  end
271
+
69
272
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: throne
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lincoln Stoll
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-12-13 00:00:00 +11:00
12
+ date: 2009-12-14 00:00:00 +11:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -60,14 +60,15 @@ extensions: []
60
60
 
61
61
  extra_rdoc_files:
62
62
  - LICENSE
63
- - README.md
63
+ - README.rdoc
64
+ - TODO
64
65
  files:
65
66
  - .document
66
67
  - .gitignore
67
- - FEATURES
68
68
  - LICENSE
69
- - README.md
69
+ - README.rdoc
70
70
  - Rakefile
71
+ - TODO
71
72
  - VERSION
72
73
  - lib/throne.rb
73
74
  - lib/throne/database.rb
data/FEATURES DELETED
@@ -1,5 +0,0 @@
1
- * Ability to load views from disk
2
- * Query these views from an object
3
- * Map the views to a hash structure, or just an array
4
- * At the top level retrieved, look for a RubyType field, and cast
5
- * vague AR methods (Create, save, hooks)