Putting REST on Rails
by Dan Kubb
|
Pages: 1, 2, 3, 4, 5, 6
The by_id Resource
The :by_id resource is a single book identified by its database ID. We want to be able to view a book with the GET method, change it with PUT, and delete it with the DELETE method. Let's add a test to test/functional/book_controller_test.rb that tests our expectations:
def test_options_by_id
options :by_id, :id => books(:http_book).id
assert_response HTTP::Status::NO_CONTENT
assert_no_entity
assert_allowed_methods :get, :put, :delete
end
This tests that when OPTIONS is performed on :by_id, a 204 No Content status code is returned. Also the Allow header should identify GET, PUT, and DELETE as allowed methods.
rake test:functionals should fail because there is no for :by_id resource yet.
The process we use is very similar to the :collection resource—we'll stub out the :by_id resource to make the functional tests pass for now. Add the following to app/controllers/book_controller.rb:
resource :by_id do |r|
r.put do
end
r.delete do
end
end
Again, because the RESTful Rails plugin handles requests just like normal Rails, we don't have to do anything to handle GET.
rake test:functionals should now pass.
View a Book
We need to test what will happen when a GET on :by_id is performed. In our case we want the by_id template to be loaded and display a single book from the database in XML format. Let's test this by adding the following to test/functional/book_controller_test.rb:
def test_get_by_id
get :by_id, :id => books(:http_book).id
assert_response HTTP::Status::OK
assert_template 'by_id'
assert_equal 'application/xml', @response.content_type
with_options :tag => 'book' do |test|
test.assert_tag :children => { :count => 1, :only => { :tag => 'id', :content => books(:http_book).id.to_s } }
test.assert_tag :children => { :count => 1, :only => { :tag => 'title', :content => books(:http_book).title } }
test.assert_tag :children => { :count => 1, :only => { :tag => 'description', :content => books(:http_book).description } }
end
end
This tests that when a GET on :by_id is performed, a 200 OK status code should be returned, and that the by_id template should be used to render the results in the specified XML format.
rake test:functionals should fail because we haven't created the XML template yet.
Before creating the template we first need a way to retrieve the book from the database using the id supplied in the URI. Add one line to the beginning of the :by_id resource in app/controllers/book_controller.rb so it becomes:
resource :by_id do |r|
conditions << @book = Book.find(params[:id])
r.put do
end
r.delete do
end
end
Next create the template app/views/book/by_id.rxml and make it look like:
xml.instruct!
@book.to_xml
rake test:functionals should now pass.
Change a Book
Now we want to be able to use PUT to change a book by its ID. We test this by adding the following to test/functional/book_controller_test.rb:
def test_put_by_id
changed_book = { :title => 'a different title', :description => 'a different description' }
id = books(:http_book).id
put :by_id, :id => id, :book => changed_book
assert_response HTTP::Status::NO_CONTENT
assert_no_entity
book = Book.find id
assert_kind_of Book, book
assert_equal changed_book[:title], book.title
assert_equal changed_book[:description], book.description
end
This tests that when PUT is performed on :by_id, a 204 No Content status code is returned. The test checks the database to ensure the record was changed successfully.
rake test:functionals should fail because we haven't added the :by_id PUT handler yet.
To handle PUT requests we update the r.put handler in app/controllers/book_controller.rb to:
r.put do
@book.attributes = params[:book]
if @book.save
render_put_success
end
end
We use the RESTful Rails render_put_success method which returns a 204 No Content status.
rake test:functionals should now pass.
Delete a Book
Finally, we want to use DELETE to remove a book by its ID.
The test we add to test/functional/book_controller_test.rb looks like this:
def test_delete_by_id
id = books(:http_book).id
delete :by_id, :id => id
assert_response HTTP::Status::NO_CONTENT
assert_no_entity
assert_raise(ActiveRecord::RecordNotFound) { Book.find id }
end
This tests that when DELETE is performed on :by_id, a 204 No Content status code is returned. The test checks the database to ensure the record was removed successfully.
rake test:functionals should fail because we haven't added the :by_id DELETE handler yet.
To handle DELETE requests we update the r.delete handler in app/controllers/book_controller.rb to:
r.delete do
if @book.destroy
render_delete_success :id => nil
end
end
This simply calls @book.destroy and does a render_delete_success when successful.
rake test:functionals should now pass.