gosseti
gosseti

Reputation: 975

Merge fields of object to create new field with Ramda

I have a collection that I’d like to grab some values from, and merge them together to form a new value. So far I’ve looked at R.evolve, but with no luck (because medias is also a collection.

This is what I’m starting with:

[{
  "permalink": "http://example.com/1",
  "medias": [{
    "filename": "image_1.png",
  }]
}, {
  "permalink": "http://example.com/3",
  "medias": [{
    "filename": "image_3.png",
  }]
}]

And this is what I’d like to end with:

[{
  "permalink": "http://example.com/1",
  "medias": [{
    "filename": "image_1.png",
    "image_url": "http://example.com/1/image_1.png"
  }]
}, {
  "permalink": "http://example.com/3",
  "medias": [{
    "filename": "image_3.png",
    "image_url": "http://example.com/3/image_3.png"
  }]
}]

Essentially what we’re doing is prepending each filename with a permalink.

This is the kind of thing I’ve been going for so far, unfortunately when running the transform on medias, I can’t get access to the permalink property.

const mapMedia = R.map(
  // can’t get access to the `permalink` in here?
)

const transformations = {
  medias: mapMedia
}

const transform = R.compose(
  R.map(R.evolve(transformations)),
)(data)

Upvotes: 2

Views: 579

Answers (1)

Scott Sauyet
Scott Sauyet

Reputation: 50807

So the problem is that the data you need is not in scope. In my opinion, the easiest way to fix that is to add some scope!

First off, you don't need that compose call, since you're only supplying it one function. So we can replace

const transform = R.compose(
  R.map(R.evolve(transformations)),
)(data)

with just

const transform = R.map(R.evolve(transformations)),

(I also removed the call with (data), since I'm trying to build this up as a reusable function. We would then call it later with transform(data).

Now we have to add context so that transformations has the object in scope. We can do that like this:

const transform = R.map(image => R.evolve(transformations(image), image));

Of course that means transformations needs to change to take this into account. We can do that like this:

const transformations = image => ({
  medias: mapMedia(image.permalink)
})

... and this in turn requires a change to mapMedia. I thought that it should only need to know about the permalink and not the entire image, so we can pass only that as above. And mapMedia would now look like this:

const mapMedia = permalink => R.map(
  medium => // do something with `medium` and `permalink`
);

There are many ways that you could do this last step. One of them, fairly aligned to your use of evolve would be this:

const mapMedia = permalink => R.map(
  medium => assoc('image_url', permalink + '/' + medium.filename, medium)
);

Another alternative to both evolve and assoc is to investigate the various lens-related functions in Ramda, such as lensProp, over, and set.

Now, if those helper functions are only used for this purpose, you might not want them cluttering things up, and you could choose to inline them in your main function. If you liked, you might combine all the above into

const addUrls = map(
  image => evolve({
    medias: map(medium => assoc('image_url', image.permalink + '/' + medium.filename, medium)),
  }, image)
);

You can see the separate functions approach or the combined one in the Ramda REPL. For good measure, there's also a version using lenses.


(And on a separate note, the word "media" is already plural. The singular is "medium". There's no good reason for "medias".)

Upvotes: 3

Related Questions