feathers-vuex: Populated records not updating in realtime

Are records populated on the feathers server supposed to publish changes to clients in realtime?

Say we have a user with populated contacts as so:

// server (in schema)
User = {
    contacts: [ User ] // populated in hook
}

// client (in model)
setupInstance(data, { models, store }) {
    const { User } = models.api
    if (data.contacts) {
        data.contacts = data.contacts.map(contact => new User(contact))
    }
    return data
}

If one of the populated users were to change its name remotely from its own account, would it be reflected live to the others? Or does the model assignment in setupInstance only serves as a means to save the user back to its own service?

I didn’t manage to make the live connection work with populated records. It only works when get/finding the services directly. Please tell me if this is expected behavior.

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 1
  • Comments: 25 (13 by maintainers)

Most upvoted comments

Thanks for your input @J3m5. You’re right, getting the contacts directly from the store is a solution (although we still have to filter out the main user + other users that might have been independently loaded to the store).

Another workaround would be to fetch the relationships separately from the client, as shown in the guide’s example where a patient record first gets loaded, then its appointments.

Still, it seems having the populated records react directly through the nested relationships would be a more elegant and convenient way to do it.

I did something similar 6 months ago.

import _get from "lodash/get";

const getItem = (service, store, select = []) => (id) => {
  const basePath = ["state", service, "keyedById", id];
  const fullPath = basePath.concat(select);
  return _get(store, fullPath);
};

const getFromStore = ({ item, service, select, localField, store }) => {
  const ids = item[localField];
  const itemGetter = getItem(service, store, select);


  return Array.isArray(ids)
    ? ids.map(itemGetter)
    : itemGetter(ids);
};

const findFromStore = ({ item, service, store, find, onlyOne }) => {
  const query = find && (typeof find === "function" ? find(item) : find);
  const { data } = store.getters[`${service}/find`]({ query });
  return onlyOne ? data[0] : data;
};
const generateGetter = ({ from, localField, as, select, find, onlyOne }) => {
  if (!from || !localField || !as) {
    return {};
  }

  const storeGetter = find ? findFromStore : getFromStore;
  return {
    getter: {
      get() {
        return storeGetter({
          item: this,
          service: from,
          select,
          localField,
          store: this.constructor.store,
          find,
          onlyOne,
        });
      },
      enumerable: true,
      configurable: true,
    },
    property: as,
  };
};

export const generateGetters = function(Model) {
  if (Array.isArray(Model.populate)) {
    Model.populate.forEach((lookup) => {
      const { property, getter } = generateGetter(lookup);
      if (property && getter) {
        Object.defineProperty(Model.prototype, property, getter);
      }
    });
  }
};

And I define relttionships in he Model like that:

  static populate = [
    {
      from: "users",
      localField: "userId",
      // foreignField: "_id",
      as: "user",
      onlyOne: true,
    },
    {
      from: "employees",
      localField: "managerId",
      as: "manager",
    },
    {
      from: "companies",
      localField: "companyId",
      as: "company",
    },
    {
      from: "roles",
      localField: "roleIds",
      as: "roles",
      select: "name",
    },
  ];

@J3m5 Is this what you meant by the getter on the model?

static setupInstance (data, { models, store }) {
....
    //Populate references in store.
    data.tags.map(r=> new Tag(r))

    //Remove the field from data
    delete data.tags

    //Replace the field with a getter to pull records from store using the findGetter
    Object.defineProperty(data, 'tags', {
      get: function () {
        return Tag.findInStore({
          query: {
            id: {
              $in: [].concat(this.tagIds)
            }
          }
        }).data || []
      }
    })

This way, record.tags can be accessed as a property and returns the reactive records. This also hooks nicely to instances where I was querying using the nested property

{
    query: {
        'tags.title': 'mytag'
    }
}

This works but I am not sure if I might be missing something because it says not to perform queries in getters (or is it for that specific case?) https://vuex.feathersjs.com/2.0-major-release.html#don-t-perform-queries-side-effects-in-getters

We are thinking about a way to populate relationships in the find/get getter with a specific query syntax, that would make it easier and you would only have the relationships populated only when you need it. You could also have the choice to fetch all the data at once or separately.