Update object value Ramda

In previous question I tried to group arrays by parent ids and then remove them from each object - Group arrays by parent ids object Ramda.

But now I have a new issue. For example I want to update title in object with id 12.

My data model:

const stuff = {
  "31": [
    {
      "id": "11",
      "title": "ramda heeeelp"
    },
    {
      "id": "12",
      "title": "ramda 123"
    }
  ],
  "33": [
    {
      "id": "3",
      "title": "..."
    }
  ],
  "4321": [
    {
      "id": "1",
      "title": "hello world"
    }
  ]
}

Attempts:

const alter = (id, key, value) => pipe(
  values,
  flatten,
  update(...) // <= end of my attempts
  // then group again
)

alter('12', 'title', 'new heading 123')(stuff)

Answers:

Answer

You can use lenses here:

  1. Over: https://ramdajs.com/docs/#over
  2. Set: https://ramdajs.com/docs/#set

const titleLens = R.curry((key, id, data) => R.lensPath([
  key, 
  R.findIndex(R.whereEq({ id }), R.propOr([], key, data)), 
  'title'
]));

// ----
const stuff = {
  "31": [
    {
      "id": "11",
      "title": "ramda heeeelp"
    },
    {
      "id": "12",
      "title": "ramda 123"
    }
  ],
  "33": [
    {
      "id": "3",
      "title": "..."
    }
  ],
  "4321": [
    {
      "id": "1",
      "title": "hello world"
    }
  ]
}

const title3111 = titleLens('31', '11', stuff);
const result = R.set(title3111, 'DID RAMDA HELP YOU?', stuff);

console.log('result', result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js" integrity="sha256-xB25ljGZ7K2VXnq087unEnoVhvTosWWtqXB4tAtZmHU=" crossorigin="anonymous"></script>

Answer

I think this would be best done with a custom lens. Here I write a lens creator (idInObjLens) that focuses on the object with the correct id, regardless of what group it falls in. Then I write myLens to accept an id and a property name and return you a lens that focuses on that property of the object.

With those, we can use Ramda's view, set, and over to see the value, set the value, or update the value with a function:

const idInObjLens = (id) => lens (
  (obj) => {
    let index = -1
    const child = find (value => (index = findIndex (propEq ('id', id), value)) > -1, values (obj) )
    if (child) return child [index]
  },
  (val, obj) => {
    let index = -1
    const [key, value] = find (([key, value]) => (index = findIndex (propEq ('id', id), value)) > -1, toPairs (obj) )
    return assoc (key, update (index, val, value), obj)
  },
)


const myLens = (id, key) => compose (idInObjLens (id), lensProp (key))

const stuff = {"31": [{"id": "11", "title": "ramda heeeelp"},  {"id": "12", "title": "ramda 123"}], "33": [{"id": "3", "title": "..."}], "4321": [{"id": "1", "title": "hello world"}]};

[
    view (myLens ('12', 'title'), stuff),
    set  (myLens ('3',  'title'), 'new heading 123', stuff),
    over (myLens ('1',  'title'), toUpper, stuff),
] .forEach (x => console .log (x))
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {lens, find, findIndex, propEq, values, toPairs, assoc, update, compose, lensProp, view, set, over, toUpper } = R</script>

Note that the set and over will only work if that id actually exists. You might want to check using view first.

myLens is simple; it's just how lens composition works. (Notice that it seems to flow backward from regular compose; the technical reasons are interesting, but beyond the scope of an SO answer.) But idInObjLens is more complex. As with all lenses, it takes a getter and a setter. Both of them simultaneously find the object key that contains the item with the id and the index of that key in the array associated with that object key. The getter simply returns the value. The setter uses assoc to update the outer object and update to update the array inside it. All other nested objects are simply returned by reference.

This is not code to be proud of. It works, and of course that's the main thing. But I really don't like calculating the array index as a side-effect of the find call. Yet calculating it a second time just seems overkill. I also don't really like the name idInObjLens and I always feel that if I don't have a good name, I'm missing something fundamental. (I don't have the same objection to myLens, as I assume you will have a better name for this for your use-case.)

The big difference between this and the solution from Hitmand is that this lens does not require you to know up-front which key in the outer object holds the item with your id. That adds a fair bit of complexity to the solution, but makes its API much more flexible.

Answer

You map all array inside properties, and use R.when to evolve all objects with matching ids, and replace the property's (title in your case) value:

const { curry, map, when, propEq, evolve, always } = R

const fn = curry((id, prop, content) =>
  map(map( // map all objects of all properties
    when(
      propEq('id', id),  // if the id of an object matches
      evolve({ [prop]: always(content) })) // evolve it's property to the content
    )
  ))

const data = {"31":[{"id":"11","title":"ramda heeeelp"},{"id":"12","title":"ramda 123"}],"33":[{"id":"3","title":"..."}],"4321":[{"id":"1","title":"hello world"}]}

const result = fn('12', 'title', 'new heading 123')(data)

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js" integrity="sha256-xB25ljGZ7K2VXnq087unEnoVhvTosWWtqXB4tAtZmHU=" crossorigin="anonymous"></script>

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.