Meal Maker: Adding code inside the menu item Getters & Setters

A link to the project is here:
Intro to JavaScript: Meal Maker Project

My code is as follows:

let menu = {
  _courses: {
    appetizers: [],
    mains: [],
    desserts: [],
  },
  get appetizers() {
    
  },
  set appetizers(appIn) {
    
  },
  get mains() {
    
  },
  set mains(mainsIn) {
    
  },
  get desserts() {
    
  },
  set desserts(dessIn) {
    
  },
  get courses() {
    return {
      appetizers: this._courses.appetizers,
      mains: this.courses._mains,
      desserts: this._courses.desserts
    }
  },
  addDishToCourses (courseName, dishName, dishPrice) {
    const dish = {
      dishName: dishName,
      dishPrice: dishPrice
    };
    this._courses[courseName].push(dish)
  },
  getRandomDishFromCourse (courseName) {
    let dishes = this._courses[courseName];
    let randomIndex = Math.floor(Math.random() * dishes.length);
    return dishes[randomIndex]
  },
  generateRandomMeal () {
    const appetizer = this.getRandomDishFromCourse("appetizers")
    const main = this.getRandomDishFromCourse("mains")
    const dessert = this.getRandomDishFromCourse("desserts");
    const totalPrice = appetizer.dishPrice + main.dishPrice + dessert.dishPrice;
    return `Your meal is ${appetizer.dishName}, ${main.dishName}, ${dessert.dishName}. The price is $${totalPrice}.`
  },
};

menu.addDishToCourses('appetizers', 'Ceasar Salad', 4.25)
menu.addDishToCourses('appetizers', 'Spring Rolls', 5.25);
menu.addDishToCourses('appetizers', 'Chef Salad', 3.75);
menu.addDishToCourses('mains', 'Fried Chicken and Poutine', 14.25);
menu.addDishToCourses('mains', 'Prawn Pasta', 13.75);
menu.addDishToCourses('mains', 'Pork Ramen', 11.50);
menu.addDishToCourses('desserts', 'Chocolate Torte', 3.75);
menu.addDishToCourses('desserts', 'Frozen Yogurt', 3.75);
menu.addDishToCourses('desserts', 'Berry Sorbet', 3.75);

let meal = menu.generateRandomMeal()
console.log(meal)

console.log(menu._courses.appetizers)

It all seems to work as it should, but I’d like to know how to use the Getters and Setters for each of the _Course types that we are instructed to build at step 4 and in step 5.

As I go through my code, I follow what’s going on, but I’m not sure where the Getters and Setters come in. In trying to solve a problem I had earlier in the project, I found other people’s code in the help section had written code within the Getters and Setters that we were not instructed to write in the project steps.

Along the way, I did notice suggestions in the hints to use the Getters and Setters, although, I’m not sure how to do so. I found the following example of code within the Getters and Setters:

const menu = {
  _courses: {
    
    _appetizers: [],
    _mains: [],
    _desserts: [],
    set appetizers(newAppetizers) {
    	this._appetizers = newAppetizers;
  	},
    get appetizers() {
    	return this._appetizers;
  	},
    set mains(newMains) {
      this._mains = newMains;
    },
    get mains() {
      return this._mains;
    },
    set desserts(newDesserts) {
      this._desserts = newDesserts;
    },
    get desserts() {
      return this._desserts;
    },
    
  },
  get courses() {
    return {
      appetizers: this._courses.appetizers,
      mains: this._courses.mains,
      desserts: this._courses.desserts,
    }
  },

What is going on in the body of the Getters and Setters of this code?

Also I notice within this example, which is part of someone else’s code that also worked, that the Getters and Setters for each menu item reside inside of the “_courses” object rather than the higher level “menu” object. In the hints for step 4, the Getters and Setters are written within the “Menu” object.

Clarification would be and some clues as to how to use the Getters and Setters within the code would be wonderful!

Thanks!

1 Like

An astute observation, and one that may well lead to the conclusion that getters and setters of needs reside in the same scope as the attributes they serve.

The literal attributes are defined with underscore prefix, and as such are not directly accessible using typical nomenclature…

 > menu = {
     courses: {
       mains: {
         'liver and onions': 12.95
       }
     }
   }
<- > {courses: {…}}
 > menu.courses.mains
<- {liver and onions: 12.95}
 >

Once we implement backing variables into the above code, everything changes.

 > menu = {
     _courses: {
       _mains: {
         'liver and onions': 12.95
       }
     }
   }
<- > {_courses: {…}}
 > menu.courses.mains
<- > Uncaught TypeError: Cannot read property 'mains' of undefined
 >

Of course, since the attributes are not private, we can access them in their literal form…

 > menu._courses._mains
<- >  {liver and onions: 12.95}

Let’s piece together the getters.

 > menu = {
     _courses: {
       _mains: {
         'liver and onions': 12.95
       }
     },
     get courses () {
       return this._courses;
     }
   }
<- > {_courses: {…}}
 > menu.courses
<- > {_mains: {…}}
 > 

The next step is essentially more of the same…


 > menu = {
     _courses: {
       _mains: {
         'liver and onions': 12.95
       },
       get mains () {
         return this._mains;
       }
     },
     get courses () {
       return this._courses;
     }
   }
<- > {_courses: {…}}
 > menu.courses.mains
<- >  {liver and onions: 12.95}
 > 

Now we go heave-ho…

 > menu = {
     _courses: {
       _mains: {
         'liver and onions': 12.95
       },
       get mains () {
         return this._mains;
       },
       set mains (entry) {      
         this._mains[Object.keys(entry)[0]] = Object.values(entry)[0];
       }
     },
     get courses () {
       return this._courses;
     }
   }
<- > {_courses: {…}}
 > menu.courses.mains = {'bacon and eggs': 10.95}
<- > {bacon and eggs: 10.95}
 > menu.courses.mains
<- > {liver and onions: 12.95, bacon and eggs: 10.95}
1 Like

Thank you so much for this. It’s starting to crystalize in my head now the more I stare at this.

So in review of what you just laid out, the section:

set mains (entry) {      
         this._mains[Object.keys(entry)[0]] = Object.values(entry)[0];
       }

Is triggered when we “add” items to the menu as you did with:

> menu.courses.mains = {'bacon and eggs': 10.95}

Correct?

If this is so, I’m now NOT clear on what is going on with:

this._mains[Object.keys(entry)[0]] = Object.values(entry)[0]

that it can now output:

> menu.courses.mains = {'bacon and eggs': 10.95}
<- > {bacon and eggs: 10.95}
 > menu.courses.mains
<- > {liver and onions: 12.95, bacon and eggs: 10.95}

setting menu.courses.mains equal to bacon and eggs: 10.95 seems to have appended that Key/Value pair to the end of _mains object, behind liver and onions.

I guess I’m not sure what’s going with "Object.keys(entry)[0] = Object.value(entry)[0].

Or perhaps something else is going on?

1 Like

@mtf is using a setter to do something other than setting. Please use a regular method which says what it does instead.

The reality of getters and setters is that they are not generally useful, they’re being shown to you because they exist in the language, not because they’re useful to you. (Note that you could use regular properties and get the same behaviour, the example here doesn’t actually use getters/setters to any benefit, it’s just crammed in there without clarifying how contrived the example is)

In particular, don’t use getters and setters to do other things than getting and setting. Your code is supposed to say what it does, not what it doesn’t do.

Their main use as far as I can see is if exposing some form of interface where the general idea is that things are being set and get, but a bit more than that is actually happening. An example is if a property doesn’t exist yet, but can be computed the first time it is asked for (but a method could be used for that as well, and would be more honest about that it is a computation, not just simple lookup, really I’m just saying I can’t come up with a non-contrived example either)

1 Like

Bottom line, as @ionatan asserts, it is contrived, and not applicable to the lesson. It does demonstrate some rather strange mechanics at play.

Note that I used objects in the example, not arrays. The setter can only take one parameter, entry, which above is an object. We do not know the keyname so must poll it with the keys method. LIkewise we poll the value with values method.

As weird as all that looks, we could feasibly (in this example) pass an object of any size and iterate over that input to extract key/value pairs, which are added to the mains object. Above there is but one, keys(entry)[0] and values(entry)[0].

Talk about a mash-up. I am in total agreement that this exercise should not be using setters, but rather an addMain method that is more explicit about what is going on. No matter what we do to make this all work, it comes out in the end looking a far cry from what it actually is. A lot of silliness.

Take from the example what you can, and hopefully as you go your way, you’ll find ways and means of doing things “more honestly” in your code creations.

Let’s go back to your mains setter…

    set mains(newMains) {
      this._mains = newMains;
    },

Note that in the initialization mains is an array. The above line replaces that with a value.

this._mains.push(newMains)

will append the array. It still makes little sense that we should have to write,

menu.courses.mains = {'liver and onions': 12.95}
menu.courses.mains = {'bacon and eggs': 10.95}

There is no evidence in the above that those objects are being pushed to an array (or in my example, added to the object).

addMain (entry) {
    this.mains.push(entry)
}

Now we can write,

menu.courses.addMain({'bacon and eggs': 10.95})

which pretty much says what it does with the argument.

 > menu = {
     _courses: {
       _mains: [],
       get mains () {
         return this._mains;
       },
       addMain (entry) {
         this.mains.push(entry);    // note the use of the getter
       }
     },
     get courses () {
       return this._courses;
     }
   }
 > menu.courses.mains    // uses both getters
<- > [{…}]
       > 0: {bacon and eggs: 10.95}
         length: 1
       > __proto__: Array(0)
1 Like

Addendum

menu_empty_object

See that mains: Object? That must be how JS supports backing variables, by secretly instantiating a backup object. Note that it is not {} like _mains? This suggests there is a this.mains = new Object() statement somewhere in the background.

In this next frame we see that JS appears to keep both objects afloat.

Consider this, though…

menu.courses
{_mains: {…}, addMain: ƒ}
addMain: ƒ addMain(entry)
mains: (...)
_mains: {bacon and eggs: 10.95, liver and onions: 12.95}
get mains: ƒ mains()
__proto__: Object

The mains object isn’t populated. We need to invoke its getter to populate it. Evidently the backing variable is the prima facie data object, in a manner of speaking. After that we’re just mimicking behavior and not much more.

One real use I see for getters and setters is in giving logic to the instantiation process. A class definition is hoisted, as are getter and setter methods in the class. That means we can call them when constructing a new instance object.

Oops, I’ve let it slip. This is not a class (custom class, that is). Shouldn’t be talking about that yet. Bookmark this topic until you get to classes.