Rails 3, jQuery and multiselect dependencies part 2.
This article is a follow up to another article on this website which focuses on using jQuery to manage the processing of multiple inter-dependent select lists on a single web page. I had a number of requests to explain the data model in a bit more detail so here goes.
I have worked on a number of internationalised (in the US, that’s spelt internationalized) applications where the selection of a ‘home country’ by a user means the display of their address fields should change to reflect their selection of a country. I’ve used this example as the basis of the multi-select article.
MySQL Data Structure
The relevant tables are in a MySQL database and are as follows:
- locales
- countries
- countrynames
- regionnames
- regions
- cities
- citynames
- users
The structure of each of the tables is as follows. Note, this is by no means a strict design criteria for your tables, it’s just a design decision that I made because it made sense to me and my warped view of the world.
The locales table contains a listing of a possible 29 locales that can be used to tailor how your application displays and interprets data
+----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| id | int(11) | YES | UNI | NULL | |
| locale | char(5) | NO | PRI | NULL | |
| name | varchar(60) | NO | | NULL | |
| fullname | varchar(60) | NO | | NULL | |
+----------+-------------+------+-----+---------+-------+
The countries table contains core country information including their names in English, their international code and their latitude and longitude.
+-----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+-------+
| id | int(11) | YES | UNI | NULL | |
| code | char(2) | NO | PRI | NULL | |
| code3 | char(3) | YES | | NULL | |
| numcode | smallint(6) | YES | | NULL | |
| url | varchar(50) | NO | MUL | NULL | |
| name | varchar(50) | NO | UNI | NULL | |
| latitude | double | NO | | NULL | |
| longitude | double | NO | | NULL | |
+-----------+-------------+------+-----+---------+-------+
The countrynames table contains the names of countries as it is interpreted in each locale. For example the country Andorra is the same in most locales ‘Andorra’ but in Russian it is ‘Андорра’.
+------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| id | int(11) | YES | UNI | NULL | |
| country_id | int(11) | NO | | NULL | |
| locale_id | int(11) | YES | | NULL | |
| name | varchar(60) | NO | | NULL | |
| fullname | varchar(60) | NO | | NULL | |
+------------+-------------+------+-----+---------+-------+
Now we’re into the messy bit. If you live in the United States, well it’s like it says on the box…it’s united ‘States’; however if you live in the United Kingdom it’s all about counties. If like me you live in Australia, we have both States and Territories (it’s a long story..look up the difference on google). Regardless of the type of country divisions, the name I’ve used is ‘Region’.
The regions table defines the region names within countries.
+------------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-----------------+------+-----+---------+----------------+
| id | int(4) unsigned | NO | PRI | NULL | auto_increment |
| country_id | int(11) | YES | | NULL | |
| code | char(3) | NO | | NULL | |
| url | varchar(50) | NO | | NULL | |
| name | varchar(50) | NO | | NULL | |
| latitude | double | NO | | NULL | |
| longitude | double | NO | | NULL | |
+------------+-----------------+------+-----+---------+----------------+
The localised version of the regions table is regionnames.
+-----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | 0 | |
| region_id | int(11) | NO | | NULL | |
| locale_id | int(11) | NO | | NULL | |
| name | varchar(60) | NO | | NULL | |
| fullname | varchar(60) | NO | | NULL | |
+-----------+-------------+------+-----+---------+-------+
Next is the cities within the regions and their localised versions. These use the tables ‘cities’ and ‘citynames’ respectively. You can see the foreign key link to the country and region tables below.
The cities table
+------------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-----------------+------+-----+---------+----------------+
| id | int(8) unsigned | NO | PRI | NULL | auto_increment |
| country_id | int(11) | NO | | NULL | |
| region_id | int(11) | YES | | NULL | |
| url | varchar(50) | NO | | NULL | |
| name | varchar(50) | NO | | NULL | |
| latitude | double | NO | | NULL | |
| longitude | double | NO | | NULL | |
+------------+-----------------+------+-----+---------+----------------+
The citynames table holds the localised version of city names.
+-----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+-------+
| id | int(11) | YES | UNI | NULL | |
| city_id | int(11) | NO | | NULL | |
| locale_id | int(11) | YES | | NULL | |
| name | varchar(60) | NO | | NULL | |
| fullname | varchar(60) | NO | | NULL | |
+-----------+-------------+------+-----+---------+-------+
There are other tables in the system including a users’ table with a locale_id which the user sets by selecting from a drop-down list on their profile-page. I haven’t included that table her because there’s also a bundle of application specific stuff in there too.
The Rails Schema
Now, like all good rails developers, you’re using schema migrations. If you’re not, go stand in the corner for twenty minutes and think about spending the same twenty minutes every week wasted looking at the conjoin between two walls. Rails database migrations are the way to go. They take a little bit of getting used to but well worth it to keep all your team on the same page, or if you’re doing your bit as a solo programmer it means you can screw it up and rewind to the last working version with ease.
As an example, I’ll show you how to create a new schema file for the cities entity. I’ll leave the remaining entities for your own interpretation.
In Rails 2, you’d use the following line
In Rails 3, it’s more DRY
This will create a new file in you db/migrate folder ending in ‘create cities.rb’. Edit this accordingly and add the fields to the migration that reflect the structure of the table we want to create. My version looks like the following:
def self.up
create_table :cities do |t|
t.integer :country_id
t.integer :region_id
t.string :url, :limit => 50, :default => "", :null => true
t.string :name, :limit => 50, :default => "", :null => false
t.float :latitude
t.float :longitude
end
end
def self.down
drop_table :cities
end
end
The Models
In the root of your application, create the necessary models. Under Rails 3, that can be done by entering
A new file is generated under the app/models folder called city.rb.
The full listing of my models folder is as follows:
city.rb
country.rb
region.rb
cityname.rb
countryname.rb
regionname.rb
profile.rb
user.rb
role.rb
admin.rb
For the purposes of this discussion we’re interested in everything but the last 4 files. These are used to store user permissions, roles, and profile data.
The content of the relevant model files is used to implement the relationships between the different entities and some field level validation rules. There is nothing whiz-bang about this, just the use of the Rails convention over configuration principle at work.
Taking it from the top, my files look like the following:
validates_presence_of :name
end
validates_presence_of :name
belongs_to :country
has_many :citynames
end
validates_presence_of :name
validates_presence_of :code
has_many :regions
has_many :citys
end
validates_presence_of :name
belongs_to :country
has_many :regionnames
end
belongs_to:city
belongs_to:locale
end
belongs_to :country
belongs_to :locale
end
belongs_to :region
belongs_to :locale
end
So, there you have it, fairly simple really, a couple of tables, a couple of model files and the material from this article and the previous article should have you up and running. As always, any queries, comments or improvements, please let me know.


2 Responses to “Rails 3, jQuery and multiselect dependencies part 2.”
Wonderful article! Any chance you may know a seed for the US cities and locales or do I have to do all this manually? Thanks, Alex
very good, thank you very much.
fails to put it to work with simple_form. would be good for you to do that version as well. be helpful.
Leave a Reply