Object Relational Mapping and Class Relationships
Updated: Dec 30, 2019
Today I am going to be discussing Object Relational Mapping also known as ORM.
To understand ORMs it is useful to understand how Ruby classes relate to one another. In Object Oriented Programming (OOP), languages try to model the real world, this makes it easier for developers to do their work and to create understanding around what they are doing. In the real world there are things and people who have different abilities and limitations. In OOP, we use classes and methods to represent these capabilities and limitations in our code.
I will first go over classes in Ruby, then we will go through creating a table in SQL, then we will go over how to create instances of our users. Finally, we create rows in our table out of the attributes from the user class.
Getting Familiar with Classes
Say we have a website where users can create an account, change their username, update their email and post a profile picture. This User class would look something like this:
In line 1, we are defining our User class. Classes are useful because they allow us to avoid repeating code. In our website we know we will be creating a ton of users, their accounts will have different content, but the same capabilities and features. Rather than create a lot of methods that are redundant for every single user, we can create a class. This class can be used over and over like a blue print, allowing us to create our users quickly.
In line 3, we are creating an attribute accessor, which allows the user to read and change their username, email and password. An attribute accessor is the same thing as the getter and setter methods, except we are able to use 1 line of code instead of 6-10 lines.
In line 5, we are creating our Initialize method. Every time a new user is created, we call this an instance and our Initialize method will be called. To create a new account, a new instance will have to input, a username, email and picture - these are also called attributes. If we do not put those in we will receive an error that we do not have enough arguments. By creating this Initialize method, we are saving ourselves from creating 3 methods for each argument.
Object Relational Mapping
Now that you have an understanding of classes, let’s get into Object Relational Mapping.
In ORM, classes can be used to create tables in Structured Query Language (SQL). SQL is used to communicate with databases and access their information.
Notice line 15 is called self.create table, it is creating an instance of a table and it is also checking to make sure this specific table does not already exist. If this table does not exist, SQL will create the new table. In User class, User is singular, however when we create a table we need to make the name of the table plural, in this case Users.
Notice in lines 19, 20, and 21, we are naming our attributes. These attributes create columns in this table that we are creating. Our instances become our rows in this new table.
Whenever we create a new table we need line 24 to save, edit or delete from the database. Because of Active Record we are able to save our table in this swift and easy way. More about Active Record another time.
Creating Tables with Instances
Now we need to call our create_table class method which creates a SQL statement that makes a table out of User instances. It creates columns that match the attributes of an individual instance of a user.
In the example below we are taking the user instances that we created previously and inserting them into the table to create rows.
In the example we create columns in our new table out of attributes, we use an INSERT INTO, line where we name our table, Users, and again insert our arguments just like we did in the new User instance. We also gave sample information to insert into the table, and these will be our columns.
In this table we have a column for id, username, email and picture. The PRIMARY KEY that we created when we created our table is a way for us to organize our information. Every row in our table has a unique id that we can refer to, and it will generate each time we add to our table.
This is how our username1 instance is saved into the database, we have to have our username, email and picture that we gave it plus the id that the database generated for us.
We want these instances to mirror what we have in their corresponding rows, this is so that we can retrieve it later on. Now that we have these rows with user1, user2, and user3’s data, let’s grab user 1’s id.
We will assign the id to be the value of user1’s id attribute. This is shown below:
You can see that in line 43 we have added id as an attribute. We set this id to nil so that the database is able to create the id for us.
At the end of the save method, line 59, we use a SQL query to get the value of the id column of the last inserted row, and we set that equal to the given user instance’s id attribute.
Let’s look at this code that instantiated a new user. This creates the user table and created 2 new instances of users in lines 65 and 66. These save methods in lines 67 and 68, persists them to the database, meaning the database will physically store the computed values in the table. It will update the value when any columns, that the computed column depends on, change.
This way of creating new instances is time consuming and repetitive so programmers found a more efficient way to accomplish this task. There is nothing programmers dislike more than repetitive code.
We will be creating objects very frequently, so now we will see a method that creates the instance and saves it.
At the end of the method in line 75 we are returning our user instance that we created. The return value of .create should always be the object that we created so that we do not have to run a separate query on our database to grab the user.
It is easy to have our .create method return the new object for us to work with.
When we are creating these .create methods we are creating a new row in our table with the class attributes. This also allows our database to use that information to share it with other programs.
If you accidentally delete the ruby object you can easily recreate it from the information in the database this is how databases allow our information to persist. Persistence is where data is saved outside of the process that created it. Which would be if we deleted our table, but then retrieved that information from the database.
It is really important for us to use databases to save our data. SQL makes it extremely easy to save and retrieve our information from them, and they prevent us from losing our data. We are lucky that Ruby is an OOP language and it is extremely dynamic.
Object Relational Mapping makes these really complex actions very simple and easy to write with much less code, which is always our goal as programmers. I started with OOP because we need to understand how classes and instances work in order to transfer that knowledge to tables in SQL.