throne 0.0.2 → 0.0.3
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/Rakefile +1 -1
- data/TODO +25 -0
- data/VERSION +1 -1
- data/lib/throne.rb +1 -1
- data/lib/throne/database.rb +4 -3
- data/lib/throne/tasks.rb +178 -119
- data/spec/database_spec.rb +2 -4
- metadata +3 -3
data/Rakefile
CHANGED
@@ -13,7 +13,7 @@ begin
|
|
13
13
|
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
14
|
gem.add_dependency "rest-client", ">= 0"
|
15
15
|
gem.add_dependency "hashie", ">= 0"
|
16
|
-
gem.add_dependency "
|
16
|
+
gem.add_dependency "yajl-ruby", ">= 0"
|
17
17
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
18
18
|
end
|
19
19
|
Jeweler::GemcutterTasks.new
|
data/TODO
CHANGED
@@ -19,3 +19,28 @@
|
|
19
19
|
|
20
20
|
* Attachments
|
21
21
|
* Just a put to url/attchment with data attached. Data in as a #read object?
|
22
|
+
|
23
|
+
* Design Documents
|
24
|
+
* Would be great to have a way to include functions into designs. Either via Couch, or embed
|
25
|
+
|
26
|
+
* Niceities
|
27
|
+
* pretty format the JSON dump?
|
28
|
+
|
29
|
+
* Model stuff 2
|
30
|
+
* By default, return a Mash. Good enough for most cases
|
31
|
+
* Have a mixin, that can be called inside something that subclasses Dash.
|
32
|
+
* when the method is called to set the database on the Dash, look for a design
|
33
|
+
document with the same name
|
34
|
+
* If it exists, map methods on the _class_ for .viewname that invoke the view,
|
35
|
+
and .listname.viewname that invoke the view from the list. these take a
|
36
|
+
params hash, and pass these in as URL params.
|
37
|
+
* when these view methods are called, iterate the result tree, and if they have a
|
38
|
+
couchdoc class and that class exists, instantiate it with the contents.
|
39
|
+
* when calling the get method on the class, do the object matching as well
|
40
|
+
* the tree setting for sub items is up to the object to do in its' constructor
|
41
|
+
* provide a convenience 'cast' method to help with this?
|
42
|
+
|
43
|
+
* The get method should return an array by default, but if a :limit=1 param set,
|
44
|
+
return a single object.
|
45
|
+
|
46
|
+
* Check that the mash constructor uses the passed in object, rather than copying.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.3
|
data/lib/throne.rb
CHANGED
data/lib/throne/database.rb
CHANGED
@@ -40,7 +40,7 @@ class Throne::Database
|
|
40
40
|
def get(docid, rev=nil)
|
41
41
|
begin
|
42
42
|
revurl = rev ? "?rev=#{rev}" : ""
|
43
|
-
JSON.parse(c.get(@url + '/' + docid + revurl))
|
43
|
+
Hashie::Mash.new(JSON.parse(c.get(@url + '/' + docid + revurl)))
|
44
44
|
rescue RestClient::ResourceNotFound
|
45
45
|
nil
|
46
46
|
end
|
@@ -82,7 +82,7 @@ class Throne::Database
|
|
82
82
|
end
|
83
83
|
nil
|
84
84
|
else
|
85
|
-
res
|
85
|
+
Hashie::Mash.new(res)
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
@@ -91,7 +91,8 @@ class Throne::Database
|
|
91
91
|
def paramify_url url, params = {}
|
92
92
|
if params && !params.empty?
|
93
93
|
query = params.collect do |k,v|
|
94
|
-
v = v.to_json if %w{key startkey endkey}.include?(k.to_s)
|
94
|
+
v = v.to_json if %w{key startkey endkey}.include?(k.to_s) &&
|
95
|
+
(v.kind_of?(Array) || v.kind_of?(Hash))
|
95
96
|
"#{k}=#{CGI.escape(v.to_s)}"
|
96
97
|
end.join("&")
|
97
98
|
url = "#{url}?#{query}"
|
data/lib/throne/tasks.rb
CHANGED
@@ -1,96 +1,125 @@
|
|
1
|
-
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
# Defines tasks for managing databases and views.
|
4
|
+
#
|
5
|
+
# To reference a file in lib/ from your design doc, use a commend like this
|
6
|
+
# // !code library.js
|
7
|
+
#
|
8
|
+
# This will include the contents at this locate.
|
9
|
+
#
|
10
|
+
# These tasks require an environment variable of SERVER_URL to be set.
|
11
|
+
#
|
12
|
+
# The documents should be stored using the following structure - all items in the
|
13
|
+
# root of base path will be included in all databases, items with a database will be
|
14
|
+
# included into that database only.
|
15
|
+
#
|
16
|
+
# base_path/
|
17
|
+
# |-- lib
|
18
|
+
# | `-- library.js
|
19
|
+
# |-- data
|
20
|
+
# | `-- .json and .yml folders with seed data
|
21
|
+
# |-- design
|
22
|
+
# | `-- <design doc name>
|
23
|
+
# | |-- lists
|
24
|
+
# | | `-- listname.js
|
25
|
+
# | |-- shows
|
26
|
+
# | | `-- showname.js
|
27
|
+
# | |-- validate_doc_update.sh
|
28
|
+
# | `-- views
|
29
|
+
# | `-- statuses
|
30
|
+
# | |-- map.js
|
31
|
+
# | `-- reduce.js
|
32
|
+
# |-- <db name>
|
33
|
+
# |-- lib
|
34
|
+
# | `-- library.js
|
35
|
+
# |-- data
|
36
|
+
# | `-- .json and .yml folders with seed data
|
37
|
+
# `-- design
|
38
|
+
# `-- <design doc name>
|
39
|
+
# |-- lists
|
40
|
+
# | `-- listname.js
|
41
|
+
# |-- shows
|
42
|
+
# | `-- showname.js
|
43
|
+
# |-- validate_doc_update.sh
|
44
|
+
# `-- views
|
45
|
+
# `-- statuses
|
46
|
+
# |-- map.js
|
47
|
+
# `-- reduce.js
|
2
48
|
#
|
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.
|
7
49
|
class Throne::Tasks
|
8
|
-
#
|
50
|
+
# This will inject Rake tasks for managing the database data. The base path should
|
51
|
+
# be where all the items are stored.
|
52
|
+
#
|
9
53
|
# @param [String] base_path the path where design docs are stored
|
10
|
-
def self.
|
54
|
+
def self.inject_tasks(base_path)
|
11
55
|
namespace :throne do
|
12
|
-
namespace :
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
56
|
+
namespace :documents do
|
57
|
+
namespace :load do
|
58
|
+
databases_in_path(base_path).each do |dbname, fulldir|
|
59
|
+
desc "Loads data for database #{dbname}, optionally into db"
|
60
|
+
task dbname.to_sym, :db do |t, args|
|
61
|
+
load_data_for_database(url(dbname, args), base_path, dbname)
|
62
|
+
end
|
17
63
|
end
|
18
|
-
end
|
19
64
|
|
20
|
-
|
21
|
-
|
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)
|
65
|
+
desc "load all dbs"
|
66
|
+
task :all => databases_in_path(base_path)
|
27
67
|
end
|
68
|
+
namespace :dump do
|
69
|
+
desc "Dump data for a database into a YAML file"
|
70
|
+
task :yml, :db do |t, args|
|
71
|
+
dump_docs_from_db(args.db, base_path, :yml)
|
72
|
+
end
|
28
73
|
|
29
|
-
|
30
|
-
|
31
|
-
|
74
|
+
desc "Dump data for a database into a JSON file"
|
75
|
+
task :json, :db do |t, args|
|
76
|
+
dump_docs_from_db(args.db, base_path, :json)
|
77
|
+
end
|
32
78
|
end
|
33
79
|
end
|
34
|
-
end
|
35
|
-
end
|
36
80
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
# `-- views
|
46
|
-
# `-- statuses
|
47
|
-
# |-- map.js
|
48
|
-
# `-- reduce.js
|
49
|
-
#
|
50
|
-
# @param [String] base_path the path where design docs are stored
|
51
|
-
def self.inject_design_doc_tasks(base_path)
|
52
|
-
namespace :throne 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)
|
81
|
+
#design tasks
|
82
|
+
namespace :design do
|
83
|
+
namespace :push do
|
84
|
+
databases_in_path(base_path).each do |dbname, fulldir|
|
85
|
+
desc "pushes designs for database #{dbname}, optionally into db"
|
86
|
+
task dbname.to_sym, :db do |t, args|
|
87
|
+
load_design_documents(url(dbname, args), base_path, dbname)
|
88
|
+
end
|
58
89
|
end
|
59
|
-
end
|
60
90
|
|
61
|
-
|
62
|
-
|
91
|
+
desc "push all designs"
|
92
|
+
task :all => databases_in_path(base_path)
|
93
|
+
end
|
63
94
|
end
|
64
|
-
end
|
65
|
-
end
|
66
95
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
96
|
+
# Db Tasks
|
97
|
+
namespace :database do
|
98
|
+
desc "Creates a database if it doesn't exist"
|
99
|
+
task :createdb, :db do |t, args|
|
100
|
+
get_db(args).create_database
|
101
|
+
puts "Database at #{url(args.db)}"
|
102
|
+
end
|
75
103
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
104
|
+
desc "Deletes a database"
|
105
|
+
task :deletedb, :db do |t, args|
|
106
|
+
get_db(args).delete_database
|
107
|
+
puts "Database at #{url(args.db)}"
|
108
|
+
end
|
81
109
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
110
|
+
desc "Deletes, creates and re-loads a database"
|
111
|
+
task :rebuilddb, :db do |t, args|
|
112
|
+
db = get_db(args)
|
113
|
+
raise "you must specify the database name (task[db])" unless args.db
|
114
|
+
db.delete_database
|
115
|
+
# re-getting the object will create the DB
|
116
|
+
db = get_db(args)
|
117
|
+
# load design docs
|
118
|
+
load_design_documents(db.url, File.join(base_path, 'design', args.db))
|
119
|
+
# load data
|
120
|
+
load_data_for_database(db.url, File.join(base_path, 'data', args.db))
|
121
|
+
puts "Done."
|
122
|
+
end
|
94
123
|
end
|
95
124
|
|
96
125
|
|
@@ -104,55 +133,57 @@ class Throne::Tasks
|
|
104
133
|
end
|
105
134
|
end
|
106
135
|
|
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
136
|
# Loads design documents into the database url, extracted from the source path
|
118
137
|
# The docs should be layed out in the following format:
|
119
|
-
#
|
120
|
-
#
|
121
|
-
# `--
|
122
|
-
#
|
123
|
-
#
|
124
|
-
#
|
125
|
-
#
|
126
|
-
# `--
|
127
|
-
# |-- map.js
|
128
|
-
# `-- reduce.js
|
138
|
+
# `-- <design doc name>
|
139
|
+
# |-- lists
|
140
|
+
# | `-- statuses
|
141
|
+
# | `-- list.js
|
142
|
+
# `-- views
|
143
|
+
# `-- statuses
|
144
|
+
# |-- map.js
|
145
|
+
# `-- reduce.js
|
129
146
|
# @param [String] db_url the url of the database to load the data in to
|
130
147
|
# @param [String] source_path the path to search for .yml and .json files
|
131
|
-
def self.load_design_documents(db_url,
|
148
|
+
def self.load_design_documents(db_url, base_path, database)
|
132
149
|
# for each folder in base path, create a new design doc key
|
133
150
|
# create a lists key
|
134
151
|
# for each path in lists, add a key with the folder name
|
135
152
|
# inside this, there is a key called list, with the contents of the list function
|
136
153
|
# views is the same, except with a map and reduce function
|
137
|
-
|
154
|
+
paths_for_item(base_path, database, 'design/*').each do |doc_path|
|
138
155
|
doc_name = File.basename(doc_path)
|
139
|
-
doc = {'lists' => {}, 'views' => {}}
|
140
|
-
|
141
|
-
|
156
|
+
doc = {'lists' => {}, 'views' => {}, 'shows' => {}}
|
157
|
+
|
158
|
+
Dir.glob(File.join(doc_path, 'lists', '*.js')) do |list_path|
|
159
|
+
list_name = File.basename(list_path).split('.').first
|
142
160
|
doc['lists'][list_name] = {}
|
143
|
-
listfn = File.join(list_path, 'list.js')
|
144
161
|
doc['lists'][list_name] =
|
145
|
-
|
162
|
+
inject_code_includes(base_path, database, list_path)
|
163
|
+
end
|
164
|
+
|
165
|
+
Dir.glob(File.join(doc_path, 'shows', '*.js')) do |show_path|
|
166
|
+
show_name = File.basename(show_path).split('.').first
|
167
|
+
doc['shows'][show_name] = {}
|
168
|
+
doc['shows'][show_name] =
|
169
|
+
inject_code_includes(base_path, database, show_path)
|
170
|
+
end
|
171
|
+
|
172
|
+
if File.exists?(vfn = File.join(doc_path, 'validate_doc_update.js'))
|
173
|
+
doc['validate_doc_update'] =
|
174
|
+
inject_code_includes(base_path, database, vfn)
|
146
175
|
end
|
176
|
+
|
147
177
|
Dir.glob(File.join(doc_path, 'views', '*')) do |view_path|
|
148
178
|
view_name = File.basename(view_path)
|
149
179
|
doc['views'][view_name] = {}
|
150
180
|
mapfn = File.join(view_path, 'map.js')
|
151
181
|
reducefn = File.join(view_path, 'reduce.js')
|
152
182
|
doc['views'][view_name]['map'] =
|
153
|
-
|
183
|
+
inject_code_includes(base_path, database, mapfn) if File.exists?(mapfn)
|
154
184
|
doc['views'][view_name]['reduce'] =
|
155
|
-
|
185
|
+
inject_code_includes(base_path, database, reducefn) if
|
186
|
+
File.exists?(reducefn)
|
156
187
|
end
|
157
188
|
# try to get the existing doc
|
158
189
|
doc_id = "_design/#{doc_name}"
|
@@ -165,7 +196,7 @@ class Throne::Tasks
|
|
165
196
|
end
|
166
197
|
doc['language'] = 'javascript'
|
167
198
|
db.save(doc)
|
168
|
-
puts "Design documents from #{
|
199
|
+
puts "Design documents from #{doc_path} loaded"
|
169
200
|
end
|
170
201
|
|
171
202
|
# try and get a document with the design name
|
@@ -178,16 +209,16 @@ class Throne::Tasks
|
|
178
209
|
# Loads data into the database url from the source path. Picks up .yml and .json
|
179
210
|
# @param [String] db_url the url of the database to load the data in to
|
180
211
|
# @param [String] source_path the path to search for .yml and .json files
|
181
|
-
def self.load_data_for_database(db_url,
|
212
|
+
def self.load_data_for_database(db_url, base_path, database)
|
182
213
|
@db = Throne::Database.new(db_url)
|
183
214
|
items = []
|
184
215
|
doccount = 0
|
185
216
|
# grab and parse all YML files
|
186
|
-
|
217
|
+
paths_for_item(base_path, database, 'data/**/*.yml').each do |yml|
|
187
218
|
items << YAML::load(File.open(yml))
|
188
219
|
end
|
189
220
|
# and json
|
190
|
-
|
221
|
+
paths_for_item(base_path, database, 'data/**/*.json').each do |json|
|
191
222
|
items << JSON.parse(File.open(json).read)
|
192
223
|
end
|
193
224
|
# load em up
|
@@ -221,14 +252,14 @@ class Throne::Tasks
|
|
221
252
|
else
|
222
253
|
puts "There is something funky with the data for #{source_path}"
|
223
254
|
end
|
224
|
-
puts "#{doccount} document(s) loaded into database at #{@db.url}"
|
225
255
|
end
|
256
|
+
puts "#{doccount} document(s) loaded into database at #{@db.url}"
|
226
257
|
end
|
227
258
|
|
228
259
|
def self.dump_docs_from_db(db, base_path, format)
|
229
260
|
raise "You must specify a DB name to dump task[dbname]" unless db
|
230
|
-
outdir = File.join(base_path, db)
|
231
|
-
|
261
|
+
outdir = File.join(base_path, db, "data")
|
262
|
+
FileUtils.mkdir_p(outdir) unless File.exists?(outdir)
|
232
263
|
@db = Throne::Database.new(url(db))
|
233
264
|
docs = []
|
234
265
|
|
@@ -239,7 +270,7 @@ class Throne::Tasks
|
|
239
270
|
outfn = format == :json ? 'dump.json' : 'dump.yml'
|
240
271
|
File.open(File.join(outdir, outfn), 'w') do |f|
|
241
272
|
if format == :json
|
242
|
-
f.puts docs
|
273
|
+
f.puts JSON.pretty_generate(docs)
|
243
274
|
elsif format == :yml
|
244
275
|
f.puts docs.to_yaml
|
245
276
|
else
|
@@ -251,22 +282,50 @@ class Throne::Tasks
|
|
251
282
|
|
252
283
|
private
|
253
284
|
|
254
|
-
def self.
|
285
|
+
def self.inject_code_includes(base_path, database, file)
|
286
|
+
res = ''
|
287
|
+
File.open(file).each do |line|
|
288
|
+
if line =~ /(\/\/|#)\ ?!code (.*)/
|
289
|
+
if File.exists?(inc = File.join(base_path, database, 'lib', $2))
|
290
|
+
res += File.read(inc)
|
291
|
+
elsif File.exists?(inc = File.join(base_path, 'lib', $2))
|
292
|
+
res += File.read(inc)
|
293
|
+
else
|
294
|
+
raise "Include file #{$2} does not exist in lib/ or #{database}/lib"
|
295
|
+
end
|
296
|
+
else
|
297
|
+
res += line
|
298
|
+
end
|
299
|
+
end
|
300
|
+
res
|
301
|
+
end
|
302
|
+
|
303
|
+
# For the gives path, returns a list of database names and their full path.
|
304
|
+
# exludes lib and our other dirs.
|
305
|
+
def self.databases_in_path(path)
|
255
306
|
res = {}
|
256
307
|
Dir.glob(path + '/*').each do |fn|
|
308
|
+
next if %w(lib data design).any? {|i| File.basename(fn) == i}
|
257
309
|
res[File.basename(fn)] = fn if File.directory?(fn)
|
258
310
|
end
|
259
311
|
res
|
260
312
|
end
|
261
313
|
|
262
|
-
def self.url(dbname)
|
263
|
-
if
|
264
|
-
return
|
265
|
-
elsif svrurl = ENV['SERVER_URL']
|
314
|
+
def self.url(dbname, args=nil)
|
315
|
+
if svrurl = ENV['SERVER_URL']
|
316
|
+
return svrurl + '/' + args.db if args && args.db
|
266
317
|
return svrurl + '/' + dbname
|
267
318
|
else
|
268
|
-
raise("You must provide a SERVER_URL
|
319
|
+
raise("You must provide a SERVER_URL")
|
269
320
|
end
|
270
321
|
end
|
271
322
|
|
323
|
+
# Given the base dir, the DB name and the item path we are after, get a list
|
324
|
+
# of items. This includes global and DB spec.
|
325
|
+
# e.g for item - data , designs, lib.
|
326
|
+
def self.paths_for_item(base, database, item_glob)
|
327
|
+
items = Dir.glob(File.join(base, item_glob))
|
328
|
+
items.concat(Dir.glob(File.join(base, database, item_glob)))
|
329
|
+
end
|
330
|
+
|
272
331
|
end
|
data/spec/database_spec.rb
CHANGED
@@ -83,15 +83,13 @@ describe Throne::Database do
|
|
83
83
|
id = @db.save(:testfield => 'true')
|
84
84
|
doc = @db.get(id)
|
85
85
|
@db.delete(doc)
|
86
|
-
|
87
|
-
should raise_error(RestClient::ResourceNotFound)
|
86
|
+
@db.get(id).should be_nil
|
88
87
|
end
|
89
88
|
|
90
89
|
it "should be able to delete a document when given a document id" do
|
91
90
|
id = @db.save(:testfield => 'true')
|
92
91
|
@db.delete(id)
|
93
|
-
|
94
|
-
should raise_error(RestClient::ResourceNotFound)
|
92
|
+
@db.get(id).should be_nil
|
95
93
|
end
|
96
94
|
end
|
97
95
|
|
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.
|
4
|
+
version: 0.0.3
|
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-
|
12
|
+
date: 2009-12-16 00:00:00 +11:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
version: "0"
|
44
44
|
version:
|
45
45
|
- !ruby/object:Gem::Dependency
|
46
|
-
name:
|
46
|
+
name: yajl-ruby
|
47
47
|
type: :runtime
|
48
48
|
version_requirement:
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|