rackjson 0.1.0
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/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.markdown +144 -0
- data/Rakefile +56 -0
- data/VERSION +1 -0
- data/lib/rackjson.rb +57 -0
- data/lib/rackjson/document.rb +29 -0
- data/lib/rackjson/json_document.rb +47 -0
- data/lib/rackjson/json_query.rb +73 -0
- data/lib/rackjson/mongo_document.rb +32 -0
- data/lib/rackjson/request.rb +38 -0
- data/lib/rackjson/resource.rb +87 -0
- data/lib/rackjson/response.rb +33 -0
- data/rackjson.gemspec +83 -0
- data/test/helper.rb +12 -0
- data/test/suite.rb +5 -0
- data/test/test_builder.rb +17 -0
- data/test/test_document.rb +63 -0
- data/test/test_json_document.rb +73 -0
- data/test/test_json_query.rb +69 -0
- data/test/test_mongo_document.rb +24 -0
- data/test/test_resource.rb +108 -0
- data/test/test_response.rb +45 -0
- metadata +125 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Oliver Nightingale
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
# RackJSON
|
2
|
+
|
3
|
+
Simple Rack middleware for persisting JSON documents.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
`gem install rackjson`
|
8
|
+
|
9
|
+
RackJSON uses MongoDB for persistence so you also need to install MongoDB.
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
In your rackup file:
|
14
|
+
|
15
|
+
`require mongo
|
16
|
+
require rackjson`
|
17
|
+
|
18
|
+
`use Rack::JSON::Resource, :collections => [:notes], :db => Mongo::Connection.new.db("mydb")`
|
19
|
+
|
20
|
+
`run lambda { |env|
|
21
|
+
[404, {'Content-Length' => '9', 'Content-Type' => 'text/plain'}, "Not Found"]
|
22
|
+
}`
|
23
|
+
|
24
|
+
This will set up a RESTful resource called 'notes' at /notes which will store any JSON document.
|
25
|
+
|
26
|
+
### REST API
|
27
|
+
|
28
|
+
To see what actions are available on the notes resource:
|
29
|
+
|
30
|
+
`curl -i -XOPTIONS http://localhost:9292/notes`
|
31
|
+
|
32
|
+
`HTTP/1.1 200 OK`
|
33
|
+
`Connection: close`
|
34
|
+
`Date: Sun, 11 Apr 2010 11:09:40 GMT`
|
35
|
+
`Content-Type: text/plain`
|
36
|
+
`Content-Length: 0`
|
37
|
+
`Allow: GET, POST`
|
38
|
+
|
39
|
+
Listing the existing notes:
|
40
|
+
|
41
|
+
`curl -i http://localhost:9292/notes`
|
42
|
+
|
43
|
+
`HTTP/1.1 200 OK
|
44
|
+
Connection: close
|
45
|
+
Date: Sun, 11 Apr 2010 11:12:04 GMT
|
46
|
+
Content-Type: application/json
|
47
|
+
Content-Length: 2`
|
48
|
+
|
49
|
+
`[]`
|
50
|
+
|
51
|
+
Currently there are no notes, create one with a post request:
|
52
|
+
|
53
|
+
`curl -i -XPOST -d'{"title":"hello world!"}' http://localhost:9292/notes`
|
54
|
+
|
55
|
+
`HTTP/1.1 201 Created
|
56
|
+
Connection: close
|
57
|
+
Date: Sun, 11 Apr 2010 11:14:17 GMT
|
58
|
+
Content-Type: application/json
|
59
|
+
Content-Length: 149`
|
60
|
+
|
61
|
+
`{"updated_at":"Sun Apr 11 12:14:17 +0100 2010","title":"hello world!","_id":"4bc1af0934701204fd000001","created_at":"Sun Apr 11 12:14:17 +0100 2010"}`
|
62
|
+
|
63
|
+
RackJSON will assign an id to this resource as _id. This can be used to access this resource directly
|
64
|
+
|
65
|
+
`curl -i http://localhost:9292/notes/4bc1af0934701204fd000001`
|
66
|
+
|
67
|
+
`HTTP/1.1 200 OK
|
68
|
+
Connection: close
|
69
|
+
Date: Sun, 11 Apr 2010 11:16:30 GMT
|
70
|
+
Content-Type: application/json
|
71
|
+
Content-Length: 147`
|
72
|
+
|
73
|
+
`[{"updated_at":"Sun Apr 11 11:14:17 UTC 2010","title":"hello world!","_id":"4bc1af0934701204fd000001","created_at":"Sun Apr 11 11:14:17 UTC 2010"}]`
|
74
|
+
|
75
|
+
This resource will also appear in the index of notes resources
|
76
|
+
|
77
|
+
`curl -i http://localhost:9292/notes`
|
78
|
+
|
79
|
+
`HTTP/1.1 200 OK
|
80
|
+
Connection: close
|
81
|
+
Date: Sun, 11 Apr 2010 11:17:27 GMT
|
82
|
+
Content-Type: application/json
|
83
|
+
Content-Length: 147`
|
84
|
+
|
85
|
+
`[{"updated_at":"Sun Apr 11 11:14:17 UTC 2010","title":"hello world!","_id":"4bc1af0934701204fd000001","created_at":"Sun Apr 11 11:14:17 UTC 2010"}]`
|
86
|
+
|
87
|
+
Update a resource using a PUT request
|
88
|
+
|
89
|
+
`curl -i -XPUT -d'{"title":"updated"}' http://localhost:9292/notes/4bc1af0934701204fd000001`
|
90
|
+
|
91
|
+
`HTTP/1.1 200 OK
|
92
|
+
Connection: close
|
93
|
+
Date: Sun, 11 Apr 2010 11:25:04 GMT
|
94
|
+
Content-Type: application/json
|
95
|
+
Content-Length: 144`
|
96
|
+
|
97
|
+
`{"updated_at":"Sun Apr 11 12:25:04 +0100 2010","title":"updated","_id":"4bc1af0934701204fd000001","created_at":"Sun Apr 11 12:25:04 +0100 2010"}`
|
98
|
+
|
99
|
+
A PUT request can also be used to create a resource at the given location:
|
100
|
+
|
101
|
+
`curl -i -XPUT -d'{"foo":"bar"}' http://localhost:9292/notes/1`
|
102
|
+
|
103
|
+
`HTTP/1.1 200 OK
|
104
|
+
Connection: close
|
105
|
+
Date: Sun, 11 Apr 2010 11:27:13 GMT
|
106
|
+
Content-Type: application/json
|
107
|
+
Content-Length: 113`
|
108
|
+
|
109
|
+
`{"updated_at":"Sun Apr 11 12:27:13 +0100 2010","_id":1,"foo":"bar","created_at":"Sun Apr 11 12:27:13 +0100 2010"}`
|
110
|
+
|
111
|
+
Finally a resource can be deleted using a DELETE request
|
112
|
+
|
113
|
+
`curl -i -XDELETE http://localhost:9292/notes/1`
|
114
|
+
|
115
|
+
`HTTP/1.1 200 OK
|
116
|
+
Connection: close
|
117
|
+
Date: Sun, 11 Apr 2010 11:29:14 GMT
|
118
|
+
Content-Type: application/json
|
119
|
+
Content-Length: 12
|
120
|
+
`
|
121
|
+
|
122
|
+
`{"ok": "true"}`
|
123
|
+
|
124
|
+
### JSON Query
|
125
|
+
|
126
|
+
RackJSON supports querying of the resources using JSONQuery style syntax. Pass the JSONQuery as query string parameters when making a get request.
|
127
|
+
|
128
|
+
`curl http://localhost:9292/notes?[?title=foo]`
|
129
|
+
|
130
|
+
This will get all resources which have a title attribute = 'foo'. Greater than and less than are also supported:
|
131
|
+
|
132
|
+
`curl http://localhost:9292/notes?[?rating>5][?position=<10]`
|
133
|
+
|
134
|
+
The resources can be ordered using the query syntax also, to get the notes ordered by rating:
|
135
|
+
|
136
|
+
`curl http://localhost:9292/notes?[\rating]`
|
137
|
+
|
138
|
+
You can limit the number of resources that are returned, to get the first 10 resources:
|
139
|
+
|
140
|
+
`curl http://localhost:9292/notes?[0:10]`
|
141
|
+
|
142
|
+
To only select certain properties of a document you can specify the fields you want, to get just the titles
|
143
|
+
|
144
|
+
`curl http://localhost:9292/notes?[=title]`
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "rackjson"
|
8
|
+
gem.summary = %Q{A rack end point for storing json documents.}
|
9
|
+
gem.description = %Q{A rack end point for storing json documents.}
|
10
|
+
gem.email = "oliver.n@new-bamboo.co.uk"
|
11
|
+
gem.homepage = "http://github.com/olivernn/rackjson"
|
12
|
+
gem.authors = ["Oliver Nightingale"]
|
13
|
+
gem.add_dependency('mongo', '>=0.19.1')
|
14
|
+
gem.add_dependency('mongo_ext', '>=0.19.1')
|
15
|
+
gem.add_dependency('json', '>=1.2.3')
|
16
|
+
gem.add_dependency('rack', '>=1.0.1')
|
17
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
18
|
+
end
|
19
|
+
Jeweler::GemcutterTasks.new
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
Rake::TestTask.new(:test) do |test|
|
26
|
+
test.libs << 'lib' << 'test'
|
27
|
+
test.pattern = 'test/**/test_*.rb'
|
28
|
+
test.verbose = true
|
29
|
+
end
|
30
|
+
|
31
|
+
begin
|
32
|
+
require 'rcov/rcovtask'
|
33
|
+
Rcov::RcovTask.new do |test|
|
34
|
+
test.libs << 'test'
|
35
|
+
test.pattern = 'test/**/test_*.rb'
|
36
|
+
test.verbose = true
|
37
|
+
end
|
38
|
+
rescue LoadError
|
39
|
+
task :rcov do
|
40
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
task :test => :check_dependencies
|
45
|
+
|
46
|
+
task :default => :test
|
47
|
+
|
48
|
+
require 'rake/rdoctask'
|
49
|
+
Rake::RDocTask.new do |rdoc|
|
50
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
51
|
+
|
52
|
+
rdoc.rdoc_dir = 'rdoc'
|
53
|
+
rdoc.title = "rackjson #{version}"
|
54
|
+
rdoc.rdoc_files.include('README*')
|
55
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
56
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/rackjson.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
module Rack #:nodoc:
|
2
|
+
class Builder
|
3
|
+
|
4
|
+
# Setup resource collections hosted behind OAuth and OpenID auth filters.
|
5
|
+
#
|
6
|
+
# ===Example
|
7
|
+
# contain :notes, :projects
|
8
|
+
#
|
9
|
+
# def contain(*args)
|
10
|
+
# @ins << lambda do |app|
|
11
|
+
# Rack::Session::Pool.new(
|
12
|
+
# CloudKit::OAuthFilter.new(
|
13
|
+
# CloudKit::OpenIDFilter.new(
|
14
|
+
# CloudKit::Service.new(app, :collections => args.to_a))))
|
15
|
+
# end
|
16
|
+
# @last_cloudkit_id = @ins.last.object_id
|
17
|
+
# end
|
18
|
+
|
19
|
+
def private_resource
|
20
|
+
@ins << lambda do |app|
|
21
|
+
Rack::JSON::UserFilter.new(
|
22
|
+
Rack::JSON::Resource.new(app, args.first),
|
23
|
+
args.first
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Setup resource collections without authentication.
|
29
|
+
#
|
30
|
+
# ===Example
|
31
|
+
# expose_resources :collections => [:notes, :projects], :db => Mongo::Connection.new.db("my_mongodb")
|
32
|
+
#
|
33
|
+
def expose_resource(*args)
|
34
|
+
@ins << lambda do |app|
|
35
|
+
Rack::JSON::Resource.new(app, args.first)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
require 'rubygems'
|
42
|
+
require 'json'
|
43
|
+
require 'rack'
|
44
|
+
require 'mongo'
|
45
|
+
require 'time'
|
46
|
+
|
47
|
+
module Rack::JSON
|
48
|
+
|
49
|
+
autoload :Document, 'rackjson/document'
|
50
|
+
autoload :JSONDocument, 'rackjson/json_document'
|
51
|
+
autoload :JSONQuery, 'rackjson/json_query'
|
52
|
+
autoload :MongoDocument, 'rackjson/mongo_document'
|
53
|
+
autoload :Request, 'rackjson/request'
|
54
|
+
autoload :Resource, 'rackjson/resource'
|
55
|
+
autoload :Response, 'rackjson/response'
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Rack::JSON
|
2
|
+
class Document
|
3
|
+
|
4
|
+
def initialize(doc)
|
5
|
+
if doc.is_a? String
|
6
|
+
@document = Rack::JSON::JSONDocument.new(doc)
|
7
|
+
else doc.is_a? OrderedHash
|
8
|
+
@document = Rack::JSON::MongoDocument.new(doc)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(name, *args)
|
13
|
+
@document.send(name, *args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_json
|
17
|
+
unless @json
|
18
|
+
gen_attrs = @document.attributes
|
19
|
+
gen_attrs.each_pair do |key, value|
|
20
|
+
|
21
|
+
if value.is_a? Mongo::ObjectID
|
22
|
+
gen_attrs[key] = gen_attrs[key].to_s
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
@json ||= JSON.generate gen_attrs
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Rack::JSON
|
2
|
+
class JSONDocument
|
3
|
+
|
4
|
+
attr_reader :attributes
|
5
|
+
|
6
|
+
def initialize(doc)
|
7
|
+
@attributes = JSON.parse(doc)
|
8
|
+
set_attributes
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_id(id)
|
12
|
+
@attributes["_id"] = id unless @attributes["_id"]
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def set_attribute_created_at
|
18
|
+
@attributes["created_at"] = Time.now unless @attributes["created_at"]
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_attribute_dates
|
22
|
+
@attributes.each_pair do |key, value|
|
23
|
+
if value.class == String && value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/)
|
24
|
+
@attributes[key] = Time.parse(value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def set_attribute_ids
|
30
|
+
@attributes["_id"] = Mongo::ObjectID.from_string(@attributes["_id"].to_s)
|
31
|
+
rescue Mongo::InvalidObjectID
|
32
|
+
return false
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_attribute_updated_at
|
36
|
+
@attributes["updated_at"] = Time.now
|
37
|
+
end
|
38
|
+
|
39
|
+
def set_attributes
|
40
|
+
private_methods.each do |method|
|
41
|
+
if method.match /^set_attribute_\w*$/
|
42
|
+
send method
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Rack::JSON
|
2
|
+
class JSONQuery
|
3
|
+
|
4
|
+
attr_accessor :options, :selector
|
5
|
+
|
6
|
+
def initialize(query_string)
|
7
|
+
@query_string = query_string
|
8
|
+
@conditions = @query_string.split(/\[|\]/).compact.reject {|s| s.empty? }
|
9
|
+
@options = {}
|
10
|
+
@selector = {}
|
11
|
+
build
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def build
|
17
|
+
@conditions.each do |condition|
|
18
|
+
private_methods.each do |method|
|
19
|
+
if method.match /^set_query_\w*$/
|
20
|
+
send method, condition
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def comparison(symbol)
|
27
|
+
{
|
28
|
+
'>' => '$gt',
|
29
|
+
'<' => '$lt',
|
30
|
+
'=<' => '$lte',
|
31
|
+
'>=' => '$gte'
|
32
|
+
}[symbol]
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_query_fields(condition)
|
36
|
+
if condition.match /^=\w+$/
|
37
|
+
@options[:fields] = condition.sub('=', '').split(',')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def set_query_selector_equality(condition)
|
42
|
+
if condition.match /^\?\w+=.+$/
|
43
|
+
field = condition.sub('?', '').split('=').first.to_sym
|
44
|
+
value = (condition.sub('?', '').split('=').last.match(/^\d+$/) ? condition.sub('?', '').split('=').last.to_f : condition.sub('?', '').split('=').last.to_s)
|
45
|
+
@selector[field] = value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def set_query_selector(condition)
|
50
|
+
if condition.match /^\?\w+>=|=<|<|>\d+$/
|
51
|
+
field = condition.sub('?', '').split(/>=|=<|<|>/).first.to_sym
|
52
|
+
value = condition.sub('?', '').split(/>=|=<|<|>/).last.to_f
|
53
|
+
@selector[field] = { comparison(condition.slice(/>=|=<|<|>/)) => value }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def set_query_skip_limit(condition)
|
58
|
+
if condition.match /^\d+:\d+$/
|
59
|
+
@options[:skip] = condition.split(':').first.to_i
|
60
|
+
@options[:limit] = condition.split(':').last.to_i
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def set_query_sort(condition)
|
65
|
+
condition.split(/,\s?/).each do |part|
|
66
|
+
if part.match /^[\/|\\]\w*$/
|
67
|
+
@options[:sort] ||= []
|
68
|
+
@options[:sort] << [part.sub(/[\/|\\]/, '').to_sym, (part.match(/\//) ? :asc : :desc)]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Rack::JSON
|
2
|
+
class MongoDocument
|
3
|
+
|
4
|
+
attr_accessor :attributes
|
5
|
+
|
6
|
+
def initialize(row)
|
7
|
+
@attributes = row
|
8
|
+
set_created_at
|
9
|
+
set_attributes
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def set_attribute_ids
|
15
|
+
@attributes["_id"] = @attributes["_id"].to_s if (@attributes["_id"].is_a? Mongo::ObjectID)
|
16
|
+
end
|
17
|
+
|
18
|
+
def set_created_at
|
19
|
+
if @attributes["_id"].is_a? Mongo::ObjectID
|
20
|
+
@attributes["created_at"] = @attributes["_id"].generation_time unless @attributes["created_at"]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_attributes
|
25
|
+
private_methods.each do |method|
|
26
|
+
if method.match /^set_attribute_\w*$/
|
27
|
+
send method
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Rack::JSON
|
2
|
+
class Request < Rack::Request
|
3
|
+
include Rack::Utils
|
4
|
+
def initialize(env)
|
5
|
+
super(env)
|
6
|
+
end
|
7
|
+
|
8
|
+
def collection
|
9
|
+
self.path_info.split('/')[1] || ""
|
10
|
+
end
|
11
|
+
|
12
|
+
def collection_path?
|
13
|
+
self.path_info.match /^\/\w+$/
|
14
|
+
end
|
15
|
+
|
16
|
+
def member_path?
|
17
|
+
self.path_info.match /^\/\w+\/\w+$/
|
18
|
+
end
|
19
|
+
|
20
|
+
def json
|
21
|
+
self.body.rewind
|
22
|
+
self.body.read
|
23
|
+
end
|
24
|
+
|
25
|
+
def query
|
26
|
+
@query ||= Rack::JSON::JSONQuery.new(unescape(query_string))
|
27
|
+
end
|
28
|
+
|
29
|
+
def resource_id
|
30
|
+
id_string = self.path_info.split('/').last.to_s
|
31
|
+
begin
|
32
|
+
Mongo::ObjectID.from_string(id_string)
|
33
|
+
rescue Mongo::InvalidObjectID
|
34
|
+
id_string.match(/^\d+$/) ? id_string.to_i : id_string
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Rack::JSON
|
2
|
+
class Resource
|
3
|
+
METHODS_NOT_ALLOWED = [:trace, :connect]
|
4
|
+
|
5
|
+
def initialize(app, options)
|
6
|
+
@app = app
|
7
|
+
@collections = options[:collections]
|
8
|
+
@db = options[:db]
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
request = Rack::JSON::Request.new(env)
|
13
|
+
if bypass? request
|
14
|
+
@app.call(env)
|
15
|
+
else
|
16
|
+
@collection = @db[request.collection]
|
17
|
+
send(request.request_method.downcase, request)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def bypass?(request)
|
24
|
+
!(@collections.include? request.collection.to_sym)
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete(request)
|
28
|
+
if request.member_path?
|
29
|
+
if @collection.remove({:_id => request.resource_id})
|
30
|
+
render "{'ok': true}"
|
31
|
+
end
|
32
|
+
else
|
33
|
+
render "", :status => 405
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
[:get, :head].each do |method|
|
38
|
+
define_method method do |request|
|
39
|
+
request.query.selector.merge!({:_id => request.resource_id}) if request.member_path?
|
40
|
+
rows = []
|
41
|
+
@collection.find(request.query.selector, request.query.options).each { |row| rows << Rack::JSON::Document.new(row).attributes }
|
42
|
+
if rows.empty? && request.member_path?
|
43
|
+
render "document not found", :status => 404, :head => (method == :head)
|
44
|
+
else
|
45
|
+
render JSON.generate(rows), :head => (method == :head)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def options(request)
|
51
|
+
if request.collection_path?
|
52
|
+
headers = { "Allow" => "GET, POST" }
|
53
|
+
elsif request.member_path?
|
54
|
+
headers = { "Allow" => "GET, PUT, DELETE" }
|
55
|
+
end
|
56
|
+
render "", :headers => headers
|
57
|
+
end
|
58
|
+
|
59
|
+
def post(request)
|
60
|
+
document = Rack::JSON::Document.new(request.json)
|
61
|
+
@collection.insert(document.attributes)
|
62
|
+
render document.to_json, :status => 201
|
63
|
+
rescue JSON::ParserError => error
|
64
|
+
render (error.class.to_s + " :" + error.message), :status => 422
|
65
|
+
end
|
66
|
+
|
67
|
+
def put(request)
|
68
|
+
@collection.find_one(:_id => request.resource_id) ? status = 200 : status = 201
|
69
|
+
document = Rack::JSON::Document.new(request.json)
|
70
|
+
document.add_id(request.resource_id)
|
71
|
+
@collection.save(document.attributes)
|
72
|
+
render document.to_json, :status => status
|
73
|
+
rescue JSON::ParserError => error
|
74
|
+
render (error.class.to_s + " :" + error.message), :status => 422
|
75
|
+
end
|
76
|
+
|
77
|
+
def render(body, options={})
|
78
|
+
Rack::JSON::Response.new(body, options).to_a
|
79
|
+
end
|
80
|
+
|
81
|
+
METHODS_NOT_ALLOWED.each do |method|
|
82
|
+
define_method method do
|
83
|
+
render "", :status => 405
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Rack::JSON
|
2
|
+
class Response
|
3
|
+
def initialize(body, options={})
|
4
|
+
@status = options[:status] || 200
|
5
|
+
@body = body
|
6
|
+
@head = options[:head] || false
|
7
|
+
@headers = options[:headers] || {}
|
8
|
+
set_headers
|
9
|
+
head_response if @head
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_a
|
13
|
+
[@status, @headers, @body]
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def head_response
|
19
|
+
@body = ""
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_headers
|
23
|
+
@headers["Content-Length"] = @body.length.to_s
|
24
|
+
begin
|
25
|
+
JSON.parse(@body)
|
26
|
+
@headers["Content-Type"] = "application/json"
|
27
|
+
rescue JSON::ParserError => error
|
28
|
+
# the response will only ever be either json or plain text
|
29
|
+
@headers["Content-Type"] = "text/plain"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/rackjson.gemspec
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{rackjson}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Oliver Nightingale"]
|
12
|
+
s.date = %q{2010-04-25}
|
13
|
+
s.description = %q{A rack end point for storing json documents.}
|
14
|
+
s.email = %q{oliver.n@new-bamboo.co.uk}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.markdown"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.markdown",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/rackjson.rb",
|
27
|
+
"lib/rackjson/document.rb",
|
28
|
+
"lib/rackjson/json_document.rb",
|
29
|
+
"lib/rackjson/json_query.rb",
|
30
|
+
"lib/rackjson/mongo_document.rb",
|
31
|
+
"lib/rackjson/request.rb",
|
32
|
+
"lib/rackjson/resource.rb",
|
33
|
+
"lib/rackjson/response.rb",
|
34
|
+
"rackjson.gemspec",
|
35
|
+
"test/helper.rb",
|
36
|
+
"test/suite.rb",
|
37
|
+
"test/test_document.rb",
|
38
|
+
"test/test_json_document.rb",
|
39
|
+
"test/test_json_query.rb",
|
40
|
+
"test/test_mongo_document.rb",
|
41
|
+
"test/test_resource.rb",
|
42
|
+
"test/test_response.rb"
|
43
|
+
]
|
44
|
+
s.homepage = %q{http://github.com/olivernn/rackjson}
|
45
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
46
|
+
s.require_paths = ["lib"]
|
47
|
+
s.rubygems_version = %q{1.3.5}
|
48
|
+
s.summary = %q{A rack end point for storing json documents.}
|
49
|
+
s.test_files = [
|
50
|
+
"test/helper.rb",
|
51
|
+
"test/suite.rb",
|
52
|
+
"test/test_builder.rb",
|
53
|
+
"test/test_document.rb",
|
54
|
+
"test/test_json_document.rb",
|
55
|
+
"test/test_json_query.rb",
|
56
|
+
"test/test_mongo_document.rb",
|
57
|
+
"test/test_resource.rb",
|
58
|
+
"test/test_response.rb"
|
59
|
+
]
|
60
|
+
|
61
|
+
if s.respond_to? :specification_version then
|
62
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
63
|
+
s.specification_version = 3
|
64
|
+
|
65
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
66
|
+
s.add_runtime_dependency(%q<mongo>, [">= 0.19.1"])
|
67
|
+
s.add_runtime_dependency(%q<mongo_ext>, [">= 0.19.1"])
|
68
|
+
s.add_runtime_dependency(%q<json>, [">= 1.2.3"])
|
69
|
+
s.add_runtime_dependency(%q<rack>, [">= 1.0.1"])
|
70
|
+
else
|
71
|
+
s.add_dependency(%q<mongo>, [">= 0.19.1"])
|
72
|
+
s.add_dependency(%q<mongo_ext>, [">= 0.19.1"])
|
73
|
+
s.add_dependency(%q<json>, [">= 1.2.3"])
|
74
|
+
s.add_dependency(%q<rack>, [">= 1.0.1"])
|
75
|
+
end
|
76
|
+
else
|
77
|
+
s.add_dependency(%q<mongo>, [">= 0.19.1"])
|
78
|
+
s.add_dependency(%q<mongo_ext>, [">= 0.19.1"])
|
79
|
+
s.add_dependency(%q<json>, [">= 1.2.3"])
|
80
|
+
s.add_dependency(%q<rack>, [">= 1.0.1"])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
data/test/helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'rack'
|
4
|
+
require 'rack/test'
|
5
|
+
require 'timecop'
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
8
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
9
|
+
require 'rackjson'
|
10
|
+
|
11
|
+
class Test::Unit::TestCase
|
12
|
+
end
|
data/test/suite.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'rack/builder'
|
3
|
+
|
4
|
+
class BuilderTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_expose_resource_shortcut
|
7
|
+
app = Rack::Builder.new do
|
8
|
+
expose_resource :collections => [:testing], :db => Mongo::Connection.new.db("test")
|
9
|
+
run lambda { |env|
|
10
|
+
[404, {'Content-Length' => '9', 'Content-Type' => 'text/plain'}, "Not Found"]
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
response = Rack::MockRequest.new(app).get('/testing')
|
15
|
+
assert response.ok?
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class DocumentTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@db = Mongo::Connection.new.db("test")
|
7
|
+
@collection = @db['resource_test']
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
@collection.drop
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_creating_from_json
|
15
|
+
json = '{"test":"hello"}'
|
16
|
+
document = Rack::JSON::Document.new(json)
|
17
|
+
assert_equal("hello", document.attributes["test"])
|
18
|
+
assert_equal(Time.now.to_s, document.attributes["created_at"].to_s)
|
19
|
+
assert_equal(Time.now.to_s, document.attributes["updated_at"].to_s)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_creating_from_json_with_id
|
23
|
+
json = '{"_id": "4b9f783ba040140525000001", "test":"hello"}'
|
24
|
+
document = Rack::JSON::Document.new(json)
|
25
|
+
assert_equal(Mongo::ObjectID.from_string('4b9f783ba040140525000001'), document.attributes["_id"])
|
26
|
+
assert_equal("hello", document.attributes["test"])
|
27
|
+
assert_equal(Time.now.to_s, document.attributes["created_at"].to_s)
|
28
|
+
assert_equal(Time.now.to_s, document.attributes["updated_at"].to_s)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_creating_from_json_with_non_object_id
|
32
|
+
json = '{"_id": 1, "test":"hello"}'
|
33
|
+
document = Rack::JSON::Document.new(json)
|
34
|
+
assert_equal(1, document.attributes["_id"])
|
35
|
+
assert_equal("hello", document.attributes["test"])
|
36
|
+
assert_equal(Time.now.to_s, document.attributes["created_at"].to_s)
|
37
|
+
assert_equal(Time.now.to_s, document.attributes["updated_at"].to_s)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_adding_id
|
41
|
+
json = '{"test":"hello"}'
|
42
|
+
document = Rack::JSON::Document.new(json)
|
43
|
+
id = @collection.insert(document.attributes)
|
44
|
+
document.add_id(id)
|
45
|
+
assert_equal(id.to_s, document.attributes[:_id].to_s)
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_creating_from_row
|
49
|
+
@collection.insert({"test"=>"hello"})
|
50
|
+
rows = []
|
51
|
+
@collection.find.each { |r| rows << r }
|
52
|
+
document = Rack::JSON::Document.new(rows.first)
|
53
|
+
assert_equal("hello", document.attributes["test"])
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_document_created_at
|
57
|
+
json = '{"test":"hello", "created_at": "01/01/2010"}'
|
58
|
+
document = Rack::JSON::Document.new(json)
|
59
|
+
assert_equal("hello", document.attributes["test"])
|
60
|
+
assert_equal("01/01/2010", document.attributes["created_at"])
|
61
|
+
assert_equal(Time.now.to_s, document.attributes["updated_at"].to_s)
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class JSONDocumentTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_parsing_simple_json_structure
|
6
|
+
hash = { "title" => "testing" }
|
7
|
+
doc = JSON.generate hash
|
8
|
+
assert_equal hash["title"], Rack::JSON::JSONDocument.new(doc).attributes["title"]
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_parsing_nested_json_structure
|
12
|
+
hash = { "title" => "nested", "nest" => { "note" => "im nested" } }
|
13
|
+
doc = JSON.generate hash
|
14
|
+
assert_equal hash["nest"], Rack::JSON::JSONDocument.new(doc).attributes["nest"]
|
15
|
+
assert_equal hash["title"], Rack::JSON::JSONDocument.new(doc).attributes["title"]
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_parsing_dates_from_json
|
19
|
+
hash = { "date" => "2010-04-10T14:20:12Z" }
|
20
|
+
doc = JSON.generate hash
|
21
|
+
assert_equal( Time.parse("2010-04-10T14:20:12Z") , Rack::JSON::JSONDocument.new(doc).attributes["date"])
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_parsing_mongo_object_id
|
25
|
+
hash = { "_id" => "4ba7e82ca04014011c000001" }
|
26
|
+
doc = JSON.generate hash
|
27
|
+
assert_equal(Mongo::ObjectID.from_string("4ba7e82ca04014011c000001"), Rack::JSON::JSONDocument.new(doc).attributes["_id"])
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_parsing_non_mongo_object_ids
|
31
|
+
hash = { "_id" => 1 }
|
32
|
+
doc = JSON.generate hash
|
33
|
+
assert_equal(hash["_id"], Rack::JSON::JSONDocument.new(doc).attributes["_id"])
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_adding_an_id
|
37
|
+
hash = { "test" => "I don't have an ID" }
|
38
|
+
doc = JSON.generate hash
|
39
|
+
json_doc = Rack::JSON::JSONDocument.new(doc)
|
40
|
+
json_doc.add_id(1)
|
41
|
+
assert_equal(1, json_doc.attributes["_id"])
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_not_overriding_an_id
|
45
|
+
hash = { "test" => "I do have an ID", "_id" => 2 }
|
46
|
+
doc = JSON.generate hash
|
47
|
+
json_doc = Rack::JSON::JSONDocument.new(doc)
|
48
|
+
json_doc.add_id(1)
|
49
|
+
assert_equal(2 , json_doc.attributes["_id"])
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_adding_timestamps
|
53
|
+
hash = { "_id" => 1 }
|
54
|
+
doc = JSON.generate hash
|
55
|
+
t = Time.now
|
56
|
+
assert_equal(t.to_s, Rack::JSON::JSONDocument.new(doc).attributes["created_at"].to_s)
|
57
|
+
assert_equal(t.to_s, Rack::JSON::JSONDocument.new(doc).attributes["updated_at"].to_s)
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_not_always_updating_created_at
|
61
|
+
hash = { "_id" => 1 }
|
62
|
+
doc = JSON.generate hash
|
63
|
+
t1 = Time.now
|
64
|
+
assert_equal(t1.to_s, Rack::JSON::JSONDocument.new(doc).attributes["created_at"].to_s)
|
65
|
+
assert_equal(t1.to_s, Rack::JSON::JSONDocument.new(doc).attributes["updated_at"].to_s)
|
66
|
+
sleep 1
|
67
|
+
hash2 = { "_id" => 1, "created_at" => t1.to_s }
|
68
|
+
doc = JSON.generate hash2
|
69
|
+
t2 = Time.now
|
70
|
+
assert_equal(t1.to_s, Rack::JSON::JSONDocument.new(doc).attributes["created_at"].to_s)
|
71
|
+
assert_equal(t2.to_s, Rack::JSON::JSONDocument.new(doc).attributes["updated_at"].to_s)
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class QueryTest < Test::Unit::TestCase
|
4
|
+
def test_ascending_sort
|
5
|
+
json_query = "[/price]"
|
6
|
+
query = Rack::JSON::JSONQuery.new(json_query)
|
7
|
+
assert_equal({:sort => [[:price, :asc]]}, query.options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_descending_sort
|
11
|
+
json_query = '[\price]'
|
12
|
+
query = Rack::JSON::JSONQuery.new(json_query)
|
13
|
+
assert_equal({:sort => [[:price, :desc]]}, query.options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_multiple_sort_fields
|
17
|
+
json_query = '[/price, \rating]'
|
18
|
+
query = Rack::JSON::JSONQuery.new(json_query)
|
19
|
+
assert_equal({:sort => [[:price, :asc], [:rating, :desc]]}, query.options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_skips_and_limits
|
23
|
+
json_query = '[0:10]'
|
24
|
+
query = Rack::JSON::JSONQuery.new(json_query)
|
25
|
+
assert_equal({:skip => 0, :limit => 10}, query.options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_map_query
|
29
|
+
json_query = '[=name]'
|
30
|
+
query = Rack::JSON::JSONQuery.new(json_query)
|
31
|
+
assert_equal({:fields => ['name']}, query.options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_single_equality_condition_with_number
|
35
|
+
json_query = '[?price=10]'
|
36
|
+
query = Rack::JSON::JSONQuery.new(json_query)
|
37
|
+
assert_equal({:price => 10}, query.selector)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_single_equality_condition_with_string
|
41
|
+
json_query = '[?name=bob!]'
|
42
|
+
query = Rack::JSON::JSONQuery.new(json_query)
|
43
|
+
assert_equal({:name => 'bob!'}, query.selector)
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_single_greater_than_condition
|
47
|
+
json_query = '[?price>10]'
|
48
|
+
query = Rack::JSON::JSONQuery.new(json_query)
|
49
|
+
assert_equal({:price => {'$gt' => 10}}, query.selector)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_single_greater_than_or_equal_condition
|
53
|
+
json_query = '[?price>=10]'
|
54
|
+
query = Rack::JSON::JSONQuery.new(json_query)
|
55
|
+
assert_equal({:price => { '$gte' => 10}}, query.selector)
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_single_less_than_condition
|
59
|
+
json_query = '[?price<10]'
|
60
|
+
query = Rack::JSON::JSONQuery.new(json_query)
|
61
|
+
assert_equal({:price => {'$lt' => 10}}, query.selector)
|
62
|
+
end
|
63
|
+
|
64
|
+
# def test_single_greater_than_or_equal_condition
|
65
|
+
# json_query = '[?price=<10]'
|
66
|
+
# query = Rack::JSON::JSONQuery.new(json_query)
|
67
|
+
# assert_equal({:price => { '$lte' => 10}}, query.selector)
|
68
|
+
# end
|
69
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class MongoDocumentTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_stringifying_mongo_object_ids
|
6
|
+
hash = { "_id" => Mongo::ObjectID.from_string("4ba7e82ca04014011c000001") }
|
7
|
+
doc = Rack::JSON::MongoDocument.new(hash).attributes
|
8
|
+
assert_equal("4ba7e82ca04014011c000001", doc["_id"])
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_not_stringifying_non_mongo_object_ids
|
12
|
+
hash = { "_id" => 1 }
|
13
|
+
doc = Rack::JSON::MongoDocument.new(hash).attributes
|
14
|
+
assert_equal({ "_id" => 1 }, doc)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_setting_the_created_at_stamp
|
18
|
+
hash = { "_id" => Mongo::ObjectID.from_string("4ba7e82ca04014011c000001") }
|
19
|
+
doc = Rack::JSON::MongoDocument.new(hash).attributes
|
20
|
+
assert_equal({ "_id" => "4ba7e82ca04014011c000001",
|
21
|
+
"created_at" => Mongo::ObjectID.from_string("4ba7e82ca04014011c000001").generation_time
|
22
|
+
}, doc)
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class ResourceTest < Test::Unit::TestCase
|
4
|
+
include Rack::Test::Methods
|
5
|
+
include Rack::Utils
|
6
|
+
def setup
|
7
|
+
@db = Mongo::Connection.new.db("test")
|
8
|
+
@collection = @db['testing']
|
9
|
+
end
|
10
|
+
|
11
|
+
def teardown
|
12
|
+
@collection.drop
|
13
|
+
end
|
14
|
+
|
15
|
+
def app
|
16
|
+
Rack::JSON::Resource.new lambda { |env|
|
17
|
+
[404, {'Content-Length' => '9', 'Content-Type' => 'text/plain'}, "Not Found"]
|
18
|
+
}, :collections => [:testing], :db => @db
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_non_existing_resource
|
22
|
+
get '/blah'
|
23
|
+
assert_equal 404, last_response.status
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_index_method
|
27
|
+
@collection.save({:testing => true})
|
28
|
+
get '/testing'
|
29
|
+
assert last_response.ok?
|
30
|
+
assert_match /"testing":true/, last_response.body
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_creating_a_document
|
34
|
+
put '/testing/1', '{"title": "testing"}'
|
35
|
+
assert_equal 201, last_response.status
|
36
|
+
assert_match /"_id":1/, last_response.body
|
37
|
+
assert_match /"title":"testing"/, last_response.body
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_show_a_single_document
|
41
|
+
put '/testing/1', '{"title": "testing first"}'
|
42
|
+
put '/testing/2', '{"title": "testing second"}'
|
43
|
+
get '/testing/1'
|
44
|
+
assert last_response.ok?
|
45
|
+
assert_match /"title":"testing first"/, last_response.body
|
46
|
+
assert_no_match /"title":"testing second"/, last_response.body
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_not_finding_a_specific_document
|
50
|
+
get '/testing/1'
|
51
|
+
assert_equal 404, last_response.status
|
52
|
+
assert_equal "document not found", last_response.body
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_index_method_with_query_parameters
|
56
|
+
@collection.save({:testing => true, :rating => 5, :title => 'testing'})
|
57
|
+
get '/testing?[?title=testing]'
|
58
|
+
assert last_response.ok?
|
59
|
+
assert_match /"title":"testing"/, last_response.body
|
60
|
+
get '/testing?[?rating=5]'
|
61
|
+
assert last_response.ok?
|
62
|
+
assert_match /"title":"testing"/, last_response.body
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_index_method_with_sort
|
66
|
+
@collection.save({:testing => true, :rating => 5, :title => 'testing'})
|
67
|
+
get '/testing?[/title]'
|
68
|
+
assert last_response.ok?
|
69
|
+
assert_match /"title":"testing"/, last_response.body
|
70
|
+
get '/testing?[?rating=5]'
|
71
|
+
assert last_response.ok?
|
72
|
+
assert_match /"title":"testing"/, last_response.body
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_putting_a_new_document
|
76
|
+
put '/testing/1', '{"title": "testing update"}'
|
77
|
+
assert_equal 201, last_response.status
|
78
|
+
assert_match /"_id":1/, last_response.body
|
79
|
+
assert_match /"title":"testing update"/, last_response.body
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_updating_a_document
|
83
|
+
@collection.save({:title => 'testing', :_id => 1})
|
84
|
+
put '/testing/1', '{"title": "testing update"}'
|
85
|
+
assert last_response.ok?
|
86
|
+
assert_match /"_id":1/, last_response.body
|
87
|
+
assert_match /"title":"testing update"/, last_response.body
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_deleting_a_document
|
91
|
+
@collection.save({:title => 'testing', :_id => 1})
|
92
|
+
assert @collection.find_one({:_id => 1})
|
93
|
+
delete '/testing/1'
|
94
|
+
assert last_response.ok?
|
95
|
+
assert_nil @collection.find_one({:_id => 1})
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_deleting_only_with_member_path
|
99
|
+
delete '/testing'
|
100
|
+
assert_equal 405, last_response.status
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_posting_a_document
|
104
|
+
post '/testing', '{"title": "testing"}'
|
105
|
+
assert last_response.status == 201
|
106
|
+
assert_match /"title":"testing"/, last_response.body
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class ResponseTest < Test::Unit::TestCase
|
4
|
+
def test_setting_the_content
|
5
|
+
response = Rack::JSON::Response.new("test")
|
6
|
+
assert_equal(3, response.to_a.length)
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_default_http_status_to_200
|
10
|
+
response = Rack::JSON::Response.new("test")
|
11
|
+
assert_equal(200, response.to_a[0])
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_setting_http_status_code
|
15
|
+
response = Rack::JSON::Response.new("test", :status => 422)
|
16
|
+
assert_equal(422, response.to_a[0])
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_response_body
|
20
|
+
response = Rack::JSON::Response.new("test")
|
21
|
+
assert_equal("test", response.to_a[2])
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_setting_the_content_length
|
25
|
+
response = Rack::JSON::Response.new("test")
|
26
|
+
assert_equal("4", response.to_a[1]["Content-Length"])
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_setting_the_content_type
|
30
|
+
response = Rack::JSON::Response.new("test")
|
31
|
+
assert_equal("text/plain", response.to_a[1]["Content-Type"])
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_sending_json
|
35
|
+
response = Rack::JSON::Response.new("{'title': 'hello'}")
|
36
|
+
assert_equal("{'title': 'hello'}", response.to_a[2])
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_head_response
|
40
|
+
response = Rack::JSON::Response.new("test", :head => true)
|
41
|
+
assert_equal("", response.to_a[2])
|
42
|
+
assert_equal("4", response.to_a[1]["Content-Length"])
|
43
|
+
assert_equal("text/plain", response.to_a[1]["Content-Type"])
|
44
|
+
end
|
45
|
+
end
|
metadata
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rackjson
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Oliver Nightingale
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-04-25 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: mongo
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.19.1
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: mongo_ext
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.19.1
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: json
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.2.3
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: rack
|
47
|
+
type: :runtime
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.0.1
|
54
|
+
version:
|
55
|
+
description: A rack end point for storing json documents.
|
56
|
+
email: oliver.n@new-bamboo.co.uk
|
57
|
+
executables: []
|
58
|
+
|
59
|
+
extensions: []
|
60
|
+
|
61
|
+
extra_rdoc_files:
|
62
|
+
- LICENSE
|
63
|
+
- README.markdown
|
64
|
+
files:
|
65
|
+
- .document
|
66
|
+
- .gitignore
|
67
|
+
- LICENSE
|
68
|
+
- README.markdown
|
69
|
+
- Rakefile
|
70
|
+
- VERSION
|
71
|
+
- lib/rackjson.rb
|
72
|
+
- lib/rackjson/document.rb
|
73
|
+
- lib/rackjson/json_document.rb
|
74
|
+
- lib/rackjson/json_query.rb
|
75
|
+
- lib/rackjson/mongo_document.rb
|
76
|
+
- lib/rackjson/request.rb
|
77
|
+
- lib/rackjson/resource.rb
|
78
|
+
- lib/rackjson/response.rb
|
79
|
+
- rackjson.gemspec
|
80
|
+
- test/helper.rb
|
81
|
+
- test/suite.rb
|
82
|
+
- test/test_document.rb
|
83
|
+
- test/test_json_document.rb
|
84
|
+
- test/test_json_query.rb
|
85
|
+
- test/test_mongo_document.rb
|
86
|
+
- test/test_resource.rb
|
87
|
+
- test/test_response.rb
|
88
|
+
has_rdoc: true
|
89
|
+
homepage: http://github.com/olivernn/rackjson
|
90
|
+
licenses: []
|
91
|
+
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options:
|
94
|
+
- --charset=UTF-8
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: "0"
|
102
|
+
version:
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: "0"
|
108
|
+
version:
|
109
|
+
requirements: []
|
110
|
+
|
111
|
+
rubyforge_project:
|
112
|
+
rubygems_version: 1.3.5
|
113
|
+
signing_key:
|
114
|
+
specification_version: 3
|
115
|
+
summary: A rack end point for storing json documents.
|
116
|
+
test_files:
|
117
|
+
- test/helper.rb
|
118
|
+
- test/suite.rb
|
119
|
+
- test/test_builder.rb
|
120
|
+
- test/test_document.rb
|
121
|
+
- test/test_json_document.rb
|
122
|
+
- test/test_json_query.rb
|
123
|
+
- test/test_mongo_document.rb
|
124
|
+
- test/test_resource.rb
|
125
|
+
- test/test_response.rb
|