In this series, I will go over the principles and foundations of object-oriented programming and some principles on databases.
In this series, I will go over the principles and foundations of object-oriented programming and some principles on databases. I will be using Ruby because I feel comfortable in the language; however, these concepts with some minor changes in syntax can be translated to other object-oriented programming languages, like Java, Node.js, etc.
Woo!
If you're creating a CLI application and/or practicing these concepts, this is an article that I think you should pay attention to. We're going to continue our talk about relationships and associations between models, specifically on how we can maintain a clean and manageable database with these relationships.
But, first let's do recap.
In my last article, we still had our Person
and Dog
classes.
Person.rb
class Person @@all = [] attr_accessor :name, :age, :height def initialize(name, age, height) @name = name @age = age @height = height #* height in inches @@all << self end def self.all @@all end def say_name puts "My name is #{self.name}." end def say_age puts "I'm #{self.age} years old." end def say_height puts "I'm about #{self.height} inches tall." end def adopt_a_dog(dog_instance) dog_instance.owner = self end def buy_a_dog(name, age, breed) # (name, age, breed, owner) #! the values for the Dog class initialize method Dog.new(name, age, breed, self) end end
Dog.rb
class Dog attr_accessor :name, :owner attr_reader :breed, :age @@all = [] def initialize(name, age, breed, owner) @name = name @age = age @breed = breed @owner = owner @@all << self end def speak puts "Bark! I'm a #{self.breed}" end end
In our console.rb
file (where we can interact with both classes and run our instance methods), we had:
require 'pry' require_relative "./app/models/dog.rb" require_relative "./app/models/person.rb" alex = Person.new("alex", 26, 68) dog1 = Dog.new("Rover", 3, "Poodle", "No Owner (Yet)") alex.adopt_a_dog(dog1) dog1 binding.pry 0
When running the line with alex.adopt_a_dog(dog1)
, the dog1
's owner attribute changes to alex
(the object, not just the name). Recall, this is what was called a has-many relationship.
A person can have many dogs and dog belongs to a person. The dog holds the reference to the instance of the owner because we want to maintain a Single Source of Truth. But there is another reason why we can't hold a reference of the dog in the Person
class.
The relationships are used in relational databases, and an simplified of a database is a data table.
Holding the reference of the dog works for the Person
class, if we only have one dog to worry about.
Now, things change when we have a second instance of the Dog
class. Notice the difference?
We needed to change our Person
class slightly. Now our instances have an extra column to account for a second dog. In this case, we're going to have to change our database and an extra column each time we want our Person
instance to have another dog.
What if we have another Person
instance, but they don't have a dog. We're going to have to put nil
values in those columns.
This other route of having a reference to all the dogs we may own in the same Person
database is going to be messy and hard to maintain. We want our lives to be easier.
Since we can't add multiple dogs as separate columns to our Person
class, we can alternatively create association methods to get data that want from another class.
We actually did this already. Recall our Dog
class and the attribute of owner. We used the attr_accessor
method so that we can get and set owners for a specific instance of Dog
. Therefore, when we call dog1.owner
we return the object alex
because a dog knows it's owner. Similarly, a person should their dog(s) as well. Let's first take a look at our console.rb
file.
console.rb
require 'pry' require_relative "./app/models/dog.rb" require_relative "./app/models/person.rb" alex = Person.new("alex", 26, 68) dog1 = Dog.new("Rover", 3, "Poodle", "No Owner (Yet)") dog2 = Dog.new("Scooby-Doo", 5, "Great Dane", "No Owner (Yet)") alex.adopt_a_dog(dog1) alex.adopt_a_dog(dog2) dog1 alex.dogs binding.pry 0
Note: I added a 0
under the binding.pry
because binding.pry
won't work when it's the last line. I added a second dog, dog2
and alex
will be it's owner as well.
I added a new method to my Person
class:
class Person ... def dogs Dog.all.select do |dog| dog.owner == self end end ... end
In our dogs
method, we're called on the Dog
class' self.all
class method to return an array of Dog
instances. Then, we're selecting all the dogs that are owned by self
(in this case, it self
refers to alex
because we're calling on this instance method, like so alex.dogs
).
As you can see when we call on the method we get both instances of Dog
.
We're one step closer to where we need to be to start thinking about creating apps and how data is managed in a relational database.