En este post aprenderás las maravillas que nos trae lodash/fp (fp viene de functional programming, genial para asegurar inmutabilidad). Lo mostramos mediante ejemplos con una guía paso a paso y usando CodePen.

Pasos

  • Abre la página de CodePen:  http://codepen.io
  • Click en create >> new  (Creamos un nuevo CodePen para que juegues con él)
  • Importa lodash/fb de la CDN: settings >> pestaña javascript >> Add external Javascript: 
    Incluye este enlace: https://cdn.jsdelivr.net/g/lodash@4(lodash.min.js+lodash.fp.min.js)
Si estás trabajando en local puedes usar npm install lodash y luego importarlo usando import {flow , set} from 'lodash/fp'
  • Para este ejemplo queremos gestionar una reserva de hotel, suponiendo que trabajaremos con una entidad como la que se muestra a continuación:
const myBooking = {
  id: '23484',
  client: {
      id: '458982',
      name: 'John Doe',
      email: 'john.doe@test.com',
      vip: false,
      business: false,
      family: false,
  }, 
  rooms: [
     {
       type: 'standard',
       beds: 2,       
     },
     {
       type: 'standard',
       beds: 1,       
     }
  ],
  extras: {
     lateCheckout: false,
     spa: false,
     minibar: false,
     seasights: false,
  }
}
  • Ahora, asumiendo que otro desarrollador hizo un sistema basado en reputación del cliente ofreceremos servicios extra. Tendremos una lista de configuraciones con algunos patrones dinámicos como "client.vip" o ["lateCheckout", "spa"] o "room[0].type" al mismo tiempo queremos conservar nuestro objeto de reserva inmutable. Qué podemos hacer?... Lodash al rescate!
  • Intentaremos crear un método para hacer una mejora de la reserva. En este recibiremos la propiedad y su valor, usaremos lodash set para, de forma dinámica, navegar entre las propiedades, asignaremos el valor y devolveremos un nuevo objeto. Añade el siguiente código en el CodePen:
const upgradeBooking = (propertypath, value, booking) => {
   return _.set(propertypath, value, booking);
}
Igual has usado la versión mutable de (set) en lodash. La versión inmutable mueve el valor "objeto" a la última posición de la lista de parámetros en lugar de en la primera.
  • Prueba la función en el CodePen
const updatedBookingA = upgradeBooking('client.vip', true, myBooking);

console.log('##### Original Booking (does not mutate) #####');
console.log(myBooking);
console.log('#########');
console.log('##### Updated Booking #####');
console.log(updatedBookingA);
console.log('#########');

El resultado que aparece en la consola es:

"##### Original Booking (does not mutate) #####"

Object {
  client: Object {
    business: false,
    email: "john.doe@test.com",
    family: false,
    id: "458982",
    name: "John Doe",
    vip: false
  },
  extras: Object {
    lateCheckout: false,
    minibar: false,
    seasights: false,
    spa: false
  },
  id: "23484",
  rooms: [Object {
  beds: 2,
  type: "standard"
}, Object {
  beds: 1,
  type: "standard"
}]
}
"#########"
"##### Updated Booking #####"

Object {
  client: Object {
    business: false,
    email: "john.doe@test.com",
    family: false,
    id: "458982",
    name: "John Doe",
+    vip: true
  },
  extras: Object {
    lateCheckout: false,
    minibar: false,
    seasights: false,
    spa: false
  },
  id: "23484",
  rooms: [Object {
  beds: 2,
  type: "standard"
}, Object {
  beds: 1,
  type: "standard"
}]
}
"#########"
  • Es hora de probar el ejemplo (haz click en el botón Run, o si tienes Auto-guardado el ejemplo estará ya ejecutándose). En la consola verás el objeto original y el actualizado.
  • ¿Qué ocurriría si el sistema de reputación aplica una mejora la primera habitación que tengas reservadas? ¿Necesitamos actualizarl este método? La respuesta es no, la función upgradeClient se encargará de manejar el array! ¿Cómo hacemos esto?:
const updatedBookingB = upgradeBooking('rooms[0].type', 'suite', updatedBookingA); console.log(updatedBookingB);
  • Supongamos que de los extras no informan de la ruta entera de la propiedad, sólo su nombre. ¿Cómo podríamos obtener la ruta entera? Interpolación de string al rescate!
const extraToUpgrade = 'lateCheckout';
const updatedBookingC = upgradeBooking(`extras.${extraToUpgrade}`, true, updatedBookingB);
console.log(updatedBookingC);
Otra opción podria ser usar la aproximación de array de parámetros desde set

El objeto final que obtenemos tras aplicar las transformaciones es:

Object {
  client: Object {
    business: false,
    email: "john.doe@test.com",
    family: false,
    id: "458982",
    name: "John Doe",
+   vip: true,
  },
  extras: Object {
+   lateCheckout: true,
    minibar: false,
    seasights: false,
    spa: false
  },
  id: "23484",
  rooms: [Object {
  beds: 2,
+ type: "suite",
}, Object {
  beds: 1,
  type: "standard"
}]
}
Quieres probarlo? Echa un ojo al ejemplo de CodePen funcionando.
  • Hacer estas actualizaciones dinámicas de forma inmutable es genial, pero no me gustaría crear objetos de reservaA, reservaB, reservaC... ¿Alguna manera de encadenar esto? Sí! Usando lodash flow, podríamos simplificar el ejemplo anterior haciendo
const extraToUpgrade = 'lateCheckout';

const finalBooking = _.flow(
  _.set('client.vip', true),
  _.set('rooms[0].type', 'suite'),
  _.set(`extras.${extraToUpgrade}`, true),
)(myBooking);

console.log(finalBooking);
Sobre flow: Está invocando a la primera función pasando myBooking como último parámetro (aplicando currying), y en cada invocación sucesiva se obtiene el valor de la anterior.
¿Quieres probarlo? Puedes hacerlo en el ejemplo de CodePen funcionando
  • Eso es genial, pero me gustaría mantener mi función upgradeBooking, ¿Qué puedo hacer? Para esto tenemos que usar currying  en nuestra función upgradeClient (más info sobre currying con un ejemplo)
- const upgradeBooking = (propertypath, value, booking) => {
+ const upgradeBoooking = (propertypath, value) => (booking) => {
   return _.set(propertypath, value, booking);
}
  • Ahora podemos usarlo dentro de lodash flow:
const finalBooking = _.flow(
  upgradeBooking('client.vip', true),
  upgradeBooking('rooms[0].type', 'suite'),
  upgradeBooking(`extras.${extraToUpgrade}`, true),
)(myBooking);

console.log(finalBooking);
Podríamos también usar la función curry de lodash y tener nuestra función upgradeBooking libre de curry :)
const upgradeBooking = _.curry((propertypath, value, booking) => {
   return _.set(propertypath, value, booking);
});


const finalBooking = _.flow(
  upgradeBooking('client.vip', true),
  upgradeBooking('rooms[0].type', 'suite'),
  upgradeBooking(`extras.${extraToUpgrade}`, true),
)(myBooking);
Quieres probarlo? Echa un ojo al ejemplo funcionando

Todo bien, pero como un compañero dice: el código es más leído que escrito. Sería buena idea agrupar toda esta "magia" en algo más legible para ayudar al siguiente desarrollador que tenga que leer este código ( o tú en dos meses) a entender lo que estamos haciendo. 

Creando algunas funciones (añadiendo semántica) tenemos:

const upgradeBasic = (key, value) => (booking) =>
  upgradeBooking(`client.${key}`, value)(booking);

const upgradeRoom = (key, value) => (booking) =>
  upgradeBooking(`rooms[0].${key}`, value)(booking);

const upgradeExtras = (key, value) => (booking) =>
  upgradeBooking(`extras.${key}`, value)(booking);
  • Ahora la secuencia dentro de flow es más legible
const finalBooking = _.flow(
  upgradeBasic('vip', true),
  upgradeRoom('type', 'suite'),
  upgradeExtras(extraToUpgrade, true),
)(myBooking);

console.log(finalBooking);
Quieres probarlo? Aquí tienes el CodePen de ejemplo

Espero que hayas aprendido y disfrutado con este post. Aquí tienes todos los ejemplos de CodePen: