giraffesoft-classy_resources 0.2.1 → 0.3.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/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