简体   繁体   中英

Merge two arrays connected by an intermediate array with Ramda

I've recently started using Ramda to work with responses from JSONAPI. I am having some trouble dealing with complex relationships and figuring out ways to get the data I want from multiple objects.

A user is assigned to a user_role, which is assigned to a role. A role can have many user_roles, but a user_role is assigned to only one role. A user can have many user_roles, but for the sake of simplicity I've only assigned one user_role to each user.

My goal is to get the role that is referenced in the user_role and place it in a new "included" object in the user object.

For example:

Take these three sets of data, users, user_roles, and roles:

const users = [
   {   
     id: 1, 
     attributes: {
       firstName: "Bob",
       lastName: "Lee"
     }, 
     relationships: {
       user_roles: {
         data: {
           id: 1, 
           type: "user_roles"
         }
       }
     },
     type: "users"
   },
   {   
     id: 2, 
     attributes: {
       firstName: "Kevin",
       lastName: "Smith"
     }, 
     relationships: {
       user_role: {
          data: {
            id: 2, 
            type: "user_roles"
          }
       }
     },
     type: "users"
   },
 ];

 const user_roles = [
   {
     id: 1,
     attributes: {
       createdAt: "7/3/2018",
       updatedAt: "7/3/2018"
     },
     relationships: {
       role: {
         data: {
           id: 3,
           type: "roles"
         }
       }
     },
     type: "user_roles"
   },
   {
     id: 2,
     attributes: {
       createdAt: "7/1/2018",
       updatedAt: "7/1/2018"
     },
     relationships: {
       role: {
         data: {
           id: 4,
           type: "roles"
         }
       }
     },
     type: "user_roles"
   } 
 ]

 const roles = [
   {  
     id: 3,
     attributes: {
       name: "manager",
       description: "manages stuff"
     },
     relationships: {
       user_roles: {
         data: [
           { 
             id: 1,
             type: "user_roles"
           },
           { 
             id: 10,
             type: "user_roles"
           }
         ]
       } 
     },
     type: "roles"
   },
   {   
     id: 4,
     attributes: {
       name: "director",
       description: "directs stuff"
     },
     relationships: {
       user_roles: {
         data: [
           { 
             id: 2,
             type: "user_roles"
           }
         ]
       } 
     },
     type: "roles"
   },
 ]

What I need is a user object that looks like this:

const newUser = [
   {   
     id: 1, 
     attributes: {
       firstName: "Bob",
       lastName: "Lee"
     }, 
     relationships: {
       user_roles: {
         data: {
         id: 1, 
           type: "user_roles"
         }
       }
     },
     type: "users",
     included: [
        {
          role: {
            name: "manager",
            description: "manages stuff"
          }
        }
     ]
   },
   {   
     id: 2, 
     attributes: {
       firstName: "Kevin",
       lastName: "Smith"
     }, 
     relationships: {
       user_role: {
         data: {
           id: 2, 
           type: "user_roles"
         }
       }
    },
    type: "users",
      included: [
        { 
          role: {
            name: "director",
            description: "directs stuff"
          }
        }
      ]
    }, 
  ];

I learned how to merge two arrays together, but having this "intermediate" array has really thrown me off and I'm having trouble even figuring out where to start with this!

My suggestion would be to break up the parts into separate functions and then compose them back together.

Note: in the example below I have updated the user_roles property nested within the user object to be an array of user roles, as suggested in your description.

First off, if these items will be looked up by id frequently, I would suggest creating indexed versions of these lists

const rolesIdx = R.indexBy(R.prop('id'), roles)
const userRolesIdx = R.indexBy(R.prop('id'), user_roles)

Then we can create a pipeline of functions that will create the required shape of elements that will end up in the included array, when given a user_role object.

const attributesForUserRole = R.pipe(
  R.path(['data', 'id']),
  R.flip(R.prop)(userRolesIdx),
  R.path(['relationships', 'role', 'data', 'id']),
  R.flip(R.prop)(rolesIdx),
  R.prop('attributes'),
  R.objOf('role')
)

Then we can create a function which will use the above attributesForUserRole function to add the list of roles to the included property.

const addIncludedRoles = user =>
  R.assoc(
    'included',
    R.map(attributesForUserRole, user.relationships.user_roles),
    user
  )

This could also be rewritten in point-free form, though this may reduce readability (up to you to decide).

const addIncludedRoles = R.chain(
  R.assoc('included'),
  R.o(R.map(attributesForUserRole), R.path(['relationships', 'user_roles']))
)

At this point, it is just a matter of mapping over your list of users with the addIncludedRoles function.

R.map(addIncludedRoles, users)

And all together:

 const users = [ { id: 1, attributes: { firstName: "Bob", lastName: "Lee" }, relationships: { user_roles: [{ data: { id: 1, type: "user_roles" } }] }, type: "users" }, { id: 2, attributes: { firstName: "Kevin", lastName: "Smith" }, relationships: { user_roles: [{ data: { id: 2, type: "user_roles" } }] }, type: "users" }, ]; const user_roles = [ { id: 1, attributes: { createdAt: "7/3/2018", updatedAt: "7/3/2018" }, relationships: { role: { data: { id: 3, type: "roles" } } }, type: "user_roles" }, { id: 2, attributes: { createdAt: "7/1/2018", updatedAt: "7/1/2018" }, relationships: { role: { data: { id: 4, type: "roles" } } }, type: "user_roles" } ] const roles = [ { id: 3, attributes: { name: "manager", description: "manages stuff" }, relationships: { user_roles: { data: [ { id: 1, type: "user_roles" }, { id: 10, type: "user_roles" } ] } }, type: "roles" }, { id: 4, attributes: { name: "director", description: "directs stuff" }, relationships: { user_roles: { data: [ { id: 2, type: "user_roles" } ] } }, type: "roles" }, ] const rolesIdx = R.indexBy(R.prop('id'), roles) const userRolesIdx = R.indexBy(R.prop('id'), user_roles) const attributesForUserRole = R.pipe( R.path(['data', 'id']), R.flip(R.prop)(userRolesIdx), R.path(['relationships', 'role', 'data', 'id']), R.flip(R.prop)(rolesIdx), R.prop('attributes'), R.objOf('role') ) const addIncludedRoles = user => R.assoc( 'included', R.map(attributesForUserRole, user.relationships.user_roles), user ) const result = R.map(addIncludedRoles, users) console.log(result) 
 <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script> 

The big merge:

In an attempt to solve this I have created the following:

users.concat(user_roles).concat(roles).reduce((newArray, obj, _, arr) => newArray.find(obj2 => obj2.id === obj.id) ? newArray : newArray.concat(R.mergeAll(arr.filter(o => o.id === obj.id))), [])

I'm unsure if this will satisfy your needs, but this is the way it works:

  1. Join all the arrays together; let's call it joinedArray
  2. Create a new array; newArray
  3. Iterate over joinedArray
    1. If the current id exists in the newArray don't push anything to it.
    2. Else use .filter to grab all of that id and do R.mergeAll to merge all of them.

You could use Functional programming in order to solve your problem, using the map method for arrays as fallows:

var newUser = users.map(function(user){
    var _user_role = user_roles.find(function(user_role){
        // here you get the user_role asociated to a user.
        return user_role.id === user.relationships.user_roles.data.id
    })
    var _role = roles.find(function(role){
        // here you get the role asociated to a user_role
        return role.id === _user_role.relationships.role.data.id
    })
    return {
        id: user.id,
        attributes: user.attributes,
        relationships: user.relationships,
        type: user.type,
        included: [{
            role: _role.attributes
        }]
    }
})

There could be some more optimization, I made it as simple as possible for your understanding.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM