Rahul
Rahul

Reputation: 685

underscore where with or condition (underscore, lodash or any other solution)

I implemented a mixin to add "or" condition with _.where

var arr = [{a:1,b:4}, {a:5}, {a:6}, {a:11}];
_.mixin({
   or: function(obj,arr,condition){
     return _.chain(arr).where(condition).union(obj).value();
   }
});

now i can use it like this and it works perfectly somewhat like a sql query

_.chain(arr).where({a:1}).or(arr,{a:11,b:3}).or(arr,{a:2}).value();
//returns [{a:1,b:4}]

_.chain(arr).where({a:1}).or(arr,{a:11}).or(arr,{a:2}).value();
//returns [{a:1,b:4},{a:11}]

_.chain(arr).where({a:1}).or(arr,{a:11}).or(arr,{b:4}).value();
//returns [{"a":1,"b":4},{"a":11}] --no duplicates

but I want to find a better way to just call _.or({a:1}), right now I have to pass arr everytime _.or(arr,{a:1}), as chained "or" gets first object as the result of previously executed functions.

Is there any way to get entire array in chained mixin function?

I want below to return the same result as my above implementation. (like a perfect sql query)

_.chain(arr).where({a:1}).or({a:11,b:3}).or({a:2}).value();//returns [{a:1,b:4}]

my prime focus is to get it through underscore, but really any other way like lodash or even some other library or solution will work too. Also it's not required that we use chaining, i am trying with compose as well but so far no luck. I want a solution which work perfectly in minimum lines. Any other suggestion to make it better is encouraged.

jsFiddle

Upvotes: 4

Views: 2009

Answers (3)

try-catch-finally
try-catch-finally

Reputation: 7624

I don't think extending underscore is the right way, Underscore's chaining is not desinged for this. You may "bend" it, but at cost of your desired shortness.

How chaining works

Underscore's chaining is a "stream operation". chain() stores the passed object internally in a closure and returns the Underscore interface where each method is bound to that object. When a filter function is applied, the result is returned and internally stored in a result object. The value() call will then return the internal reference.

Custom solution

You can solve this using a so called fluent interface:

// Fluent interface to filter an array with chained, alternative conditions.
// Usage: whereOr([...]).or({...}).or({...}).end()
//        whereOr([...], {...}).or({...}).end()
// Objects are _.where() conditions to subsequently apply to the array.
function whereOr(arr, condition) {
    var result = [],
        iface;

    iface =  {
        or: function(subcondition) {
            result = result.concat(_.where(arr, subcondition));
            return iface;
        },
        end: function() {
            return _.union(result);
        }
    };

    if (condition) {
        return iface.or(condition);
    }

    return iface;
}

You can then do

var arr = [{a:1,b:4}, {a:5}, {a:6}, {a:11}];
whereOr(arr).or({a:1}).or({a:11}).or({a:2}).end();

If you like, you could mix this function into Underscore, of course:

_.mixin({
    whereOr: whereOr
});

How it works

When whereOr() is called it establishes a scope that holds the reference to the original arr. It returns an object that provides the fluent functions or() and end() which both have access to the inital array. While end() will return the result, or() will apply Userscore's where() to the internal reference and add the result to result. It then returns the interface object again. This way or() provides another possibility to filter until end() returns the unionized result. In this example whereOr() optionally accepts a first filter condition as second argument.

You may compare whereOr() with chain() and end() with value().

jsFiddle

Upvotes: 1

homam
homam

Reputation: 1975

Try this:

// makeOr is a constructor for a 'chainable' type with: `{or, value}`
var makeOr = () =>  {
  var fs = []
  var obj = {
    or: f => {
      fs.push(f)
      return obj
    },
    value: (arr) => _.chain(fs).map(f => _.where(arr, f)).union().flatten().value()
  }     
  return obj
}

// override underscore where 
// if f is our 'chainable' type we evaluate it, otherwise we default to underscore where
(function() {
  where = _.where
  _.mixin({
    where: (arr, f) => (!!f.value && !!f.or) ? f.value(arr) : where(arr, f)
  })
})();

var result = _.chain(arr)
  .where(makeOr().or({a: 1}).or({a:5}).or({a:11}))
  .where({a:5}).value()

JSFiddle

PS. Check this out too: Hey Underscore, You're Doing It Wrong!

Upvotes: 1

Gary S.
Gary S.

Reputation: 146

This is an interesting problem to take on, thank you for bringing this to my attention Rahul!

I came up with a function that solves this problem using underscore helper functions (though without chaining). It takes 2 arguments, the array you start with, and the propsObj array with properties you want to match.

JsFiddle

Upvotes: 2

Related Questions