Say we have a prop.users of the shape:
const users = [
{username: 'bob', age: 30, tags: [{name: 'work', id: 1}, {name: 'boring', id: 2}]},
{username: 'jim', age: 25, tags: [{name: 'home', id: 3}, {name: 'fun', id: 4}]},
{username: 'jane', age: 30, tags: [{name: 'vacation', id: 5}, {name: 'fun', id: 4}]}
];
if you want to filter by username or age it's quite strait forward:
R.filter(R.propEq('username', 'jane'))(users);
things get tricky when you have something like tags
and you want to filter all users with name: 'fun'
this is the solution:
const hasFunTag = R.any(R.propEq('name', 'fun'))
R.filter(R.compose(hasFunTag, R.prop('tags')))(users)
my first approach to solve this was:
R.filter(R.where({tags: R.contains({name:'fun'})}))(users);
which didn't work because the tags array can contain other properties like id in our case.
this is consistent with REST APIs responses.
you could solve it by providing the id too:
R.filter(R.where({tags: R.contains({name:'fun', id: 4})}))(users);
but most times you only know name and if you want to write a function and provide the filter as argument, you are stuck.
brains tend to jump to solutions by resorting to what they already know. I knew i could solve my problem if i removed id from the array and that was something i knew how to do:
const changeTags = (tag) => R.project(['name'], tag)
const extract = users.map(user => ({...user, tags: changeTags(user.tags)}))
R.filter(R.where({tags: R.contains({name:'fun'})}))(extract);
Let's see what happens here:
R.projecthelps you pick the properties you need from the giventagobject.- use spread operator and change
tagsbased on function above. - now we can filter as intended.
It solves the querying issue i had but it also transforms the initial users array and if your code uses somehow the id in the tags array or you typed your tags array, you are stuck.
this led me to think the brain needed to learn something new. I asked ramda and FP master @asharif and my React Vienna mentor to help me out of my mental paradigm. I asked him if ramda can solve this without modifying the initial array. 15' later here comes the other way of thinking my brain needed.
const hasFunTag = R.any(R.propEq('name', 'fun'))
R.filter(R.compose(hasFunTag, R.prop('tags')))(users)
it seems like all we needed was a R.any and a R.compose in the mix.
I'm gonna try an explanation:
R.filtertakes theusersarray and looks at each object inside.R.composein ramda executes from right to left so- We need the path inside the object where
hasFunTagfunction should look.R.prop('tags', users[0])lets say, returns[{"id": 1, "name": "work"}, {"id": 2, "name": "boring"}]. R.any(R.propEq('name', 'fun'))(of above [] result)returns eithertrueorfalsedepending on whether there is anamekey with valuefun. For the above array it returns false.R.anyhelps us out of the 'removing keys before checking' issue i talked about before.- This response in turn becomes the filter predicate and removes the first object from the
usersarray.
This opens up the possibility for further abstraction of course. I'd like to explore that in the future.
A few places in your examples you refer to
data, but your data is actually in the variableusers. I think you made some typos and meant to refer tousers.