feathersjs-ecosystem / feathers-vuex

Integration of FeathersJS, Vue, and Nuxt for the artisan developer
https://vuex.feathersjs.com
MIT License
445 stars 109 forks source link

Associations Pattern (feedback) #440

Closed corepay closed 4 years ago

corepay commented 4 years ago

So trying to figure out how to fetch associations from a service without dups came up with this. Seems to work and wanted to share to help anyone with similar needs - or better yet get feedback from Marshall and better minds on where this will create problems down the road.

So I have Postgres/Sequelize/Feathers on the backend with the following resources.

A Customer has 1:1 associations to either a Companies record or a Contacts (not both)

The Companies record and Contacts record have 1:1 associations with Phones, Emails and Addresses

My goal is to fetch the customers and show them in a Quasar Table with the associated contact or company name, email address, phone number and address.

First I expect a lot of Customers so setup my Feathers server services to turn off pagination in my before find hook on each of the services before hook.

before: {
  find: [
    async context => {
        const { params } = context;
        if (params.query && params.query.paginate === "false") {
          params.paginate =
            params.query.$paginate === "false" ||
            params.query.$paginate === false;
          delete params.query.paginate;
        }
    }
  ]
}

I setup my Customer List .vue file using the (Renderless Data Components)[https://vuex.feathersjs.com/data-components.html#renderless-data-components] pattern

Basically, I setup some arrays to hold and filter out duplicated associated data ids and perform $in queries to fetch the data matching those id's. It looks something like this:

<script>
import { mapGetters, mapActions, mapState } from "vuex";
export default {
  components: {
    Settings
  },
  data() {
    return {
      loading: false,
      filter: "",
      pagination: {
        rowsPerPage: 0,
        sortBy: "created",
        descending: false
      }
    };
  },

  async created() {
    this.loading = true;
    // setup temporary arrays to hold ids
    let companyArray = [];
    let contactArray = [];
    let phoneArray = [];
    let emailArray = [];
    let addressArray = [];

    await this.findCustomers({
      query: {
        paginate: "false"
      }
    })
      .then(response => {
        response.forEach(item => {
          if (item.company_id) {
            companyArray.push(item.company_id);
          }
          if (item.contact_id) {
            contactArray.push(item.contact_id);
          }
        });
      })
      .catch(err => {
        console.log(err.message)
      });

    const filteredContacts = Array.from(new Set(contactArray)); // <- new array no duplicate contacts
    await this.findContacts({
      query: {
        paginate: "false",
        id: {
          $in: this.filteredContacts  // <- query on contact ids
        }
      }
    })
      .then(res => {
        res.forEach(item => {
          if (item.address_id) {
            addressArray.push(item.address_id); // <- push the contact address id in temp array
          }
          if (item.default_phone_id) {
            phoneArray.push(item.phone_id); // <- push the contact phone id in temp array
          }
          if (item.default_email_id) {
            emailArray.push(item.email_id); // <- push the contact email id in temp array
          }
        });
      })
      .catch(err => {
        console.log(err.message)
      });

    const filteredCompanies = Array.from(new Set(companyArray)); // <- new array removed duplicates

    await this.findCompanies({
      query: {
        paginate: "false",
        id: {
          $in: this.filteredCompanies // <- query by company ids
        }
      }
    })
      .then(res => {
        res.forEach(item => {
          if (item.address_id) {
            addressArray.push(item.address_id); // <- push the company address id in temp array
          }
          if (item.default_phone_id) {
            phoneArray.push(item.phone_id); // <- push the company phone id in temp array
          }
          if (item.default_email_id) {
            emailArray.push(item.email_id); // <- push the company email id in temp array
          }
        });
      })
      .catch(err => {
        console.log(err.message)
      });

    const filteredEmails = Array.from(new Set(emailArray));
    await this.findEmails({
      query: {
        paginate: "false",
        id: {
          $in: this.filteredEmails
        }
      }
    }).catch(err => {
      console.log(err.message)
    });

    const filteredAddresses = Array.from(new Set(addressArray));
    await this.findAddresses({
      query: {
        paginate: "false",
        id: {
          $in: this.filteredAddresses
        }
      }
    })
      .catch(err => {
        console.log(err.message)
      });

    const filteredPhones = Array.from(new Set(phoneArray));
    await this.findPhones({
      query: {
        paginate: "false",
        id: {
          $in: this.filteredPhones
        }
      }
    }).catch(err => {
      console.log(err.message)
    });
  },

  computed: {
    ...mapState("customers", { areCustomersLoading: "isFindPending" }),
    ...mapGetters("customers", { findCustomersInStore: "find" }),
    customers() {
      return this.findCustomersInStore({}).data;
    },
    ...mapState("companies", { areCompaniesLoading: "isFindPending" }),
    ...mapGetters("companies", { findCompaniesInStore: "find" }),
    companies() {
      return this.findCompaniesInStore({}).data;
    },
    ...mapState("contacts", { areContactsLoading: "isFindPending" }),
    ...mapGetters("contacts", { findContactsInStore: "find" }),
    contacts() {
      return this.findContactsInStore({}).data;
    },
    ...mapState("invoices", { areInvoicesLoading: "isFindPending" }),
    ...mapGetters("invoices", { findInvoicesInStore: "find" }),
    invoices() {
      return this.findInvoicesInStore({}).data;
    },
    ...mapState("emails", { areEmailsLoading: "isFindPending" }),
    ...mapGetters("emails", { findEmailsInStore: "find" }),
    emails() {
      return this.findEmailsInStore({}).data;
    },
    ...mapState("phones", { arePhonesLoading: "isFindPending" }),
    ...mapGetters("phones", { findPhonesInStore: "find" }),
    phones() {
      return this.findPhonesInStore({}).data;
    },
    ...mapState("addresses", { areAddressesLoading: "isFindPending" }),
    ...mapGetters("addresses", { findAddressesInStore: "find" }),
    addresses() {
      return this.findAddressesInStore({}).data;
    },
  },
  methods: {
    ...mapActions("customers", { findCustomers: "find" }),
    ...mapActions("invoices", { findInvoices: "find" }),
    ...mapActions("companies", { findCompanies: "find" }),
    ...mapActions("contacts", { findContacts: "find" }),
    ...mapActions("addresses", { findAddresses: "find" }),
    ...mapActions("emails", { findEmails: "find" }),
    ...mapActions("phones", { findPhones: "find" }),

// Associate the fetched data to the customer to display in the table
setCustomerData() {
      this.customers.forEach(customer => {
        this.companies.forEach(company => {
          if (company.id === customer.company_id) {
            this.addresses.forEach(address => {
              if (company.default_address_id === address.id) {
                customer.address = address;
              }
            });
            this.emails.forEach(email => {
              if (company.default_email_id === email.id) {
                customer.email = email;
              }
            });
            this.phones.forEach(phone => {
              if (company.default_phone_id === phone.id) {
                customer.phone = phone;
              }
            });
            customer.company = company;
          }
        });
        this.contacts.forEach(contact => {
          if (contact.id === customer.contact_id) {
            this.addresses.forEach(address => {
              if (contact.default_address_id === address.id) {
                customer.address = address;
              }
            });
            this.emails.forEach(email => {
              if (contact.default_email_id === email.id) {
                customer.email = email;
              }
            });
            this.phones.forEach(phone => {
              if (contact.default_phone_id === phone.id) {
                customer.phone = phone;
              }
            });
            customer.contact = contact;
          }
        });
      });
    },
  }
};
</script>
corepay commented 4 years ago

PS: I am a freshman coder at best, so likely this code is bloated, should have used ES6 maps or whatever for arrays but it seems to work. Comments on how to tune this would be appreciated