XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement

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.

Pages: 1, 2, 3, 4, 5, 6

Next Pagearrow