1

I have a complex query I'm trying to make. I've got courses that have 9 weeks. I'm trying to trigger an email based on the dates of those nine weeks. So the course is like...

jf892iejf929j
{name: Course Name,
 month1: date,
 month2: date,
 month3: date
}

Now, if the date of one of those months is, let's say today, I want to send an email. This is what I have right now.

const ia = _(courses)
            .map((course, id) => ({id, ...course}))
            .filter(course => course.course === 'Course Name')
            .filter(course => moment(new Date(course.month9)).isSameOrAfter(moment(new Date())))
            .map(course => {
                if (moment(new Date(course.month1)).isSame(moment(new Date()))) {  

    HERE IS WHERE I'M TRYING TO AVOID 9 IF THENS
                }
            })
            .value()

I'm trying to get something back like

 {courseId: courseId,
  coursetype: Course,
  month: month1 (the original key,
  date: date (original value of month1 key
}

I hope this makes sense... Is there an easier way to find just the months that match the date and then hand back data?

EDITED QUESTION FOR CLARITY

So, I found a local mentor here to walk me through this, and here is what we did.

Instead of iterating inside the lodash query, we returned the value

    const ia = _(courses)
            .map((course, id) => ({id, ...course}))
            .filter(course => course.course === 'Course Name')
            .filter(course => moment(new Date(course.month9)).tz("America/Phoenix").isSameOrAfter(moment(new Date(), 'day').tz("America/Phoenix")))
            .value()

Then, we created the final object like this:

var finalObj = [];
for (var i = 0; i<ia.length; i++) {
    for (var key in ia[i]) {
        if (key.indexOf("month") >= 0) {
            if (moment(new Date(ia[i [key])).tz("America/Phoenix").isSame(moment(new Date('Mon Jul 10 2017 20:00:00 GMT-0700 (MST)'), 'day').tz("America/Phoenix"))) { 
                finalObj.push({
                    "courseId": ia[i].id,
                    "courseName": ia[i].course,
                    "month": key,
                    "date": ia[i][key]
                });
            }
        }
    }
}

This did achieve the result I wanted, but I'm wondering if there isn't a way with ES6 and Lodash to achieve the same effect.

Justin Handley
  • 1,159
  • 1
  • 7
  • 14
  • Can you add a proper working example in jsbin or in a stackoverflow's code snippet? – Canastro Feb 25 '17 at 09:20
  • I don't understan what you want, but you can return obj by value in array with `var delete_id = _.result(_.find(savedViews, function(obj) { return obj.description === view; }), 'id');` [link](http://stackoverflow.com/questions/31054021/how-to-use-lodash-to-find-and-return-an-object-from-array) – Andrii Starusiev Feb 25 '17 at 09:53
  • Hi There - I edited the question and put an example of a straight javascript function that does accomplish what we want. I'm wondering if there is a way to pull it off in ES6 / lodash? – Justin Handley Feb 27 '17 at 14:45
  • No offense, but why are you doing your data-processing client-side? This is a job for SQL, not JavaScript. – Jared Smith Feb 27 '17 at 15:00
  • No offense taken @JaredSmith - This is an app getting data from Firebase, which doesn't currently have server side querying to the degree I need it. Am I wrong about that? – Justin Handley Feb 27 '17 at 18:53
  • @JustinHandley you're probably not mistaken about Firebase's capabilities. Whether or not Firebase is sufficient for your use case is a subjective call. – Jared Smith Feb 27 '17 at 19:26

1 Answers1

0

It's a very interesting question - something that I have seen come up often, but not often addressed. If you are consuming a public api, it can be near impossible to change it on the backend.

From what I have seen, no, there is no "right" way of doing this, but for the sake of testing (and keeping future devs sane) I would encourage breaking it down a bit more.

For example, take your final snippet here:

   const ia = _(courses)
            .map((course, id) => ({id, ...course}))
            .filter(course => course.course === 'Course Name')
            .filter(course => moment(new Date(course.month9)).tz("America/Phoenix").isSameOrAfter(moment(new Date(), 'day').tz("America/Phoenix")))
            .value()

That's great, and looks all slick because you have a bunch of one liners, but revisiting it later, eh, not so much.

You could do something like:

// -- BusinessRules.js --
/** 
 * Does x, y, z check
 */
export const createCourse = (course, id) => ({id, ...course});

/** 
 * Does x, y, z check
 */
export const isCourseX = course => course.course === 'Course Name';

/** 
 * Does x, y, z check
 */
function isTimezone(course, timezone) {
    return moment(new Date(course.month9))
        .tz(timezone)
        .isSameOrAfter(
            moment(new Date(), 'day').tz(timezone)
        );
}

// Use some partial magic to create expressive verbs
export const isTimeZoneArizona = _.partial(isTimezone, "America/Phoenix");
export const isTimeZoneMelbourne = _.partial(isTimezone, "Aussie/Melbourne");


// -- App.js --
const ia = _(courses)
            .map(createCourse)
            .filter(isCourseX)
            .filter(isTimeZoneArizona)
            .value();

It looks similar to yours, but you can now build up an entire collection of business rules, as complex as you like, without "muddying" the main flow.

It's also easier to test, as you can easily test each "rule" by itself and in isolation.

You can also easily add more rules as required. The rules, once tested, can also then be shared with other filter sequences.

Edit As a sidenote, avoid chains, they can come back to haunt you - plus they require you pulling in all of lodash to use them.

With the refined methods presented above, you can simplify it anyway with something like:

const a = courses.map(createCourse).filter(isCourseX).filter(isTimeZoneArizone);
Chris
  • 54,599
  • 30
  • 149
  • 186