Putting REST on Rails
by Dan Kubb
|
Pages: 1, 2, 3, 4, 5, 6
View the List of Books
Even though GET requests are handled automatically we still need to test what will happen when a GET on :collection is done. In our case we want the collection template to be loaded and display a list of all the books in the database in XML format. Let's add a test to test/functional/book_controller_test.rb that ensures this:
def test_get_collection
get :collection
assert_response HTTP::Status::OK
assert_template 'collection'
assert_equal 'application/xml', @response.content_type
with_options :tag => 'books' do |test|
test.assert_tag :children => { :count => 1, :only => { :tag => 'title', :content => 'Books' } }
test.assert_tag :children => { :count => 1, :only => { :tag => 'book', :content => '' } }
end
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 => 'link', :attributes => { :href => @controller.url_for(:action => 'by_id', :id => books(:http_book)) } } }
end
end
This tests that when a GET :collection is performed, a 200 OK status code should be returned. Also the collection 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 need a way to retrieve a list of all the books from the database. Add one line to the beginning of the :collection resource in app/controllers/book_controller.rb so it becomes:
resource :collection do |r|
conditions << @books = Book.find(:all)
r.post do
end
end
Note that the assignment of @book to conditions is a convention in RESTful Rails. It allows conditional request handling which may be described in more detail at a later time.
Next create the template app/views/book/collection.rxml and make it look like:
xml.instruct!
xml.books do
xml.title 'Books'
@books.each do |book|
xml.book do
xml.id book.id
xml.title book.title
xml.link :href => url_for(:only_path => false, :action => 'by_id', :id => book)
end
end
end
rake test:functionals should now pass.
Add to the List of Books
So what exactly should we do when handling POST in :collection? Well, its job will be to add a new book to the database as well as redirect the client to the new location of the book. With these expectations we add the following to test/functional/book_controller_test.rb:
def test_post_collection
new_book = { :title => 'test' }
post :collection, :book => new_book
assert_response HTTP::Status::CREATED
id = Book.maximum('id')
assert_location :action => 'by_id', :id => id
book = Book.find(id)
assert_kind_of Book, book
assert_equal new_book[:title], book.title
assert_equal new_book[:description], book.description
end
This tests that when a POST is performed on :collection, a 201 Created status code should be returned. Also the Location header should be set properly and a database check to should be made to ensure the new book was actually created.
rake test:functionals should fail because we haven't added the :collection POST handler yet.
To handle POST requests, we update the r.post handler in app/controllers/book_controller.rb to:
r.post do
@book = Book.new params[:book]
if @book.save
render_post_success :action => 'by_id', :id => @book
end
end
In this example we use the RESTful Rails render_post_success method which returns a 201 Created status, and sets the Location header to the URI of the new book.
rake test:functionals should now pass.
We're done with the :collection resource now. It handles GET, HEAD, OPTIONS, and POST requests in about nine lines of code. Next we'll move on to the :by_id resource which represents a book using its ID in the database.