Learning Ruby on Rails- Drop Down Lists in One to Many Relationships

If you are just looking for some help without reading my rambling post……
the short answer is, collection_select – something like this:

<%= collection_select(:city, :country_id, @countries, :id, :country_name, {:prompt => false}) %>

not f.select as I did the first time round

<%= f.select('country_id', @countries.collect {|r| [h(r.country_name), r.id] }, { :include_blank => false }) %>

🙁


One of the first things I always like to know when starting out on a new language/framework is….how easy is it to whip up a webpage for some basic CRUD work. With Ruby, it really is easy. I think that’s its biggest draw-card – that old generate/scaffold routine that you see in the viral video on how to build a blog in ten minutes. That just sold me I tell you. It isn’t like we don’t all already have our own cobbled together code generators or ORMs in the languages we already code in, but with Ruby, its already there for you. You don’t have to port to it. Great stuff.

The only thing is this: I like to one louder before I dive in headfirst. I like to know how easy it is to set up my CRUD page for a table that has a foreign key. Oooooh, now were talking. Because you know, CRUD for basic lookup tables like Gender or Age Group or Country or whatever where you only ever have an ID field and a Name field is pretty much a non event. So often, these are simply lookup tables that really don’t even need a front end – they contain such well defined base static data that you don’t even need to be able to maintain them. For example, Gender. I mean how often are we realistically going to edit that puppy? Practically never. Maybe you have a special case in your app and you need Male, Female and Unknown or Hermaphrodite. But really, it is so few and far between that we can pretty much ignore it by and large. So really, the advantage given to me by scaffold for creating these CRUDs is negligible. What I’m really interested in is the sort of secondary lookup tables. The ones that reference our base tables. You know, City (which would probably contain a Country_ID foreign key). Okay, maybe even that’s a bad example since there are a finite number of cities and you could arguable populate it upfront with no need for edits. But lets extrapolate, and say Address table. Now there’s a good one – probably going to have a City_ID foreign key in there, probably a Country_ID as well, though not strictly necessary since if we have City, then we can get Country, but maybe we’ve denormalised a little for whatever reason. I want to know how easy it is to set up a bunch of webpages for maintaining the Address table with nice drop downs for my foreign keys. This to me is where I like to jump in. Why do I like to know this? Simple – because this kind of easy table with only a few fields is indicative of how difficult it is going to be for me to create my main data table, which is usually going to have a bunch of foreign keys. That large bastard sitting right there in the middle of your data diagram – that swine is going to be the pain in the wotsit to code up, with all your sundry lookup fields and stuff. And really, I know you feel my pain because not only do you have to get the drop downs in there, but on the edit screen, you have to get the drop down to select the right value as well. And that’s a pain in the afore-mentioned wotsit.

And what really annoys me about so much of the literature out there is that this just never seems to be in the books. At least, not slap bang up front and in your face. Sure if you read through the entire tomb, you’ll learn it, but I never find nice concrete examples of this. Maybe I’m the only one who likes to figure this basic fundamental piece out first before I start working. But really, it is such an important piece of coding understanding to me, that I can never understand why books don’t have it front and centre. I’ve never written an app that doesn’t need this functionality over and over again.

So, anyway, having worked through a few Ruby on Rails tutorials, I think the framework is excellent. Doesn’t everyone? I mean it’s fast, it’s neat, it’s cool. But now I want my drop down lists!

For this example, I’m going to start with the idea of City and Country. The relationship is pretty straightforward right? A city is in a country. One Country can have many Cities. That’s my one to many relationship. So, we need to set up both City and Country:

ruby script/generate scaffold Country country_name:string

Great, Rails sets up the scaffolding for us for the Countries. Let’s do the same for Cities:

>ruby script/generate scaffold City city_name:string country_id:integer

Now migrate

rake db:migrate

Now we should have 2 sets of CRUD screens

Countries…..to wit:

Cities….to woo:

Adding countries is no problem – it is just a name after all. There’s only one box to fill in, the trick is adding cities, because cities belong to countries, and this is where I want my magic. Currently the data is listed as in the screenshot above – City Name and Country ID. So, let’s begin – we first set up relationships in our models go to app/models directory and in our city.rb model we want to add this:

class City < ActiveRecord::Base
	belongs_to :country
end

Then in country.rb, we set up the corollary

class Country < ActiveRecord::Base
	has_many :cities
end 

go here for some good reading on relationships in Rails.

Now this, I have to say is when the beauty of Ruby on Rails really shone through for me. Having set up our relationships there, we can very easily get our country name for our city – go to app/views/cities/ and open up index.html.erb. Find the following:

<% @cities.each do |city| %>
  <tr>
    <td><%=h city.city_name %></td>
    <td><%=h city.country_id %></td>

and change it to

<% @cities.each do |city| %>
  <tr>
    <td><%=h city.city_name %></td>
    <td><%=h city.country.country_name %></td>

Magically, when we refresh our cities listing page at localhost:3000/cities, we now get the country name listed:

Now we get into the meat and potatoes of this article – how to actually get the drop down going in the new and edit screens – your friend here is a little something called collection_select! There are two steps to getting this right – step one – you need to modify the cities controller to return the cities and step two – you need to modify the actual views to show the drop down using the collection_select helper. So into the controller first –
app/controlers/cities_controller.rb and find

  def new
    @city = City.new

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @city }
    end
  end

add in the countries by doing the following:

  def new
    @city = City.new
    @countries = Country.all
    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @city }
    end
  end

and then pop along to app/views/cities/new.html.erb and

    <%= f.label :country_id %><br />
    <%= f.text_field :country_id %>

needs a little change to this:

    <%= f.label :country_id %><br />
    <%= collection_select(:city, :country_id, @countries, :id, :country_name, {:prompt => false}) %> 

And what do we get for our few lines of code? Navigate to localhost:3000/cities/new and you are presented with:

Sweet! Go on, add a few cities in - it will just work. So what have we done? Well, forgive me if I get this wrong, because I'm not fluent in either Ruby or Rails or Activerecord, but the way I visualise it, in the cities controller we essentially exposed what amounts to a class of which contains all the countries. Then in our view, we basically said, use this class of countries and display them in a drop down - use the country_name field/property as our visible element and the id field/property as the drop down id. Then, when we hit save, grab the id field and assign it to country_id in the database. Since we are saving to the Cities table, popping this field into the country_id field is perfect - it sets up our relationship for us.

Now we just have to tidy up a little by adding the same functionality to the edit screen. So, off to
app/views/cities/edit.html.erb and find:

    <%= f.label :country_id %><br />
    <%= f.text_field :country_id %>

change it to read

    <%= f.label :country_id %><br />
    <%= collection_select(:city, :country_id, @countries, :id, :country_name, {:prompt => false}) %> 

and before we get ahead of ourselves, we need to add the @countries variable into the cities controller.
So, back to
app/controlers/cities_controller.rb

  def edit
    @city = City.find(params[:id])
  end

see, right there we don't have a countries variable defined. Change it to this and you are set

  def edit
    @city = City.find(params[:id])
    @countries = Country.all
  end

Voila - you are done!
One more thing to tidy up - when you create or edit your city, you end up on the "show" screen, which still lists the country id instead of the country name.

This is a simple fix, because we can just reference the country name through the city collection so go to

app/views/cities/show.html.erb and look for this line:

  <%=h @city.country_id %>

change it as follows:

  <%=h @city.country.country_name %>

Too very cool!

Now, if you want to know more about Ruby and such, here's some other good reading:
many to many dance off - and old post, but good stuff about many to many realationships in Rails.
has and belong to many - more about relationships in Rails.