throne 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|