Nested Resources, Model Associations, and Seeding - The Rails Way
So, I wanted to write a post that covers some new ideas (nested resources) and some good ol' rails fundamentals (model associations). I will be using a very simple app to work through these ideas. The purpose is to practice our skills and not get bogged down in the content.
The app will be for a pickup football league and will keep track of the different teams and the players that play for the those teams. Our domain mapping looks like the following:
With our models planned out, let's jump into the code
1) Rails new PickUpFootball
2) Generate the models with the data we planned above
rails g model team name:string
rails g model players name:string team_id:integer
3) Go into the models and make the association
4) Go into the Seed file and populate the app with some data
["Giants", "Ravens", "Eagles", "Patriots"].each do |mascot|
Team.create( name: mascot)
team_one = Team.find_by_name("Giants")
team_one.players.create([{ name: 'Bill' }, { name: 'John' }, { name: 'Dave' }])
team_two = Team.find_by_name("Ravens")
team_two.players.create([{ name: 'Sam' }, { name: 'Chris' }, { name: 'Kyle' }])
team_three = Team.find_by_name("Eagles")
team_three.players.create([{ name: 'Neal' }, { name: 'Sean' }, { name: 'Kevin' }])
team_four = Team.find_by_name("Patriots")
team_four.players.create([{ name: 'Smith' }, { name: 'Spike' }, { name: 'Flannery' }])
***This code can totally be re-factored. I definitely didn't follow the DRY principle.
***Also, if you mistype and need to delete a model you'll first need to run rake db:rollback and then rails destroy model "name of model".
6) We want to a listing of the Teams and Players so we will need to generate a controller for each as well as corresponding views.
rails g controller teams index show
rails g controller players show
7) Now, in order to see our views, we will need to create our routes. This is where we can implement our first objective, nested routes. Typically, we would just put in the code
But, it would be nice if our urls were more descriptive and instead of passing ids it passed what our users would naturally type. Enter nested resources. Since we have the association of players belong to one specific team we would write our routes as followed:
8) Now we need to launch our server and open our homepage, which I set as teams/index, which I set with the following code
9) Now we need to show a listing of our teams
This is what I placed in my team's controller:
This is what I placed in my index view:
<% @teams.each do |team| %>
<li><%= link_to team.name, team %></li>
We should now have a listing of teams that are hyperlinked that take us to show page for a particular team.
10) We now need to list the players for a particular team. To do this we need to scope the particular team we are dealing with. In our show method we need to define @team in such a way that it grabs the id from the url it is being passed.
@team = Team.find(params[:id])
Then we need to cycle through our players and list them out for that team. I placed this in my team's show page.
<% @team.players.each do |player| %>
<li> <%= link_to player.name, team_player_url(@team, player) %></li>
It is important to realize how nested routes change our routes. Do a quick rake routes to see how they have changed. Look at the above route. For team_player_url we have to pass it first the team's id (we defined @team as being specifically that in our controller) and then the player's id. Before nested routes we simply passed 1 id in our parameters, but now we need to pass 2 (team & player).
11) This should link us to a player's individual page where we could potential put a bunch of information like stats, contact info etc. For our purposes we will just list out that players name. Since we have scoped team and players we can place this code in our views
<h1><%= @player.name %></h1>
If you refresh your browser now you will get a NoMethodError. We need to go into our player's controller give our show method some much needed information.
@team = Team.find(params[:team_id])
@player = @team.players.find_by_id(params[:id])
To make this a bit more fancy and for practice we'll place a before_filter that way we do not have to keep repeating our @team code. So, with some refactoring our new player's controller looks like this
before_filter :require_team
@team = Team.find(params[:team_id])
@player = @team.players.find_by_id(params[:id])
Yay, congrats. We now have completed a small app that allowed us to practice nested resources, model associations, seeding data, and before filters.