throne 0.0.1 → 0.0.2

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.
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)