Is it possible to use a variable object inside collection.find() in mongoose?

78 views Asked by At

Context: I am trying to code the back end of an "Advanced Search" option It is a project I'm coding to practice JS, MongoDB(Mongoose), Next, Node, Etc.

The Schema I'm using is the following:

House Schema:{
 name:String,
 address:{
   street:String
   city:String,
   state:String
   },
 operations:{
   rent:Boolean, 
   shortRent:Boolean, 
   purchase:Boolean,
   agentRentPrice:Number,
   agentShortRentPrice:Number,
   agentSellingPrice:Number},
 features:{
   bathrooms:Number,
   bedrooms:Number,
   beds:Number,
   amenities:[String],
   },
}

Now, the FrontEnd sends the following info (in req.body):

query = {
  name: null,
  address: { city: null, state: null },
  operations: {
    rentAvailable: false,
    purchaseAvailable: false,
    shortRentAvailable: false,
    agentRentPriceMin: null,
    agentSellingPriceMin: null,
    agentShortRentPriceMin: null,
    agentRentPriceMax: null,
    agentSellingPriceMax: null,
    agentShortRentPriceMax: null,
  },
  features: {
    bathroomsMin: null,
    dormsMin: null,
    bedsMin: null,
    amenities: null,
  },
}

Of course, those "null" values will be replaced with numbers or strings with the parameters introduced by the user.

With this object, I then declare another:

let queryObj = {
  name: query.name,
  address: { city: query.address.city, state: query.address.state },
  operations: {
    rentAvailable: query.operations.rentAvailable,
    purchaseAvailable: query.operations.purchaseAvailable,
    shortRentAvailable: query.operations.shortRentAvailable,
    agentRentPrice: {
      $gte: query.operations.agentRentPriceMin,
      $lte: query.operations.agentRentPriceMax,
    },
    agentSellingPrice: {
      $gte: query.operations.agentSellingPriceMin,
      $lte: query.operations.agentSellingPriceMax,
    },
    agentShortRentPrice: {
      $gte: query.operations.agentShortRentPriceMin,
      $lte: query.operations.agentShortRentPriceMax,
    },
  },
  features: {
    bathrooms: { $gte: query.features.bathroomsMin },
    dorms: { $gte: query.features.dormsMin },
    beds: { $gte: query.features.bedsMin },
    amenities: { $in: query.features.amenities },
  },
}

Finally I reduce this object, removing any "null", "false", "" and "{}" values.

For example, the user searches for: house available for renting, in New York, with 2 bedrooms, with a pool, and max Rent price of $10.000

So, req.body.query will be

{
  name: null,
  address: { city: "New York", state: null },
  operations: {
    rentAvailable: true,
    purchaseAvailable: false,
    shortRentAvailable: false,
    agentRentPriceMin: null,
    agentSellingPriceMin: null,
    agentShortRentPriceMin: null,
    agentRentPriceMax: 10000,
    agentSellingPriceMax: null,
    agentShortRentPriceMax: null,
  },
  features: {
    bathroomsMin: null,
    dormsMin: 2,
    bedsMin: null,
    amenities: ["pool"],
  },
}

Next, I declare

let queryObj = {
  name: null,
  address: { city: "New York", state: null },
  operations: {
    rentAvailable: true,
    purchaseAvailable: false,
    shortRentAvailable: false,
    agentRentPrice: {
      $gte: null,
      $lte: 10000,
    },
    agentSellingPrice: {
      $gte: null,
      $lte: null,
    },
    agentShortRentPrice: {
      $gte: null,
      $lte: null,
    },
  },
  features: {
    bathrooms: { $gte: null },
    dorms: { $gte: 2 },
    beds: { $gte: null },
    amenities: { $in: ["pool"] },
  },
};

I have a function here that reduces this object (removing "false" "null" "" and "{}" values):


queryObj = {
  address: { city: "New York" },
  operations: {
    rentAvailable: true,
    agentRentPrice: {
      $lte: 10000,
    },
  },
  features: {
    dorms: { $gte: 2 },
    amenities: { $in: ["pool"] },
  },
};

As you can see, "query" (and therefore "queryObj") will vary a lot; the user may or may not use any of the available search parameters, so (as i see it) it is not possible to "hard-code" the queryObj structure

I've tried using

Home.aggregate([{$match:queryObj}]) 

without success (returns no results).

Is it possible what I'm trying to do?

2

There are 2 answers

1
lpizzinidev On BEST ANSWER

Try to use a function to build the filter:

const body = {
  name: null,
  address: { city: 'New York', state: null },
  operations: {
    rentAvailable: true,
    purchaseAvailable: false,
    shortRentAvailable: false,
    agentRentPriceMin: null,
    agentSellingPriceMin: null,
    agentShortRentPriceMin: null,
    agentRentPriceMax: 10000,
    agentSellingPriceMax: null,
    agentShortRentPriceMax: null,
  },
  features: {
    bathroomsMin: null,
    dormsMin: 2,
    bedsMin: null,
    amenities: ['pool'],
  },
};

const buildFilter = (body) => {
  const filter = {};
  if (body.name != null) {
    filter['name'] = body.name;
  }
  if (body.address.city != null) {
    filter['address.city'] = body.address.city;
  }
  if (body.address.state != null) {
    filter['address.state'] = body.address.state;
  }
  if (body.operations.rentAvailable != null) {
    filter['operations.rentAvailable'] = body.operations.rentAvailable;
  }
  if (body.operations.purchaseAvailable != null) {
    filter['operations.purchaseAvailable'] = body.operations.purchaseAvailable;
  }
  if (body.operations.shortRentAvailable != null) {
    filter['operations.shortRentAvailable'] =
      body.operations.shortRentAvailable;
  }
  let agentRentPrice = {};
  if (body.operations.agentRentPriceMin != null) {
    agentRentPrice = { $gte: body.operations.agentRentPriceMin };
  }
  if (body.operations.agentRentPriceMax != null) {
    agentRentPrice = {
      ...agentRentPrice,
      $lte: body.operations.agentRentPriceMax,
    };
  }
  if (Object.keys(agentRentPrice).length > 0) {
    filter['operations.agentRentPrice'] = agentRentPrice;
  }
  let agentSellingPrice = {};
  if (body.operations.agentSellingPriceMin != null) {
    agentSellingPrice = { $gte: body.operations.agentSellingPriceMin };
  }
  if (body.operations.agentSellingPriceMax != null) {
    agentSellingPrice = {
      ...agentSellingPrice,
      $lte: body.operations.agentSellingPriceMax,
    };
  }
  if (Object.keys(agentSellingPrice).length > 0) {
    filter['operations.agentSellingPrice'] = agentSellingPrice;
  }
  let agentShortRentPrice = {};
  if (body.operations.agentShortRentPriceMin != null) {
    agentShortRentPrice = { $gte: body.operations.agentShortRentPriceMin };
  }
  if (body.operations.agentShortRentPriceMax != null) {
    agentShortRentPrice = {
      ...agentShortRentPrice,
      $lte: body.operations.agentShortRentPriceMax,
    };
  }
  if (Object.keys(agentShortRentPrice).length > 0) {
    filter['operations.agentShortRentPrice'] = agentShortRentPrice;
  }
  if (body.features.bathrooms != null) {
    filter['features.bathrooms'] = { $gte: body.features.bathrooms };
  }
  if (body.features.dorms != null) {
    filter['features.dorms'] = { $gte: body.features.dorms };
  }
  if (body.features.beds != null) {
    filter['features.beds'] = { $gte: body.features.beds };
  }
  if (body.features.amenities != null) {
    filter['features.amenities'] = { $in: body.features.amenities };
  }
  return filter;
};

console.log(buildFilter(body));

A little verbose, but it is due to the fact that you don't have a 1-on-1 correspondence between the attributes of the body and your filter's properties.

0
NeNaD On

You can solve this by creating the filter dynamically on the backend. Basically, you will start with an empty filter, and then you will check each user input, and add it to the filter if exists. That way, you don't have to worry if the user will send multiple filters, or if it will not send filters at all. Everything will be added dynamically.

const queryObj = {
  address: { city: "New York" },
  operations: {
    rentAvailable: true,
    agentRentPrice: {
      $lte: 10000,
    },
  },
  features: {
    dorms: { $gte: 2 },
    amenities: { $in: ["pool"] },
  },
};

// Initialize empty filter.
const filter = {};

if (queryObj?.address?.city) filter['address.city'] = queryObj.address.city;

if (typeof queryObj?.operations?.rentAvailable === boolean) filter['operations.rentAvailable'] = queryObj.operations.rentAvailable;

if (queryObj?.operations?.agentRentPrice?.$lte) filter['operations.agentRentPrice'] = { $lte: queryObj.operations.agentRentPrice.$lte };