Joe
Joe

Reputation: 4254

Pipes, ramda with dynamic arguments

cons columnDefs = [
   {
     label: 'The_First_Name',
     value: getProp,
     args: ['firstName'] // different number of arguments depending on function
   },
   {
     label: 'City',
     value: getNestedProperty,
     args: ['location', 'city'] 
   }
]

const data = [
  {
     firstName: 'Joe',
     lastName: 'Smith',
     location: {
       city: 'London'
     }
  },
   {
     firstName: 'Anna',
     lastName: 'Andersson',
     location: {
       city: 'Stockholm'
     }
  }
]

const getProp = (object, key) => R.prop(key, object);

const getNestedProperty = (obj, args) => R.path(..args, obj);

Ramda pipe to map the data:

const tableBuilder = R.pipe(R.map); // some ramda functions in here

const rows = tableBuilder(data, columnDefs);

The wanted output:

rows output:

[
   {
      The_First_Name: 'Joe',
      city: 'London'
   },
   {
      The_First_Name: 'Anna',
      city: 'Stockholm'
   }
]

The key of each row is the label property in the columnDefs. The value is fetched from the Ramda function in the value prop together with the arguments defined in the args prop.

https://plnkr.co/edit/rOGh4zkyOEF24TLaCZ4e?p=preview

Totally stuck. Is this even possible to do with Ramda? Or is better to do it in plain javascript?

Upvotes: 0

Views: 1181

Answers (2)

Scott Sauyet
Scott Sauyet

Reputation: 50807

The following should work:

const tableBuilder = (objs, spec) => objs .map (
  obj => Object .assign ( ...spec.map (
    ( {label, value, args} ) => ( { [label]: value (obj, args) } )
  ))
)

const getProp = (object, key) => R.prop (key, object);

const getNestedProperty = (obj, args) => R.path (args, obj);

const columnDefs = [
   {label: 'The_First_Name', value: getProp, args: ['firstName']},
   {label: 'City', value: getNestedProperty, args: ['location', 'city']}
]

const data = [
  {firstName: 'Joe', lastName: 'Smith', location: {city: 'London'}},
  {firstName: 'Anna', lastName: 'Andersson', location: {city: 'Stockholm'}}
]

console .log (
  tableBuilder (data, columnDefs)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script> <script>
const {prop, path} = R                                                         </script>

But it's working mostly by happenstance. You might need to rethink a bit how your functions are defined.

This is calling the equivalent of prop (['firstName'], obj), which happens to work like prop ('firstName', obj), but only for the same reason that 'foo' + ['bar'] yields 'foobar'. It's a coincidence you probably should not depend upon.

The trouble is that you want to treat uniformly functions that take a single argument and ones that take an array of arguments. This is a problem. You probably need to make this consistent.

While you can write a Ramda gloss on this, I'm not sure it will be any more readable. Perhaps replacing Object .assign (...spec.map ( with mergeAll (spec.map ( would be cleaner. And if you don't mind changing parameter orders, there is probably a bit more clean-up. But this is already fairly readable.

Update

The answer from @customcommander convinced me that Ramda really could add some value here. This requires that you be willing to swap the parameter order for your value functions, and to be willing to call it as a fully curried function (tableBuilder (columnDefs) (data)), but it does lead to some pretty nice code.

This is mostly the work of customcommander, but I adjusted it a bit to make a more readable function:

const spec = ({label, value, args}) => ({[label]: value(args)})

const tableBuilder = pipe(
  map(spec),
  mergeAll,
  applySpec,
  map
)

const columnDefs = [
   {label: 'The_First_Name', value: prop, args: ['firstName']},
   {label: 'City', value: path, args: ['location', 'city']}
]

const data = [
  {firstName: 'Joe', lastName: 'Smith', location: {city: 'London'}},
  {firstName: 'Anna', lastName: 'Andersson', location: {city: 'Stockholm'}}
]


console .log (
  tableBuilder (columnDefs) (data)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script><script>
const {prop, path, pipe, map, mergeAll, applySpec} = R                        </script>

This has the same issue as before with prop, but do note that you can replace it here with path without harm. The main point is that all your value functions should have the same inputs (an array of values and the object to work on.) If they do, then this should work for any of them.

Upvotes: 0

customcommander
customcommander

Reputation: 18941

You can use applySpec to create an object from another one:

const obj = applySpec({
  The_First_Name: prop('firstName'),
  city: path(['location', 'city'])
})

obj({
  firstName: 'Joe',
  lastName: 'Smith',
  location: {
     city: 'London'
   }
});
//=> {"The_First_Name": "Joe", "city": "London"}

Then you can use that function to map over you array:

const data = [
  {
     firstName: 'Joe',
     lastName: 'Smith',
     location: {
       city: 'London'
     }
  },
   {
     firstName: 'Anna',
     lastName: 'Andersson',
     location: {
       city: 'Stockholm'
     }
  }
];

const obj = applySpec({
  The_First_Name: prop('firstName'),
  city: path(['location', 'city'])
})

console.log(

  map(obj, data)

);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {applySpec, prop, path, map} = R;</script>


This is how you transform columnDefs into an object that you can use with applySpec:

const spec = def => ({[def.label]: apply(def.value, def.args)});
const specs = compose(mergeAll, map(spec));

const columnDefs = [
   {
     label: 'The_First_Name',
     value: prop,
     args: ['firstName'] // different number of arguments depending on function
   },
   {
     label: 'City',
     value: path,
     args: [['location', 'city']]
   }
]

const data = [
  {
     firstName: 'Joe',
     lastName: 'Smith',
     location: {
       city: 'London'
     }
  },
   {
     firstName: 'Anna',
     lastName: 'Andersson',
     location: {
       city: 'Stockholm'
     }
  }
]

const spec = def => ({[def.label]: apply(def.value, def.args)});
const specs = compose(mergeAll, map(spec));

console.log(

  map(applySpec(specs(columnDefs)), data)

);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {apply, compose, mergeAll, map, prop, path, applySpec} = R;</script>

Upvotes: 1

Related Questions