giraffesoft-classy_resources 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :patch: 1
2
+ :patch: 0
3
3
  :major: 0
4
- :minor: 2
4
+ :minor: 3
@@ -12,25 +12,8 @@ module ClassyResources
12
12
  ResourceBuilder.new(self, *options)
13
13
  end
14
14
 
15
- def load_collection(resource, parent = nil)
16
- parent.nil? ? load_shallow_collection(resource) : load_nested_collection(resource, parent)
17
- end
18
-
19
- def create_object(resource, object_params, parent = nil)
20
- parent.nil? ? create_shallow_object(resource, object_params) : create_nested_object(resource, object_params, parent)
21
- end
22
-
23
- def load_parent_object(parent)
24
- load_object(parent, params[parent_id_name(parent)])
25
- end
26
-
27
- def parent_id_name(parent)
28
- :"#{parent.to_s.singularize}_id"
29
- end
30
-
31
- def collection_url_for(resource, format, parent = nil)
32
- parent = parent.nil? ? "" : "/#{parent}/:#{parent_id_name(parent)}"
33
- [parent, "/#{resource}.#{format}"].join
15
+ def collection_url_for(resource, format)
16
+ "/#{resource}.#{format}"
34
17
  end
35
18
 
36
19
  def object_route_url(resource, format)
@@ -75,22 +58,27 @@ module ClassyResources
75
58
 
76
59
  protected
77
60
  def define_collection_get(resource, format)
78
- parent = options[:parent]
79
- get collection_url_for(resource, format, parent) do
61
+ get collection_url_for(resource, format) do
80
62
  set_content_type(format)
81
- serialize(load_collection(resource, parent), format)
63
+ serialize(load_collection(resource), format)
82
64
  end
83
65
  end
84
66
 
85
67
  def define_collection_post(resource, format)
86
- parent = options[:parent]
87
- post collection_url_for(resource, format, parent) do
68
+ post collection_url_for(resource, format) do
88
69
  set_content_type(format)
89
- object = create_object(resource, params[resource.to_s.singularize] || {}, parent)
70
+ object = build_object(resource, params[resource.to_s.singularize] || {})
90
71
 
91
- response['location'] = object_url_for(resource, format, object)
92
- response.status = 201
93
- serialize(object, format)
72
+ if object.valid?
73
+ object.save
74
+
75
+ response['location'] = object_url_for(resource, format, object)
76
+ response.status = 201
77
+ serialize(object, format)
78
+ else
79
+ response.status = 422
80
+ serialize(object.errors, format)
81
+ end
94
82
  end
95
83
  end
96
84
 
@@ -107,7 +95,14 @@ module ClassyResources
107
95
  set_content_type(format)
108
96
  object = load_object(resource, params[:id])
109
97
  update_object(object, params[resource.to_s.singularize])
110
- serialize(object, format)
98
+
99
+ if object.valid?
100
+ object.save
101
+ serialize(object, format)
102
+ else
103
+ response.status = 422
104
+ serialize(object.errors, format)
105
+ end
111
106
  end
112
107
  end
113
108
 
@@ -1,19 +1,11 @@
1
1
  module ClassyResources
2
2
  module ActiveRecord
3
- def load_shallow_collection(resource)
3
+ def load_collection(resource)
4
4
  class_for(resource).all
5
5
  end
6
6
 
7
- def load_nested_collection(resource, parent)
8
- load_parent_object(parent).send(resource)
9
- end
10
-
11
- def create_shallow_object(resource, params)
12
- class_for(resource).create!(params)
13
- end
14
-
15
- def create_nested_object(resource, params, parent)
16
- load_parent_object(parent).send(resource).create!(params)
7
+ def build_object(resource, object_params)
8
+ class_for(resource).new(object_params)
17
9
  end
18
10
 
19
11
  def load_object(resource, id)
@@ -21,12 +13,16 @@ module ClassyResources
21
13
  end
22
14
 
23
15
  def update_object(object, params)
24
- object.update_attributes(params)
16
+ object.attributes = params
25
17
  end
26
18
 
27
19
  def destroy_object(object)
28
20
  object.destroy
29
21
  end
22
+
23
+ error ::ActiveRecord::RecordNotFound do
24
+ response.status = 404
25
+ end
30
26
  end
31
27
  end
32
28
 
@@ -1,33 +1,34 @@
1
+ require 'classy_resources/sequel_errors_to_xml'
2
+
1
3
  module ClassyResources
2
4
  module Sequel
3
- def load_shallow_collection(resource)
4
- class_for(resource).all
5
- end
6
-
7
- def load_nested_collection(resource, parent)
8
- load_parent_object(parent).send(resource)
9
- end
5
+ class ResourceNotFound < RuntimeError; end
10
6
 
11
- def create_shallow_object(resource, object_params)
12
- class_for(resource).create(object_params)
7
+ def load_collection(resource)
8
+ class_for(resource).all
13
9
  end
14
10
 
15
- def create_nested_object(resource, object_params, parent)
16
- c = class_for(resource).new(object_params)
17
- load_parent_object(parent).send(:"add_#{resource.to_s.singularize}", c)
11
+ def build_object(resource, object_params)
12
+ class_for(resource).new(object_params)
18
13
  end
19
14
 
20
15
  def load_object(resource, id)
21
- class_for(resource).find(:id => id)
16
+ r = class_for(resource).find(:id => id)
17
+ raise ResourceNotFound if r.nil?
18
+ r
22
19
  end
23
20
 
24
21
  def update_object(object, params)
25
- object.update(params)
22
+ object.set params
26
23
  end
27
24
 
28
25
  def destroy_object(object)
29
26
  object.destroy
30
27
  end
28
+
29
+ error ResourceNotFound do
30
+ response.status = 404
31
+ end
31
32
  end
32
33
  end
33
34
 
@@ -0,0 +1,19 @@
1
+ # stolen from active record
2
+ #
3
+ module ClassyResources
4
+ module SequelErrorsToXml
5
+ def to_xml(options={})
6
+ options[:root] ||= "errors"
7
+ options[:indent] ||= 2
8
+ options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
9
+
10
+ options[:builder].instruct! unless options.delete(:skip_instruct)
11
+ options[:builder].errors do |e|
12
+ full_messages.each { |msg| e.error(msg) }
13
+ end
14
+ end
15
+ end
16
+
17
+ ::Sequel::Model::Validation::Errors.send(:include, SequelErrorsToXml)
18
+ end
19
+
@@ -39,6 +39,18 @@ class ActiveRecordTest < Test::Unit::TestCase
39
39
  expect { assert_equal Post.first.to_xml, @response.body }
40
40
  end
41
41
 
42
+ context "on POST to /posts with invalid params" do
43
+ setup do
44
+ Post.destroy_all
45
+ post '/posts.xml', :post => {}
46
+ end
47
+
48
+ expect { assert_equal 422, @response.status }
49
+ expect { assert_equal "application/xml", @response.content_type }
50
+ expect { assert_equal Post.create.errors.to_xml, @response.body }
51
+ expect { assert_equal 0, Post.count }
52
+ end
53
+
42
54
  context "on GET to /posts/id" do
43
55
  setup do
44
56
  @post = create_post
@@ -50,6 +62,16 @@ class ActiveRecordTest < Test::Unit::TestCase
50
62
  expect { assert_equal "application/xml", @response.content_type }
51
63
  end
52
64
 
65
+ context "on GET to /posts/id with a missing post" do
66
+ setup do
67
+ get "/posts/doesntexist.xml"
68
+ end
69
+
70
+ expect { assert_equal 404, @response.status }
71
+ expect { assert @response.body.empty? }
72
+ expect { assert_equal "application/xml", @response.content_type }
73
+ end
74
+
53
75
  context "on PUT to /posts/id" do
54
76
  setup do
55
77
  @post = create_post
@@ -65,72 +87,80 @@ class ActiveRecordTest < Test::Unit::TestCase
65
87
  end
66
88
  end
67
89
 
68
- context "on DELETE to /posts/id" do
90
+ context "on PUT to /posts/id with invalid params" do
69
91
  setup do
70
92
  @post = create_post
71
- delete "/posts/#{@post.id}.xml"
93
+ put "/posts/#{@post.id}.xml", :post => {:title => ""}
72
94
  end
73
95
 
74
- expect { assert_equal 200, @response.status }
96
+ expect { assert_equal 422, @response.status }
75
97
  expect { assert_equal "application/xml", @response.content_type }
98
+ expect { assert_equal Post.create.errors.to_xml, @response.body }
76
99
 
77
- should "destroy the post" do
78
- assert_nil Post.find_by_id(@post)
100
+ should "not update the post" do
101
+ assert_not_equal "", @post.reload.title
79
102
  end
80
103
  end
81
104
 
82
- context "on GET to /posts/id/comments" do
105
+ context "on PUT to /posts/id with a missing post" do
106
+ setup do
107
+ put "/posts/missing.xml", :post => {:title => "Changed!"}
108
+ end
109
+
110
+ expect { assert_equal 404, @response.status }
111
+ expect { assert @response.body.empty? }
112
+ expect { assert_equal "application/xml", @response.content_type }
113
+ end
114
+
115
+ context "on DELETE to /posts/id" do
83
116
  setup do
84
117
  @post = create_post
85
- 2.times { @post.comments.create!(hash_for_comment) }
86
- 2.times { create_comment }
87
- get "/posts/#{@post.id}/comments.xml"
118
+ delete "/posts/#{@post.id}.xml"
88
119
  end
89
120
 
90
121
  expect { assert_equal 200, @response.status }
91
122
  expect { assert_equal "application/xml", @response.content_type }
92
- expect { assert_equal @post.comments.to_xml, @response.body }
123
+
124
+ should "destroy the post" do
125
+ assert_nil Post.find_by_id(@post)
126
+ end
93
127
  end
94
128
 
95
- context "on POST to /posts/id/comments" do
129
+ context "on DELETE to /posts/id with a missing post" do
96
130
  setup do
97
- Comment.destroy_all
98
- @post = create_post
99
- post "/posts/#{@post.id}/comments.xml", :comment => hash_for_comment
131
+ delete "/posts/missing.xml"
100
132
  end
101
133
 
102
- expect { assert_equal 201, @response.status }
134
+ expect { assert_equal 404, @response.status }
103
135
  expect { assert_equal "application/xml", @response.content_type }
104
- expect { assert_equal "/comments/#{@post.comments.reload.first.id}.xml", @response.location }
105
- expect { assert_equal 1, @post.comments.reload.count }
106
- expect { assert_equal Comment.first.to_xml, @response.body }
136
+ expect { assert @response.body.empty? }
107
137
  end
108
138
 
109
- context "on POST to /posts/id/comments with a JSON post body" do
139
+ context "on POST to /comments with a JSON post body" do
110
140
  setup do
111
- @post = create_post
112
- post "/posts/#{@post.id}/comments.xml", {:comment => hash_for_comment(:author => 'james')}.to_json,
113
- :content_type => 'application/json'
141
+ Comment.destroy_all
142
+ post "/comments.xml", {:comment => hash_for_comment(:author => 'james')}.to_json,
143
+ :content_type => 'application/json'
114
144
  end
115
145
 
116
146
  expect { assert_equal 201, @response.status }
117
147
  expect { assert_equal "application/xml", @response.content_type }
118
- expect { assert_equal "/comments/#{@post.comments.reload.first.id}.xml", @response.location }
119
- expect { assert_equal 1, @post.comments.reload.count }
120
- expect { assert_equal 'james', @post.comments.first.author }
148
+ expect { assert_equal "/comments/#{Comment.first.id}.xml", @response.location }
149
+ expect { assert_equal 1, Comment.count }
150
+ expect { assert_equal 'james', Comment.first.author }
121
151
  end
122
152
 
123
153
  context "on POST to /posts/id/comments with a XML post body" do
124
154
  setup do
125
- @post = create_post
126
- post "/posts/#{@post.id}/comments.xml", Comment.new(:author => 'james').to_xml,
127
- :content_type => 'application/xml'
155
+ Comment.destroy_all
156
+ post "/comments.xml", Comment.new(:author => 'james').to_xml,
157
+ :content_type => 'application/xml'
128
158
  end
129
159
 
130
160
  expect { assert_equal 201, @response.status }
131
161
  expect { assert_equal "application/xml", @response.content_type }
132
- expect { assert_equal "/comments/#{@post.comments.reload.first.id}.xml", @response.location }
133
- expect { assert_equal 1, @post.comments.reload.count }
134
- expect { assert_equal 'james', @post.comments.first.author }
162
+ expect { assert_equal "/comments/#{Comment.first.id}.xml", @response.location }
163
+ expect { assert_equal 1, Comment.count }
164
+ expect { assert_equal 'james', Comment.first.author }
135
165
  end
136
166
  end
@@ -1,7 +1,7 @@
1
1
  require 'rubygems'
2
+ require 'activerecord'
2
3
  require 'sinatra'
3
4
  require 'classy_resources/active_record'
4
- require 'activerecord'
5
5
 
6
6
  ActiveRecord::Base.configurations = {'sqlite3' => {:adapter => 'sqlite3',
7
7
  :database => ':memory:'}}
@@ -23,19 +23,20 @@ end
23
23
 
24
24
  class Post < ActiveRecord::Base
25
25
  has_many :comments
26
+ validates_presence_of :title
26
27
  end
27
28
 
28
29
  class Comment < ActiveRecord::Base
29
30
  belongs_to :post
30
31
  end
31
32
 
33
+ set :raise_errors, false
32
34
 
33
35
  define_resource :posts, :collection => [:get, :post],
34
36
  :member => [:get, :put, :delete],
35
37
  :formats => [:xml, :json]
36
38
 
37
- define_resource :comments, :collection => [:get, :post],
38
- :parent => :posts
39
+ define_resource :comments, :collection => [:get, :post]
39
40
 
40
41
  use ClassyResources::PostBodyParams
41
42
 
@@ -1,7 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'sinatra'
3
- require 'classy_resources/sequel'
4
3
  require 'sequel'
4
+ require 'classy_resources/sequel'
5
5
 
6
6
  Sequel::Model.db = Sequel.sqlite
7
7
 
@@ -20,6 +20,7 @@ end
20
20
 
21
21
  class User < Sequel::Model(:users)
22
22
  one_to_many :subscriptions
23
+ validates_presence_of :name
23
24
  end
24
25
 
25
26
  class Subscription < Sequel::Model(:subscriptions)
@@ -27,8 +28,9 @@ class Subscription < Sequel::Model(:subscriptions)
27
28
  validates_presence_of :user_id
28
29
  end
29
30
 
31
+ set :raise_errors, false
32
+
30
33
  define_resource :users, :collection => [:get, :post],
31
34
  :member => [:put, :delete, :get]
32
35
 
33
- define_resource :subscriptions, :collection => [:get, :post],
34
- :parent => :users
36
+ define_resource :subscriptions, :collection => [:get, :post]
@@ -0,0 +1,25 @@
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 SequelErrorsToXmlTest < Test::Unit::TestCase
8
+ context "serializing a sequel errors object" do
9
+ before do
10
+ @subscription = Subscription.new
11
+ @subscription.valid?
12
+ end
13
+
14
+ should "serialize in a format that is active resource compatible" do
15
+ str =<<-__END__
16
+ <?xml version="1.0" encoding="UTF-8"?>
17
+ <errors>
18
+ <error>user_id is not present</error>
19
+ </errors>
20
+ __END__
21
+ assert_equal str, @subscription.errors.to_xml
22
+ end
23
+ end
24
+ end
25
+
data/test/sequel_test.rb CHANGED
@@ -45,6 +45,16 @@ class SequelTest < Test::Unit::TestCase
45
45
  expect { assert_equal "application/xml", @response.content_type }
46
46
  end
47
47
 
48
+ context "on GET to /users/id with a missing user" do
49
+ setup do
50
+ get "/users/missing.xml"
51
+ end
52
+
53
+ expect { assert_equal 404, @response.status }
54
+ expect { assert @response.body.empty? }
55
+ expect { assert_equal "application/xml", @response.content_type }
56
+ end
57
+
48
58
  context "on PUT to /users/id" do
49
59
  setup do
50
60
  @user = create_user
@@ -60,51 +70,68 @@ class SequelTest < Test::Unit::TestCase
60
70
  end
61
71
  end
62
72
 
63
- context "on DELETE to /users/id" do
73
+ context "on PUT to /users/id with invalid params" do
64
74
  setup do
65
75
  @user = create_user
66
- delete "/users/#{@user.id}.xml"
76
+ put "/users/#{@user.id}.xml", :user => {:name => ""}
77
+ @invalid_user = User.new
78
+ @invalid_user.valid?
67
79
  end
68
80
 
69
- expect { assert_equal 200, @response.status }
81
+ expect { assert_equal 422, @response.status }
82
+ expect { assert_equal @invalid_user.errors.to_xml, @response.body }
70
83
  expect { assert_equal "application/xml", @response.content_type }
71
84
 
72
- should "destroy the user" do
73
- assert_nil User.find(:id => @user.id)
85
+ should "not update the user" do
86
+ assert_not_equal "Changed!", @user.reload.name
74
87
  end
75
88
  end
76
89
 
77
- context "on GET to /users/id/comments" do
90
+ context "on PUT to /users/id with a missing user" do
91
+ setup do
92
+ put "/users/missing.xml", :user => {:name => "Changed!"}
93
+ end
94
+
95
+ expect { assert_equal 404, @response.status }
96
+ expect { assert @response.body.empty? }
97
+ expect { assert_equal "application/xml", @response.content_type }
98
+ end
99
+
100
+ context "on DELETE to /users/id" do
78
101
  setup do
79
102
  @user = create_user
80
- 2.times { @user.add_subscription(Subscription.new(hash_for_subscription)) }
81
- 2.times { create_subscription(:user_id => 9) }
82
- get "/users/#{@user.id}/subscriptions.xml"
103
+ delete "/users/#{@user.id}.xml"
83
104
  end
84
105
 
85
106
  expect { assert_equal 200, @response.status }
86
107
  expect { assert_equal "application/xml", @response.content_type }
87
- expect { assert_equal @user.subscriptions.to_xml, @response.body }
108
+
109
+ should "destroy the user" do
110
+ assert_nil User.find(:id => @user.id)
111
+ end
88
112
  end
89
113
 
90
- context "on POST to /users/id/subscriptions" do
114
+ context "on DELETE to /users/id with a missing user" do
91
115
  setup do
92
- @user = create_user
93
- post "/users/#{@user.id}/subscriptions.xml", :subscription => hash_for_subscription
116
+ delete "/users/missing.xml"
94
117
  end
95
118
 
96
- expect { assert_equal 201, @response.status }
119
+ expect { assert_equal 404, @response.status }
97
120
  expect { assert_equal "application/xml", @response.content_type }
98
- expect { assert_equal "/subscriptions/#{@user.reload.subscriptions.first.id}.xml", @response.location }
99
- expect { assert_equal 1, @user.reload.subscriptions.length }
121
+ expect { assert @response.body.empty? }
100
122
  end
101
123
 
102
- context "on POST to /users/id/subscriptions with no params" do
103
- should "not raise" do
104
- @user = create_user
105
- assert_nothing_raised {
106
- post "/users/#{@user.id}/subscriptions.xml", :subscription => {}
107
- }
124
+ context "on POST to /subscriptions with invalid params" do
125
+ setup do
126
+ Subscription.destroy_all
127
+ @subscription = Subscription.new
128
+ @subscription.valid?
129
+ post "/subscriptions.xml", :subscription => {}
108
130
  end
131
+
132
+ expect { assert_equal 422, @response.status }
133
+ expect { assert_equal "application/xml", @response.content_type }
134
+ expect { assert_equal 0, Subscription.count }
135
+ expect { assert_equal @subscription.errors.to_xml, @response.body }
109
136
  end
110
137
  end
data/test/test_helper.rb CHANGED
@@ -8,7 +8,7 @@ require 'mocha'
8
8
  class Test::Unit::TestCase
9
9
  protected
10
10
  def create_post(opts = {})
11
- Post.create!({}.merge(opts))
11
+ Post.create!({:title => 'awesome'}.merge(opts))
12
12
  end
13
13
 
14
14
  def hash_for_comment(opts = {})
@@ -20,7 +20,7 @@ class Test::Unit::TestCase
20
20
  end
21
21
 
22
22
  def create_user(opts = {})
23
- u = User.new({}.merge(opts))
23
+ u = User.new({:name => 'james'}.merge(opts))
24
24
  u.save
25
25
  u
26
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: giraffesoft-classy_resources
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Golick
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-01-29 00:00:00 -08:00
12
+ date: 2009-01-30 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -30,11 +30,13 @@ files:
30
30
  - lib/classy_resources/post_body_param_parsing.rb
31
31
  - lib/classy_resources/post_body_params.rb
32
32
  - lib/classy_resources/sequel.rb
33
+ - lib/classy_resources/sequel_errors_to_xml.rb
33
34
  - lib/classy_resources.rb
34
35
  - test/active_record_test.rb
35
36
  - test/fixtures
36
37
  - test/fixtures/active_record_test_app.rb
37
38
  - test/fixtures/sequel_test_app.rb
39
+ - test/sequel_errors_to_xml_test.rb
38
40
  - test/sequel_test.rb
39
41
  - test/test_helper.rb
40
42
  has_rdoc: false