Testing Nested RESTful Resources in Rails

Let’s scaffold some nested RESTful resources in Rails.
rails --database sqlite3 sandbox
script/generate scaffold_resource Artist name:string
script/generate scaffold_resource Song title:string artist_id:integer

… and see if we have a green build.

rake db:migrate
rake

So far so good, including the 14 functional tests that came with the scaffold.

We want Song to be associated to Artist as a one-to-many relationship and we want our application to reflect that in a RESTful way, so that we can access an artist’s songs as http://expample.com/artists/11/songs/4. In config/routes.rb, we replace the two lines:

map.resources :songs
map.resources :artists

with

map.resources :artists do |artists|
artists.resources :songs
end

We need to tell Song that it belongs_to :artist and Artist that it has_many :songs. The SongsController needs to be aware of the Artist associated with the songs:

class SongsController < ApplicationController

before_filter(:capture_artist)

def index
@songs = @artist.songs
respond_to do |format| format.html end
end

[...]

def create
@song = @artist.songs.create(params[:song])

respond_to do |format|
if @song.valid?
flash[:notice] = 'Song was successfully created.'
format.html { redirect_to song_url(@artist, @song) }
else
format.html { render :action => "new" }
end
end
end

private

def capture_artist
@artist = Artist.find(params[:artist_id])
end
end

The song_url methods in the SongsController and the Views in app/views/songs also need to be updated to reflect the relationship between the Artist and Song resources, as song_url(@artist, @song), etc.

By now it would make perfect sense to delete the following two lines from config/routes.rb, as they don’t any more make much sense as far as the context of our resources goes:

map.connect ':controller/:action/:id.:format'
map.connect ':controller/:action/:id'

At this point we have managed to break the build, because the tests in SongsControllerTest do not match the newly defined routes. Testing the new routing map is relatively easy and can be achieved as:

def test_should_route_songs_of_artist
options = {:controller => 'songs', :action => 'index', :artist_id => "2"}
assert_routing('artists/2/songs', options)
end

Then, the Controller tests must conform to the amended routes.

def test_should_get_new
get :new, :artist_id => 1
assert_response :success
end

My complete test/functional/songs_controller_test.rb looks like:

require File.dirname(__FILE__) + '/../test_helper'
require 'songs_controller'

class SongsController; def rescue_action(e) raise e end; end

class SongsControllerTest < Test::Unit::TestCase

def setup
@controller = SongsController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
Artist.create(:name => 'test')
end

def test_should_route_songs_of_artist
options = { :controller => 'songs',
:action => 'index',
:artist_id => '2' }
assert_routing('artists/2/songs', options)
end

def test_should_route_song_of_artist
options = { :controller => 'songs',
:action => 'show',
:artist_id => '2',
:id => '1' }
assert_routing('artists/2/songs/1', options)
end

def test_should_get_new
get :new, :artist_id => 1
assert_response :success
end

def test_should_show_song
Song.create(:title => 'test')
get :show, :artist_id => 1, :id => 1
assert_response :success
end

def test_should_get_edit
Song.create(:title => 'test')
get :edit, :artist_id => 1, :id => 1
assert_response :success
end

def test_should_destroy_song
Song.create(:title => 'test')
delete :destroy, :artist_id => 1, :id => 1
assert_equal 0, Song.count
end

def test_should_redirect_to_songs_list_after_destroy
Song.create(:title => 'test')
delete :destroy, :artist_id => 1, :id => 1
assert_redirected_to songs_path
end

def test_should_update_song
Song.create(:title => 'test')
put :update, :artist_id => 1, :id => 1, :song => { }
assert_redirected_to song_path(assigns(:artist), assigns(:song))
end

def test_should_create_song
post :create, :artist_id => '1', :song => { }
assert_equal 1, Song.count
end

def test_should_show_song_after_create
post :create, :artist_id => '1', :song => { }
assert_redirected_to song_path(assigns(:artist), assigns(:song))
end

def teardown
Artist.find(:all).each do |a| a.destroy end
Song.find(:all).each do |s| s.destroy end
end
end

As the code stands now, and if you have removed all traces of fixtures from your tests, like I have (I usually avoid Rails fixtures, but that’s another story…), the build should be happy again.

你可能感兴趣的:(html,Flash,Access,Rails)