Object Relational Mapping(ORM) — RUBY
What is Object Relational Mapping(ORM)?
A technique of accessing a relational database using an object-oriented programming language. Object Relational Mapping is a way for programs to manage database data by “mapping” database tables to classes and instances of classes to rows in those tables. ORM is a conventional way to organize programs when we want those programs to connect to a database.
Why use ORM?
Two good reasons are:
- Cutting down on repetitive code.
- Implementing conventional patterns that are organized and sensical.
Cutting Down on Repetition
Let’s use the example that we have a program that helps a veterinary office keep track of the pets it treats and those pets’ owners. Such a program would have a Owner
class and a Cat
class. The program needs to connect to a database so that the veterinary office can persist information about its pets and owners.
The program would create a connection to the database:
database_connection = SQLite3::Database.new('db/pets.db')
Create an owners table and a cats table:
database_connection.execute("CREATE TABLE IF NOT EXISTS cats(id INTEGER PRIMARY KEY, name TEXT, breed TEXT, age INTEGER)")database_connection.execute("CREATE TABLE IF NOT EXISTS owners(id INTEGER PRIMARY KEY, name TEXT)")
Regularly insert new cats and owners into these tables:
database_connection.execute("INSERT INTO cats (name, breed, age) VALUES ('Maru', 'scottish fold', 3)")database_connection.execute("INSERT INTO cats (name, breed, age) VALUES ('Hana', 'tortoiseshell', 1)")
There is a lot of repetition in the lines above. In fact, the only difference between the two lines is the actual values. Instead of repeating the same, or similar, code any time we want to perform common actions against our database, we can write a series of methods to abstract that behavior.
We can write a .save
method on our Cat
class that handles the common action of INSERT
ing data into the database.
class Cat @@all = [] def initialize(name, breed, age)
@name = name
@breed = breed
@age = age
@@all << self
end def self.all
@@all
end def self.save(name, breed, age, database_connection)
database_connection.execute("INSERT INTO cats (name, breed, age) VALUES (?, ?, ?)",name, breed, age)
end
end
Now let’s create some new cats and save them to the database:
database_connection = SQLite3::Database.new('db/pets.db')Cat.new("Maru", "scottish fold", 3)
Cat.new("Hana", "tortoiseshell", 1)Cat.all.each do |cat|
Cat.save(cat.name, cat.breed, cat.age, database_connection)
end
The connection to the database is established. Two new cats are created and then iterate over our collection of cat instances stored in the Cat.all
method. Inside this iteration, we used the Cat.save
method, giving it arguments of the data specific to each cat to INSERT
those cat records into the cat's table. We now have some re-usable code––code that we can easily use again and again to "save" or INSERT
, cat records into the database.
Design
Follow the convention: classes are mapped to or equated with tables and instances of a class are equated to table rows.
If we have a Cat
class, we have a cat's table. Cat instances get stored as rows in the cat's table.
Further, we don’t have to make our own potentially confusing or non-sensical decisions about what kinds of methods we will build to help our classes communicate with our database.