Normalizing State

const vendingMachine = [
  {
    id: 1,
    name: 'Leela'
    location: {
      id: 1,
      name: 'Lake View'
    },
    inventory: [
      {
        id: 1,
        name: 'Yay Chips'
      },
      {
        id: 2,
        name: 'Chips of Cookies',
      }
    ]
  },
  {
    id: 2,
    name: 'Farnsworth',
    location: {
      id: 1,
      name: 'Lake View'
    },
    inventory: [
      {
        id: 1,
        name: 'Yay Chips'
      },
      {
        id: 3,
        name: 'Bag of Bretzels',
      }
    ]
  }
]
Location
Inventory
Location
Inventory
Machine
Machine
const vendingMachine = [
  {
    id: 1,
    name: 'Leela'
    location: {
      id: 1,
      name: 'Lake View'
    },
    inventory: [
      {
        id: 1,
        name: 'Yay Chips'
      },
      {
        id: 2,
        name: 'Chips of Cookies',
      }
    ]
  },
  {
    id: 2,
    name: 'Farnsworth',
    location: {
      id: 1,
      name: 'Lake View'
    },
    inventory: [
      {
        id: 1,
        name: 'Yay Chips'
      },
      {
        id: 3,
        name: 'Bag of Bretzels',
      }
    ]
  }
]
Rename: Lakeview
Rename: Lakeview

The case for normalization

  • Data is duplicated and leaves room for update errors

  • Data is deeply nested and hard to update

  • Updates to nested data leads to complete re-renders of unrelated components

{
  machines : {
    byId : {
      "machine1" : {
        id : "machine1",
        name: "Leela",
        snacks : ["inventory1", "inventory2"]
      },
      "machine2" : {
        id : "machine2",
        name : "Farnsworth",
        snacks : ["inventory1", "inventory3"]
      }
    },
    allIds : ["machine1", "machine2"]
  },
  
  snacks: {
    byId: {
      "inventory1": {
        id: "snack1",
        name: "Yay Chips"
      },
      "inventory2": {
        id: "snack2",
        name: "Chips of Cookies"
      },
      "inventory3": {
        id: "snack3",
        name: "Bag of bretzels"
      }
    },
    allIds: ["inventory1", "inventory2", "inventory3"]
  }
}
  
Id Name Inventory
machine1 Leela inventory1,
inventory2
machine2 Farnsworth inventory1,
inventory3
Id Name
inventory1 yay chips
inventory2 chips of cookie
inventory3 bag of  bretzels

Machines

Inventory

{
  machines : {
    byId : {
      "machine1" : {
        ...,
        inventory : ["inventory1", "inventory2"]
      },
      "machine2" : {
        ...,
        inventory : ["inventory1", "inventory3"]
      }
    },
  },
  
  inventory: {
    byId: {
      "inventory1": { ... },
      "inventory2": { ... },
      "inventory3": { ... }
      }
    },
  },
  
  inventoryInMachine: [
    {
      inventory_id: "inventory1",
      machine_id: "machine1"
    },
    {
      inventory_id: "inventory2",
      machine_id: "machine1"
    },
    {
      inventory_id: "inventory1",
      machine_id: "machine2"
    }
    ...
  ]
}
  

inventory and machine relationship table

Id Name Inventory
machine1 Leela inventory1,
inventory2
machine2 Farnsworth inventory1,
inventory3
Id Name
inventory1 yay chips
inventory2 chips of cookie
inventory3 bag of  bretzels

Machines

Inventory

Inventory in Machines

Inventory Id MachineId
inventory1 machine1
inventory2 machine1
inventory1 machine2
inventory3 machine2

Enter: Vuex ORM

Vuex ORM is a plugin for Vuex to enable Object-Relational Mapping access to the Vuex Store. Vuex ORM lets you create "normalized" data schema within Vuex Store.

What is Vuex ORM?

Step 1: Define Data Model

Step 2: Insert Data into Model

Step 3: Query Data!

Organizing Data in Vuex ORM

Step 1: Define Data Model

class Machine extends Model {
  static entity = 'machines'

  static fields () {
    return {
      id: this.attr(null),
      name: this.string('')
    }
  }

}

name of data entity

`this.$store
.state.entities`

Step 1: Define Data Model

class Inventory extends Model {
  static entity = 'inventory'

  static fields () {
    return {
      id: this.attr(null),
      name: this.string('')
    }
  }
}

fields/attributes in data entity

Step 2: Insert Data into Model

Machine.insert({ 
  data: [
    { id: 1, name: "Leela" },
    { id: 2, name: "Farnsworth" }
  ]
})

Step 2: Insert Data into Model

Inventory.insert({ 
  data: [ 
    { id: 1, name: 'Yay Chips' },
    { id: 2, name: 'Chips of Cookies' },
    { id: 3, name: 'Bag of Bretzels' },
    { id: 4, name: 'Corn Crisps' },
    { id: 5, name: 'Triangle Chips' } 
  ]
})

Step 3: Query Data

// Fetch all machines.
const machines = Machine.all()

 

 

// Fetch all snacks.
const snacks = Snack.all()
├── src/
  ├── components
  ├── App.vue
  └── store.js
├── models
  └── Machine.js
├── ...
└── public/
models
Machine.js

Vuex ORM Folder Structure

import Vue from "vue";
import Vuex from "vuex";

import VuexORM from "@vuex-orm/core";
import Machine from "@/models/Machine";

Vue.use(Vuex);

const database = new VuexORM.Database();
database.register(Machine);

export default new Vuex.Store({
  plugins: [VuexORM.install(database)],
  state: {},
  getters: {},
  mutations: {},
  actions: {
    init() {
      const machines = [...]
      Machine.insert({ data: machines });
    }
  }
});

store.js

initialize DB

register model to DB

add VuexORM as a plugin

import Vue from "vue";
import Vuex from "vuex";

import state from "./state";
import actions from "./actions";
import getters from "./getters";
import mutations from "./mutations";

import VuexORM from "@vuex-orm/core";
import Machine from "@/models/Machine";

Vue.use(Vuex);

const database = new VuexORM.Database();
database.register(Machine);

const store = new Vuex.Store({
  plugins: [VuexORM.install(database)],
  state,
  getters,
  mutations,
  actions
});

if (store._actions.init) {
  store.dispatch("init");
}

export default store;

store/index.js

import Machine from "@/models/Machine";

const init = () => {
  //initialize db here //
  const machines = [...]
  Machine.insert({ data: machines });
};

main.js

Let's create a machine model in Vuex ORM

Exercise Time!

[step-0]

https://github.com/shortdiv/vuex-normalize-state

The power of an ORM is revealed when relationships are established.

 

In Vuex ORM, relationships are defined as attributes in Model's static fields.

Relationships in Vuex ORM

  • One to One
  • One to Many
  • Many to One
  • ....

Relationships in Vuex ORM

Id Name Location
machine1 Leela Lake View
machine2 Farnsworth Wicker Park
machine3 Fry Lake View
Id Name
location1 Lake View
location2 Wicker Park
location3 Boystown

Machines

Locations

Id Name Inventory
machine1 Leela Lake View
machine2 Farnsworth Wicker Park
machine3 Fry Lake View
Id Name
location1 Lake View
location2 Wicker Park
location3 Boystown

Machines

Machines in Location

Locations MachineId
location1 machine1, machine3
location2 machine2
location3

Locations

class Location extends Model {
  static entity = 'locations'

  static fields () {
    return {
      id: this.attr(null),
      name: this.string(''),
      latlng: this.latlng(''),
      machines: this.hasMany(Machine, 'machine_id'),
    }
  }
}

define one to many relationship

import Machine from "@/models/Machine";

const init = () => {
  const machines = [
    {
      id: 1,
      name: "Leela",
      lastStocked: "Feb 2019",
      condition: "Working",
      location: "Lake View",
      latlng: [-87.6554288, 41.94705],
      inventory: []
    },
    ...
  ]
  Machine.insert({ data: machines });
};

export default {
  init,
  ...
}

store/actions.js

import Machine from "@/models/Machine";
import Location from "@/models/Location";

const init = () => {
  const machines = [
    {
      id: 1,
      name: "Leela",
      lastStocked: "Feb 2019",
      condition: "Working",
      location_id: 1,
      inventory: [
    },
    ...
  ]
  const locations = [
    {
      id: 1,
      name: "Lake View",
      latlng: [-87.6554288, 41.94705]
    },
    ...
  ]
  Machine.insert({ data: machines });
  Location.insert({ data: locations });
};

export {
  init,
  ...
}

store/actions.js

Let's create a location model in Vuex ORM

Exercise Time!

[step-1]

https://github.com/shortdiv/vuex-normalize-state

Id Name Inventory
machine1 Leela Lake View
machine2 Farnsworth Wicker Park
machine3 Fry Lake View
Id Name Locked Down
location1 Lake View false
location2 Wicker Park false
location3 Boystown false

Machines

Machines in Location

Locations MachineId
location1 machine1, machine3
location2 machine2
location3

Locations

Id Name Inventory
machine1 Leela Lake View
machine2 Farnsworth Wicker Park
machine3 Fry Lake View
Id Name Locked Down
location1 Lake View false
location2 Wicker Park true
location3 Boystown false

Machines

Machines in Location

Locations MachineId
location1 machine1, machine3
location2 machine2
location3

Locations

Query Builders in Vuex ORM

// Get Query Builder instance.
const query = Machine.query()

 

 

const machine = Machine
               .query()
               .where('location_id', 1).get()

get all machines at location_id 1, i.e. Lake View

Query Builders in Vuex ORM

├── src/
  ├── components
  ├── App.vue
  └── store.js
├── models
  └── Machine.js
├── helpers
  └── LocationQuery.js
├── ...
└── public/
helpers
LocationQuery.js
import Location from "@/models/Location";

const getLocationById = location_id => {
  const location = Location.query()
    .where("id", parseInt(location_id))
    .get();
  return location[0];
};

export { 
  getLocationById,
};

helpers/locationQueries.js

helpers/locationQueries.js

<template>
  <section class="action-section">
    <div class="machine-information">
      <div class="machine-id">
        <h3>Location</h3>
        <p>{{ selectedNeighborhood.name }}</p>
      </div>
      ...
    </div>
    ...
  </section>
</template>
<script>
import { 
  getLocationById
} from "@/helpers/locationQueries";

export default {
  name: "LocationView",
  props: {
   neighborhoodId: String // query param //
  }
  computed: {
    selectedNeighborhood() {
     return getLocationById(this.neighborhoodId);
    }
  }
}
</script>

components/LocationView.vue

Let's create some helper queries in Vuex ORM

Exercise Time!

[step-2]

https://github.com/shortdiv/vuex-normalize-state

class Snack extends Model {
  static entity = 'snacks'

  static fields () {
    return {
      id: this.attr(null),
      name: this.string(''),
      machines: this.belongsToMany(
                  Machine,
                  MachineSnack,
                  'snack_id',
                  'machine_id'
                ),
    }
  }
}
class MachineSnack extends Model {
  static entity = 'machineSnacks'
  static primaryKey = ['snack_id', 'machine_id']

  static fields () {
    return {
      snack_id: this.attr(null),
      name: this.string(''),
      machines: this.belongsToMany(
                  Machine,
                  MachineSnack,
                  'snack_id',
                  'machine_id'
                ),
    }
  }
}

Exercise Time!

Let's work

Normalizing State

By shortdiv

Normalizing State

  • 596