Ruby on Rails, STI and Active Record Associations (belongs_to)

Oleksii Voitsekhovych
4 min readAug 30, 2022

I had the task to save the client’s address. State, city, street. Of course, you can use ordinary way — add states, cities, streets tables and add fields like:

id number

name string

parent_id number

and implement it through the belongs_to relation. But in this case it will be too much, because it can be easier :)

Let’s use STI — Single Table Inheritance.

What the documentation tells us:

“Sometimes, you may want to share fields and behavior between different models. Let’s say we have Car, Motorcycle, and Bicycle models. We will want to share the color and price fields and some methods for all of them, but having some specific behavior for each, and separated controllers too.”

In our case we need to store the same data — there is no need to create many tables, you can put everything in one.

Let’s start the most delicious part — coding :)

We create a default project.

rails new sti_example

We create a migration for the AddressPart base class

rails generate model address_part type:string name:string

And we create three models — State, City, Street

rails generate model state --parent=AddressPart
rails generate model city --parent=AddressPart
rails generate model street --parent=AddressPart

So, what do we have? We have table address_parts and four models: AddressPart, State, City, Street.

Firstly, let’s check whether it works or not.

rails db:migrate — Created the table.
rails c — opened the console and added test records

State.create(name: ‘CA’)

City.create(name: ‘Sacramento’)

Street.create(name: ‘Ace Ct’)

now you need to check that the data has been correctly saved in the table.

p ActiveRecord::Base.connection.exec_query(‘select * from address_parts’)

And what do we see in the screenshot? There are three entries in the table, which are stored there.

It’s good. But now you need to make a parent-child relationship for that data. Close the console with the exit command. Then delete the existing data and create another migration.

rails db:reset — the database was reset. and since we have only one table there, we basically just cleaned the data.
rails generate migration add_parentid_to_address_part parentid:integer
rails db:migrate

Now you need to modify the models. Just a little bit :)

street.rb
city.rb
state.rb

Now we add the data to the table. (by running the rails console)

state = State.create(name: ‘CA’)

city ​​= City.create(name: ‘Sacramento’)

Make a relation between state and the city.

state.city << city

street = Street.create(name: ‘Ace Ct’)

and add this street to the city

city.street << street

For the purity of the experiment, let’s add additional city without streets and one more street in Sacramento

street = Street.create(name: ‘Aerojet Dr.’)

city.street << street

city ​​= City.create(name: ‘San Diego’)

state.city << city

and look at the table to see what we got as a result

p ActiveRecord::Base.connection.exec_query(‘select * from address_parts’)

So, what can we see here?

The state of CA has two child records (with parentid = 1) — the cities Sacramento and San Diego.

The city of Sacramento has two child records (with parentid = 2) these are Aerojet Dr. and Ace Ct.

And there is the city of San Diego — which has no streets :)

We also have all relations working. It means that executing the following commands will show us the data structure (what exactly the commands will show — it your homework :) )

state = State.first

p state

p state.city

p state.city.first.street

street = Street.first

p street

p street.city

p street.city.state

That’s all for now. Thanks for reading. Successful projects to you. And in the next article, we will talk how to generate some addresses with the Faker gem.

--

--