giraffesoft-classy_resources 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +30 -0
- data/VERSION.yml +4 -0
- data/lib/classy_resources/active_record.rb +26 -0
- data/lib/classy_resources/mime_type.rb +216 -0
- data/lib/classy_resources/sequel.rb +25 -0
- data/lib/classy_resources.rb +103 -0
- data/test/active_record_test.rb +80 -0
- data/test/fixtures/active_record_test_app.rb +25 -0
- data/test/fixtures/sequel_test_app.rb +19 -0
- data/test/sequel_test.rb +76 -0
- data/test/test_helper.rb +19 -0
- metadata +65 -0
data/README.rdoc
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
= Classy Resources
|
2
|
+
|
3
|
+
Think resource_controller, except for Sinatra.
|
4
|
+
|
5
|
+
= Usage
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'sinatra'
|
9
|
+
require 'classy_resources'
|
10
|
+
require 'classy_resources/active_record'
|
11
|
+
# ... or require 'classy_resources/sequel'
|
12
|
+
# more ORMs coming (it's also easy to implement your own)...
|
13
|
+
|
14
|
+
define_resource :posts, :member => [:get, :put, :delete],
|
15
|
+
:collection => [:get, :post],
|
16
|
+
:formats => [:xml, :json, :yaml]
|
17
|
+
|
18
|
+
The above declaration will create the five actions specified, each responding to all of the formats listed.
|
19
|
+
|
20
|
+
- GET /resources.format # => index
|
21
|
+
- POST /resources.format # => create
|
22
|
+
- GET /resources/1.format # => show
|
23
|
+
- PUT /resources/1.format # => update
|
24
|
+
- DELETE /resources/1.format # => destroy
|
25
|
+
|
26
|
+
Since ClassyResources was designed to be active resource compatible, the params formats and return values are what AR expects.
|
27
|
+
|
28
|
+
== Copyright
|
29
|
+
|
30
|
+
Copyright (c) 2008 James Golick, Daniel Haran, GiraffeSoft Inc.. See LICENSE for details.
|
data/VERSION.yml
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module ClassyResources
|
2
|
+
module ActiveRecord
|
3
|
+
def load_collection(resource)
|
4
|
+
class_for(resource).all
|
5
|
+
end
|
6
|
+
|
7
|
+
def create_object(resource, params)
|
8
|
+
class_for(resource).create!(params)
|
9
|
+
end
|
10
|
+
|
11
|
+
def find_object(resource, id)
|
12
|
+
class_for(resource).find(id)
|
13
|
+
end
|
14
|
+
|
15
|
+
def update_object(object, params)
|
16
|
+
object.update_attributes(params)
|
17
|
+
end
|
18
|
+
|
19
|
+
def destroy_object(object)
|
20
|
+
object.destroy
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
include ClassyResources::ActiveRecord
|
26
|
+
|
@@ -0,0 +1,216 @@
|
|
1
|
+
# borrowed from activesupport
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Mime
|
5
|
+
SET = []
|
6
|
+
EXTENSION_LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
|
7
|
+
LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
|
8
|
+
|
9
|
+
# Encapsulates the notion of a mime type. Can be used at render time, for example, with:
|
10
|
+
#
|
11
|
+
# class PostsController < ActionController::Base
|
12
|
+
# def show
|
13
|
+
# @post = Post.find(params[:id])
|
14
|
+
#
|
15
|
+
# respond_to do |format|
|
16
|
+
# format.html
|
17
|
+
# format.ics { render :text => post.to_ics, :mime_type => Mime::Type["text/calendar"] }
|
18
|
+
# format.xml { render :xml => @people.to_xml }
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
class Type
|
23
|
+
def self.html_types; @@html_types; end
|
24
|
+
def self.browser_generated_types; @@browser_generated_types; end
|
25
|
+
|
26
|
+
@@html_types = Set.new [:html, :all]
|
27
|
+
|
28
|
+
@@browser_generated_types = Set.new [:html, :url_encoded_form, :multipart_form, :text]
|
29
|
+
|
30
|
+
# A simple helper class used in parsing the accept header
|
31
|
+
class AcceptItem #:nodoc:
|
32
|
+
attr_accessor :order, :name, :q
|
33
|
+
|
34
|
+
def initialize(order, name, q=nil)
|
35
|
+
@order = order
|
36
|
+
@name = name.strip
|
37
|
+
q ||= 0.0 if @name == Mime::ALL # default wilcard match to end of list
|
38
|
+
@q = ((q || 1.0).to_f * 100).to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
@name
|
43
|
+
end
|
44
|
+
|
45
|
+
def <=>(item)
|
46
|
+
result = item.q <=> q
|
47
|
+
result = order <=> item.order if result == 0
|
48
|
+
result
|
49
|
+
end
|
50
|
+
|
51
|
+
def ==(item)
|
52
|
+
name == (item.respond_to?(:name) ? item.name : item)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class << self
|
57
|
+
def lookup(string)
|
58
|
+
LOOKUP[string]
|
59
|
+
end
|
60
|
+
|
61
|
+
def lookup_by_extension(extension)
|
62
|
+
EXTENSION_LOOKUP[extension]
|
63
|
+
end
|
64
|
+
|
65
|
+
# Registers an alias that's not used on mime type lookup, but can be referenced directly. Especially useful for
|
66
|
+
# rendering different HTML versions depending on the user agent, like an iPhone.
|
67
|
+
def register_alias(string, symbol, extension_synonyms = [])
|
68
|
+
register(string, symbol, [], extension_synonyms, true)
|
69
|
+
end
|
70
|
+
|
71
|
+
def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
|
72
|
+
Mime.instance_eval { const_set symbol.to_s.upcase, Type.new(string, symbol, mime_type_synonyms) }
|
73
|
+
|
74
|
+
SET << Mime.const_get(symbol.to_s.upcase)
|
75
|
+
|
76
|
+
([string] + mime_type_synonyms).each { |string| LOOKUP[string] = SET.last } unless skip_lookup
|
77
|
+
([symbol.to_s] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext] = SET.last }
|
78
|
+
end
|
79
|
+
|
80
|
+
def parse(accept_header)
|
81
|
+
if accept_header !~ /,/
|
82
|
+
[Mime::Type.lookup(accept_header)]
|
83
|
+
else
|
84
|
+
# keep track of creation order to keep the subsequent sort stable
|
85
|
+
list = []
|
86
|
+
accept_header.split(/,/).each_with_index do |header, index|
|
87
|
+
params, q = header.split(/;\s*q=/)
|
88
|
+
if params
|
89
|
+
params.strip!
|
90
|
+
list << AcceptItem.new(index, params, q) unless params.empty?
|
91
|
+
end
|
92
|
+
end
|
93
|
+
list.sort!
|
94
|
+
|
95
|
+
# Take care of the broken text/xml entry by renaming or deleting it
|
96
|
+
text_xml = list.index("text/xml")
|
97
|
+
app_xml = list.index(Mime::XML.to_s)
|
98
|
+
|
99
|
+
if text_xml && app_xml
|
100
|
+
# set the q value to the max of the two
|
101
|
+
list[app_xml].q = [list[text_xml].q, list[app_xml].q].max
|
102
|
+
|
103
|
+
# make sure app_xml is ahead of text_xml in the list
|
104
|
+
if app_xml > text_xml
|
105
|
+
list[app_xml], list[text_xml] = list[text_xml], list[app_xml]
|
106
|
+
app_xml, text_xml = text_xml, app_xml
|
107
|
+
end
|
108
|
+
|
109
|
+
# delete text_xml from the list
|
110
|
+
list.delete_at(text_xml)
|
111
|
+
|
112
|
+
elsif text_xml
|
113
|
+
list[text_xml].name = Mime::XML.to_s
|
114
|
+
end
|
115
|
+
|
116
|
+
# Look for more specific XML-based types and sort them ahead of app/xml
|
117
|
+
|
118
|
+
if app_xml
|
119
|
+
idx = app_xml
|
120
|
+
app_xml_type = list[app_xml]
|
121
|
+
|
122
|
+
while(idx < list.length)
|
123
|
+
type = list[idx]
|
124
|
+
break if type.q < app_xml_type.q
|
125
|
+
if type.name =~ /\+xml$/
|
126
|
+
list[app_xml], list[idx] = list[idx], list[app_xml]
|
127
|
+
app_xml = idx
|
128
|
+
end
|
129
|
+
idx += 1
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
|
134
|
+
list
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def initialize(string, symbol = nil, synonyms = [])
|
140
|
+
@symbol, @synonyms = symbol, synonyms
|
141
|
+
@string = string
|
142
|
+
end
|
143
|
+
|
144
|
+
def to_s
|
145
|
+
@string
|
146
|
+
end
|
147
|
+
|
148
|
+
def to_str
|
149
|
+
to_s
|
150
|
+
end
|
151
|
+
|
152
|
+
def to_sym
|
153
|
+
@symbol || @string.to_sym
|
154
|
+
end
|
155
|
+
|
156
|
+
def ===(list)
|
157
|
+
if list.is_a?(Array)
|
158
|
+
(@synonyms + [ self ]).any? { |synonym| list.include?(synonym) }
|
159
|
+
else
|
160
|
+
super
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def ==(mime_type)
|
165
|
+
return false if mime_type.blank?
|
166
|
+
(@synonyms + [ self ]).any? do |synonym|
|
167
|
+
synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See
|
172
|
+
# ActionController::RequestForgeryProtection.
|
173
|
+
def verify_request?
|
174
|
+
browser_generated?
|
175
|
+
end
|
176
|
+
|
177
|
+
def html?
|
178
|
+
@@html_types.include?(to_sym) || @string =~ /html/
|
179
|
+
end
|
180
|
+
|
181
|
+
def browser_generated?
|
182
|
+
@@browser_generated_types.include?(to_sym)
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
def method_missing(method, *args)
|
187
|
+
if method.to_s =~ /(\w+)\?$/
|
188
|
+
$1.downcase.to_sym == to_sym
|
189
|
+
else
|
190
|
+
super
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Build list of Mime types for HTTP responses
|
197
|
+
# http://www.iana.org/assignments/media-types/
|
198
|
+
|
199
|
+
Mime::Type.register "*/*", :all
|
200
|
+
Mime::Type.register "text/plain", :text, [], %w(txt)
|
201
|
+
Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
|
202
|
+
Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript )
|
203
|
+
Mime::Type.register "text/css", :css
|
204
|
+
Mime::Type.register "text/calendar", :ics
|
205
|
+
Mime::Type.register "text/csv", :csv
|
206
|
+
Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml )
|
207
|
+
Mime::Type.register "application/rss+xml", :rss
|
208
|
+
Mime::Type.register "application/atom+xml", :atom
|
209
|
+
Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml )
|
210
|
+
|
211
|
+
Mime::Type.register "multipart/form-data", :multipart_form
|
212
|
+
Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
|
213
|
+
|
214
|
+
# http://www.ietf.org/rfc/rfc4627.txt
|
215
|
+
# http://www.json.org/JSONRequest.html
|
216
|
+
Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ClassyResources
|
2
|
+
module Sequel
|
3
|
+
def load_collection(resources)
|
4
|
+
class_for(resources).all
|
5
|
+
end
|
6
|
+
|
7
|
+
def create_object(resource, params)
|
8
|
+
class_for(resource).create(params)
|
9
|
+
end
|
10
|
+
|
11
|
+
def find_object(resource, id)
|
12
|
+
class_for(resource).find(:id => id)
|
13
|
+
end
|
14
|
+
|
15
|
+
def update_object(object, params)
|
16
|
+
object.update(params)
|
17
|
+
end
|
18
|
+
|
19
|
+
def destroy_object(object)
|
20
|
+
object.destroy
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
include ClassyResources::Sequel
|
@@ -0,0 +1,103 @@
|
|
1
|
+
dir = File.expand_path(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
3
|
+
require 'classy_resources/mime_type'
|
4
|
+
|
5
|
+
module ClassyResources
|
6
|
+
def class_for(resource)
|
7
|
+
resource.to_s.singularize.classify.constantize
|
8
|
+
end
|
9
|
+
|
10
|
+
def define_resource(*options)
|
11
|
+
ResourceBuilder.new(self, *options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def collection_url_for(resource, format)
|
15
|
+
"/#{resource}.#{format}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def object_route_url(resource, format)
|
19
|
+
"/#{resource}/:id.#{format}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def object_url_for(resource, format, object)
|
23
|
+
"/#{resource}/#{object.id}.#{format}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def set_content_type(format)
|
27
|
+
content_type Mime.const_get(format.to_s.upcase).to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def serialize(object, format)
|
31
|
+
object.send(:"to_#{format}")
|
32
|
+
end
|
33
|
+
|
34
|
+
class ResourceBuilder
|
35
|
+
attr_reader :resources, :options, :main, :formats
|
36
|
+
|
37
|
+
def initialize(main, *args)
|
38
|
+
@main = main
|
39
|
+
@options = args.pop if args.last.is_a?(Hash)
|
40
|
+
@resources = args
|
41
|
+
@formats = options[:formats] || :xml
|
42
|
+
|
43
|
+
build!
|
44
|
+
end
|
45
|
+
|
46
|
+
def build!
|
47
|
+
resources.each do |r|
|
48
|
+
[*formats].each do |f|
|
49
|
+
[:member, :collection].each do |t|
|
50
|
+
[*options[t]].each do |v|
|
51
|
+
send(:"define_#{t}_#{v}", r, f) unless v.nil?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
protected
|
59
|
+
def define_collection_get(resource, format)
|
60
|
+
get collection_url_for(resource, format) do
|
61
|
+
set_content_type(format)
|
62
|
+
serialize(load_collection(resource), format)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def define_collection_post(resource, format)
|
67
|
+
post collection_url_for(resource, format) do
|
68
|
+
set_content_type(format)
|
69
|
+
object = create_object(resource, params[resource.to_s.singularize])
|
70
|
+
redirect object_url_for(resource, format, object)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def define_member_get(resource, format)
|
75
|
+
get object_route_url(resource, format) do
|
76
|
+
set_content_type(format)
|
77
|
+
object = find_object(resource, params[:id])
|
78
|
+
serialize(object, format)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def define_member_put(resource, format)
|
83
|
+
put object_route_url(resource, format) do
|
84
|
+
set_content_type(format)
|
85
|
+
object = find_object(resource, params[:id])
|
86
|
+
update_object(object, params[resource.to_s.singularize])
|
87
|
+
serialize(object, format)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def define_member_delete(resource, format)
|
92
|
+
delete object_route_url(resource, format) do
|
93
|
+
set_content_type(format)
|
94
|
+
object = find_object(resource, params[:id])
|
95
|
+
destroy_object(object)
|
96
|
+
""
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
include ClassyResources
|
103
|
+
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
require 'sinatra'
|
3
|
+
require 'sinatra/test/unit'
|
4
|
+
require File.dirname(__FILE__) + '/fixtures/active_record_test_app'
|
5
|
+
|
6
|
+
class ActiveRecordTest < Test::Unit::TestCase
|
7
|
+
context "on GET to /posts with xml" do
|
8
|
+
setup do
|
9
|
+
2.times { create_post }
|
10
|
+
get '/posts.xml'
|
11
|
+
end
|
12
|
+
|
13
|
+
expect { assert_equal 200, @response.status }
|
14
|
+
expect { assert_equal Post.all.to_xml, @response.body }
|
15
|
+
expect { assert_equal "application/xml", @response.content_type }
|
16
|
+
end
|
17
|
+
|
18
|
+
context "on GET to /posts with json" do
|
19
|
+
setup do
|
20
|
+
2.times { create_post }
|
21
|
+
get '/posts.json'
|
22
|
+
end
|
23
|
+
|
24
|
+
expect { assert_equal 200, @response.status }
|
25
|
+
expect { assert_equal Post.all.to_json, @response.body }
|
26
|
+
expect { assert_equal "application/json", @response.content_type }
|
27
|
+
end
|
28
|
+
|
29
|
+
context "on POST to /posts" do
|
30
|
+
setup do
|
31
|
+
Post.destroy_all
|
32
|
+
post '/posts.xml', :post => {:title => "whatever"}
|
33
|
+
end
|
34
|
+
|
35
|
+
expect { assert_equal 302, @response.status }
|
36
|
+
expect { assert_equal "/posts/#{Post.first.id}.xml", @response.location }
|
37
|
+
expect { assert_equal "whatever", Post.first.title }
|
38
|
+
expect { assert_equal "application/xml", @response.content_type }
|
39
|
+
end
|
40
|
+
|
41
|
+
context "on GET to /posts/id" do
|
42
|
+
setup do
|
43
|
+
@post = create_post
|
44
|
+
get "/posts/#{@post.id}.xml"
|
45
|
+
end
|
46
|
+
|
47
|
+
expect { assert_equal 200, @response.status }
|
48
|
+
expect { assert_equal @post.to_xml, @response.body }
|
49
|
+
expect { assert_equal "application/xml", @response.content_type }
|
50
|
+
end
|
51
|
+
|
52
|
+
context "on PUT to /posts/id" do
|
53
|
+
setup do
|
54
|
+
@post = create_post
|
55
|
+
put "/posts/#{@post.id}.xml", :post => {:title => "Changed!"}
|
56
|
+
end
|
57
|
+
|
58
|
+
expect { assert_equal 200, @response.status }
|
59
|
+
expect { assert_equal @post.reload.to_xml, @response.body }
|
60
|
+
expect { assert_equal "application/xml", @response.content_type }
|
61
|
+
|
62
|
+
should "update the post" do
|
63
|
+
assert_equal "Changed!", @post.reload.title
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "on DELETE to /posts/id" do
|
68
|
+
setup do
|
69
|
+
@post = create_post
|
70
|
+
delete "/posts/#{@post.id}.xml"
|
71
|
+
end
|
72
|
+
|
73
|
+
expect { assert_equal 200, @response.status }
|
74
|
+
expect { assert_equal "application/xml", @response.content_type }
|
75
|
+
|
76
|
+
should "destroy the post" do
|
77
|
+
assert_nil Post.find_by_id(@post)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'sinatra'
|
3
|
+
require 'classy_resources/active_record'
|
4
|
+
require 'activerecord'
|
5
|
+
|
6
|
+
ActiveRecord::Base.configurations = {'sqlite3' => {:adapter => 'sqlite3',
|
7
|
+
:database => ':memory:'}}
|
8
|
+
ActiveRecord::Base.establish_connection('sqlite3')
|
9
|
+
|
10
|
+
ActiveRecord::Base.logger = Logger.new(STDERR)
|
11
|
+
ActiveRecord::Base.logger.level = Logger::WARN
|
12
|
+
|
13
|
+
ActiveRecord::Schema.define(:version => 0) do
|
14
|
+
create_table :posts do |t|
|
15
|
+
t.string :title
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Post < ActiveRecord::Base
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
define_resource :posts, :collection => [:get, :post],
|
24
|
+
:member => [:get, :put, :delete],
|
25
|
+
:formats => [:xml, :json]
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'sinatra'
|
3
|
+
require 'classy_resources/sequel'
|
4
|
+
require 'sequel'
|
5
|
+
|
6
|
+
Sequel::Model.db = Sequel.sqlite
|
7
|
+
|
8
|
+
Sequel::Model.db.instance_eval do
|
9
|
+
create_table! :users do
|
10
|
+
primary_key :id
|
11
|
+
varchar :name
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class User < Sequel::Model(:users)
|
16
|
+
end
|
17
|
+
|
18
|
+
define_resource :users, :collection => [:get, :post],
|
19
|
+
:member => [:put, :delete, :get]
|
data/test/sequel_test.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
require 'sinatra'
|
3
|
+
require 'sinatra/test/unit'
|
4
|
+
require File.dirname(__FILE__) + '/fixtures/sequel_test_app'
|
5
|
+
require 'activesupport'
|
6
|
+
|
7
|
+
class Sequel::Model
|
8
|
+
def to_xml(opts={})
|
9
|
+
values.to_xml(opts)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class SequelTest < Test::Unit::TestCase
|
14
|
+
context "on GET to /users with xml" do
|
15
|
+
setup do
|
16
|
+
2.times { create_user }
|
17
|
+
get '/users.xml'
|
18
|
+
end
|
19
|
+
|
20
|
+
expect { assert_equal 200, @response.status }
|
21
|
+
expect { assert_equal User.all.to_xml, @response.body }
|
22
|
+
expect { assert_equal "application/xml", @response.content_type }
|
23
|
+
end
|
24
|
+
|
25
|
+
context "on POST to /users" do
|
26
|
+
setup do
|
27
|
+
User.destroy_all
|
28
|
+
post '/users.xml', :user => {:name => "whatever"}
|
29
|
+
end
|
30
|
+
|
31
|
+
expect { assert_equal 302, @response.status }
|
32
|
+
expect { assert_equal "/users/#{User.first.id}.xml", @response.location }
|
33
|
+
expect { assert_equal "whatever", User.first.name }
|
34
|
+
expect { assert_equal "application/xml", @response.content_type }
|
35
|
+
end
|
36
|
+
|
37
|
+
context "on GET to /users/id" do
|
38
|
+
setup do
|
39
|
+
@user = create_user
|
40
|
+
get "/users/#{@user.id}.xml"
|
41
|
+
end
|
42
|
+
|
43
|
+
expect { assert_equal 200, @response.status }
|
44
|
+
expect { assert_equal @user.to_xml, @response.body }
|
45
|
+
expect { assert_equal "application/xml", @response.content_type }
|
46
|
+
end
|
47
|
+
|
48
|
+
context "on PUT to /users/id" do
|
49
|
+
setup do
|
50
|
+
@user = create_user
|
51
|
+
put "/users/#{@user.id}.xml", :user => {:name => "Changed!"}
|
52
|
+
end
|
53
|
+
|
54
|
+
expect { assert_equal 200, @response.status }
|
55
|
+
expect { assert_equal @user.reload.to_xml, @response.body }
|
56
|
+
expect { assert_equal "application/xml", @response.content_type }
|
57
|
+
|
58
|
+
should "update the user" do
|
59
|
+
assert_equal "Changed!", @user.reload.name
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "on DELETE to /users/id" do
|
64
|
+
setup do
|
65
|
+
@user = create_user
|
66
|
+
delete "/users/#{@user.id}.xml"
|
67
|
+
end
|
68
|
+
|
69
|
+
expect { assert_equal 200, @response.status }
|
70
|
+
expect { assert_equal "application/xml", @response.content_type }
|
71
|
+
|
72
|
+
should "destroy the user" do
|
73
|
+
assert_nil User.find(:id => @user.id)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../lib/classy_resources'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'test/unit'
|
4
|
+
require 'context'
|
5
|
+
require 'zebra'
|
6
|
+
require 'mocha'
|
7
|
+
|
8
|
+
class Test::Unit::TestCase
|
9
|
+
protected
|
10
|
+
def create_post(opts = {})
|
11
|
+
Post.create!({}.merge(opts))
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_user(opts = {})
|
15
|
+
u = User.new({}.merge(opts))
|
16
|
+
u.save
|
17
|
+
u
|
18
|
+
end
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: giraffesoft-classy_resources
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James Golick
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-27 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: TODO
|
17
|
+
email: james@giraffesoft.ca
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- README.rdoc
|
26
|
+
- VERSION.yml
|
27
|
+
- lib/classy_resources
|
28
|
+
- lib/classy_resources/active_record.rb
|
29
|
+
- lib/classy_resources/mime_type.rb
|
30
|
+
- lib/classy_resources/sequel.rb
|
31
|
+
- lib/classy_resources.rb
|
32
|
+
- test/active_record_test.rb
|
33
|
+
- test/fixtures
|
34
|
+
- test/fixtures/active_record_test_app.rb
|
35
|
+
- test/fixtures/sequel_test_app.rb
|
36
|
+
- test/sequel_test.rb
|
37
|
+
- test/test_helper.rb
|
38
|
+
has_rdoc: false
|
39
|
+
homepage: http://github.com/giraffesoft/classy_resources
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.2.0
|
61
|
+
signing_key:
|
62
|
+
specification_version: 2
|
63
|
+
summary: TODO
|
64
|
+
test_files: []
|
65
|
+
|