A Beginner's Guide to Dealing With Classes and Object Relationships in Javascript
Tags: webdev, javascript, oop
Published: 2021-05-11 11:50:10

Today I wanted to take a look at creating Javascript class instances and recreating the class relationships from the backend (database, models, etc.) on the frontend (Javascript in our case). I'm a student, so I'm still learning the ins and outs of Javascript, and what's worse I'm coming to it from Ruby. Don't get me wrong, I've been super excited to get to JS the whole time. It's such a big part of how we experience the internet (and it's got all the fun flashy stuff), but there's just something about the simplicity and intuitiveness of Ruby that I'm really missing right now trying to learn JS. 

One of the big adjustments for me was being separated from the backend and having to mirror its structure and objects in a way that you just never have to think about when you're using something like Ruby on Rails. So am I writing this mostly for my own benefit as an attempt to hammer this into my brain and prevent future headaches? ...Maybe, but hopefully this can also make it easier for y'all the first time around.

So where do we start? There's no database schema or migrations to guide our associations, so we'll have to build our own. As usual, the magic happens in our class.
```javascript
class Pizza {

}
```
Specifically the constructor method:
```javascript
class Pizza {
  constructor(pizza) {
    this.name = pizza.name
    this.toppings = pizza.toppings
    this.deliciousness = pizza.deliciousness
  }
}
```
What's going on there? I won't get too much into `this` because it could be, and already is I'm sure, a full post itself, but more on that (...this?) [here](https://www.w3schools.com/js/js_this.asp) if you want a deeper look. For our purposes, suffice it to say that `this` is going to be our new instance of the Pizza class. What we're doing in the constructor is telling our class how to build instances of itself and what attributes to give them. If you've worked with another Object Oriented language this should look familiar. In Ruby, for example, the `initialize` method works essentially the same way.

So what's that `pizza` argument? `pizza` is what we've decided to call the object that's being passed in from our backend. Say, for example, we're getting a JSON object back from a pizza API. That object might look something like this: 
```JSON
[
  {
    "id": 1,
    "name": "Margherita",
    "deliciousness": 9,
    "toppings": ["Mozzarella", "Tomato", "Basil"]
  },
  {
    "id": 2,
    "name": "Hawaiian",
    "deliciousness": 3,
    "toppings": ["Ham", "Pineapple"]
  }
  ...      
```   
So we'd want to iterate through each of those objects and make new Pizza instances out of them. It's not very intuitive at first (why would we take one perfectly good object and then make a different identical object out of it?), but that's where our object relationships come in. Speaking of which, let's add some.  

For our example, we'll say that `Pizzas` belong to a `Human` and a `Human` can have many `Pizzas`. That relationship would need to be reflected wherever we're getting our objects from (in this example, the database of our API), but so long as it is, we can represent it on the frontend by adding additional attributes in our constructors. 
```javascript
class Pizza {
  constructor(pizza) {
    this.name = pizza.name
    this.toppings = pizza.toppings
    this.deliciousness = pizza.deliciousness
    this.human = pizza.human
  }
}

class Human {
  constructor(human) {
    this.name = human.name
    this.pizzas = human.pizzas
  }
}

``` 
This code works; in our JSON object there would be something from the database indicating which human owns which pizza `{"name": "Margherita", "human": {"name": "Cole"}}` and we can call our constructor method on a pizza object at any time to create a new class instance `new Pizza(pizza)`. But there are a couple potential issues with that. The most common one for me had to do with class functions. I was getting errors left and right saying `TypeError: <myFunc> is not a function` when it was clearly defined right there in my code. 

Same classes as before, but let's create new instances and add a function this time. 
```javascript
// add function to Human class 
class Human {
  constructor(human) {
    this.name = human.name
    this.pizzas = human.pizzas
  }
  haveASlice(){
    console.log("Pizza is the best!")
  }
}

// our pretend backend objects 
let pizza = {
  "name": "Margherita",
  "deliciousness": 9,
  "toppings": ["Mozzarella", "Tomato", "Basil"],
  "human": {"name": "Cole"}
}
let human = {"name": "Cole"}

// instantiating new class objects
let newPizza = new Pizza(pizza)
let newHuman = new Human(human)
``` 
Great! All set up, now we have new instances of each class and we can call the function `haveASlice` on any instance of the Human class. So if we hop in the console and call
```javascript
newHuman.haveASlice()
// returns
Pizza is the best!
```
Perfect, just like we expected! What about we go the round about way?
```javascript
newPizza.human.haveASlice()
// returns
Uncaught TypeError: newPizza.human.haveASlice is not a function
```  
What happened? `newPizza.human` is a human just like `newHuman`, right? In fact, they look exactly the same. The problem here is that even though they have the same attributes, `newPizza.human` is just a regular old javascript object, where as `newHuman` is an instance of a the Human class, which means that it has access to the functions defined in that class.

This was a big source of frustration in a recent project before I figured out the difference between the two and where in my code I still had regular objects floating around. The solution here is to always create instances, to associate them with the other classes they have relationships with in the constructor of that class, and to pay attention to the order that you're creating instances. For example, as long as I'm creating the Human instance first, I can do this:
```javascript
class Pizza {
  static all = []

  constructor(pizza) {
    this.name = pizza.name
    this.toppings = pizza.toppings
    this.deliciousness = pizza.deliciousness
    this.human = Human.all.find(human => human.name === pizza.human.name)
    Pizza.all.push(this)
  }
}

class Human {
  static all = []

  constructor(human) {
    this.name = human.name
    this.pizzas = human.pizzas.map(pizza => new Pizza(pizza))
  }
}

```  
I know there might be a lot of new stuff there, but the idea is to create Pizza instances inside of the Human constructor and search for an existing Human instance when we're back in the Pizza constructor, so that the relationship between the classes is always being maintained by class instances. Here's a commented version:
```javascript
class Pizza {
  // initialize an array for all my instances to live
  static all = []

  constructor(pizza) {
    this.name = pizza.name
    this.toppings = pizza.toppings
    this.deliciousness = pizza.deliciousness
    // find an existing Human from the Human.all array below
    this.human = Human.all.find(human => human.name === pizza.human.name)
    // add 'this' (the new Pizza instance) to the Pizza.all array 
    Pizza.all.push(this)
  }
}

class Human {
  // initialize an array for all my instances to live
  static all = []

  constructor(human) {
    this.name = human.name
    // create new Pizza instances, collect them in an array
    // and make that array of instances an attribute on the new Human instance  
    this.pizzas = human.pizzas.map(pizza => new Pizza(pizza))
  }
}
```
I hope this has been helpful. Thanks for reading, go forth and create pizzas!