0

I'm attempting to create an extensible base model class that will allow for diffing between original and attributes so that I can create a getDirty() method on the child class that will diff between the objects orignal properties and any changed attributes. Here's the class so far:

import Vue from 'vue'

class Model {

  constructor (attributes) {
  this.original = attributes
  this.attributes = attributes

  Object.keys(this.attributes).forEach((property) => {
    Object.defineProperty(this, property, {
      get: () => this.getAttribute(property),
      set: (value) => this.setAttribute(property, value)
    })
  })
  }

  getAttribute (key) {
    return this.attributes[key]
  }

  setAttribute (key, value) {
    this.attributes[key] = value
  }

  static fetchById (id) {
    if (!id) {
      throw new Error('Cannot fetch without id')
    }

  return Vue.prototype.$http
    .get(this.endpoint + id)
    .then(response => {
      return new this(response.data)
    })
    .catch(error => {
      console.error(error)
    })
  }
}

export default Model

With this code, I am creating an account by extending this Model with an Account model:

import Model from './Model'
import Vue from 'vue'

class Account extends Model {

  static endpoint = '/api/accounts/'

  getOwner () {
    if (this.company_owner_uuid) {
      return 'company'
    }
    return 'person'
  }

  getDirty = () => {
    // This log is showing the changed name from the component input, rather than the original
    console.log(this.original.person_owner.first_name)
    const diff = Object.keys(this.original).reduce((diff, key) => {
      if (this.attributes[key] === this.original[key]) return diff
      return {
        ...diff,
        [key]: this.original[key]
      }
    }, {})

    return diff
  }

  update () {
     return Vue.prototype.$http
       .put(`${Account.endpoint}/${this.account_uuid}`, this.getDirty())
       .then(response => {
         console.log(response)
          return response.data
       })
       .catch(error => {
         console.error(error)
       })
  }
}

export default Account

And in my component:

Account.fetchById(this.id).then(account => {
  this.account = account
})
  .catch(error => {
    console.error(error)
  })
  .then(() => {
    // hide loading or whatever after promise is fully resolved...
  })

This all works really so far aside from when I make a change to the account, it mutates both this.original and this.attributes. Does anyone have a recommendation for creating an immutable this.original version of the account object that is passed into the constructor that could then be used for comparison to the mutated attributes? My end goal here is to only send any modified attributes to the back-end (I'm using Laravel 5.8). Thanks!

Matt Larsuma
  • 1,456
  • 4
  • 20
  • 52
  • You need to make a copy of attributes instead of having two references to the same object. If attributes is guaranteed to always be shallow (no nested arrays/objects) you can do this: `this.original = { ...attributes };`. Otherwise you'll need to make a deep copy. – Paul Apr 25 '19 at 18:01
  • You might be interested in this [recursive diff](https://stackoverflow.com/questions/33232823/how-to-compare-two-objects-and-get-key-value-pairs-of-their-differences/33233053#33233053) I wrote awhile back – Mulan Apr 25 '19 at 18:03
  • I've tried using the object spread operator and am getting the same result. Also, there are nested arrays/objects. How do I make a deep copy? – Matt Larsuma Apr 25 '19 at 18:53
  • Update: this works for cloning: JSON.parse(JSON.stringify(attributes)). – Matt Larsuma Apr 25 '19 at 18:55

0 Answers0