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