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 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 "json", ">= 0"
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.2
1
+ 0.0.3
@@ -1,4 +1,4 @@
1
- require 'json'
1
+ require 'yajl/json_gem'
2
2
  require 'rest_client'
3
3
  require 'hashie'
4
4
 
@@ -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}"
@@ -1,96 +1,125 @@
1
- # Defines rake tasks for managing databases and views.
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
- # Injects rake tasks for loading and dumping data from databases.
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.inject_data_tasks(base_path)
54
+ def self.inject_tasks(base_path)
11
55
  namespace :throne do
12
- namespace :load_data do
13
- dirs_in_path(base_path).each do |dir, fulldir|
14
- desc "Loads data for database #{dir}"
15
- task dir.to_sym do
16
- load_data_for_database(url(dir), fulldir)
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
- desc "load all dbs"
21
- task :all => dirs_in_path(base_path)
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
- 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)
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
- # 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
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
- desc "push all designs"
62
- task :all => dirs_in_path(base_path)
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
- # 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
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
- 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
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
- 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."
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
- # 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
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, source_path)
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
- Dir.glob(File.join(source_path, '*')).each do |doc_path|
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
- Dir.glob(File.join(doc_path, 'lists', '*')) do |list_path|
141
- list_name = File.basename(list_path)
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
- File.read(listfn) if File.exists?(listfn)
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
- File.read(mapfn) if File.exists?(mapfn)
183
+ inject_code_includes(base_path, database, mapfn) if File.exists?(mapfn)
154
184
  doc['views'][view_name]['reduce'] =
155
- File.read(reducefn) if File.exists?(reducefn)
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 #{source_path} loaded"
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, source_path)
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
- Dir.glob(source_path + '/**/*.yml').each do |yml|
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
- Dir.glob(source_path + '/**/*.json').each do |json|
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
- Dir.mkdir(outdir) unless File.exists?(outdir)
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.to_json
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.dirs_in_path(path)
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 dburl = ENV['DB_URL']
264
- return dburl
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 or DB_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
@@ -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
- lambda {@db.get(id)}.
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
- lambda {@db.get(id)}.
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.2
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-14 00:00:00 +11:00
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: json
46
+ name: yajl-ruby
47
47
  type: :runtime
48
48
  version_requirement:
49
49
  version_requirements: !ruby/object:Gem::Requirement