Как искать и вводить посторонний объект по идентификатору с Рамдой? - PullRequest
0 голосов
/ 24 апреля 2019

У меня есть несколько коллекций объектов с базовыми отношениями 1-ко-многим между ними. Моя цель - написать функцию (или функции, которые могут быть составлены при необходимости), чтобы разрешать / вводить поля внешних идентификаторов в сторонние объекты.

Например, у меня есть следующий объект:

const store = {
  users: [
    {
      teamId: 'team-1',
      name: 'user 1',
    },
    {
      teamId: 'team-2',
      name: 'user 2',
    },
  ],
  teams: [
    {
      id: 'team-1',
      regionId: 'region-1',
      name: 'Team 1',
    },
    {
      id: 'team-2',
      regionId: 'region-2',
      name: 'Team 2',
    }
  ],
  regions: [
    {
      id: 'region-1',
      name: 'Region 1',
    },
    {
      id: 'region-2',
      name: 'Region 2',
    },
  ],
}

Моя цель - разрешить это следующим образом:

const users = [
    {
      teamId: 'team-1',
      name: 'user 1',
      team: {
        id: 'team-1',
        regionId: 'region-1',
        region: {
          id: 'region-1',
          name: 'Region 1',
        },
        name: 'Team 1',
      }
    },
    // ...and so on
]

Я не далеко от разрешения первого уровня:

const findObject = (collection, idField = 'id') => id => R.find(R.propEq(idField, id), R.prop(collection, store))
const findTeam = findObject('teams')
const findRegion = findObject('regions')
const inject = field => R.useWith(
  R.merge,
  [R.objOf(field), R.identity]
)
const injectTeam = R.useWith(
  inject('team'),
  [findTeam]
)
const injectRegion = R.useWith(
  inject('region'),
  [findRegion]
)

R.map(injectTeam('team-1'))(store.users)

Но это слишком много для меня, чтобы обдумать это, я только что сделал с Рамдой намного проще. В идеале решение позволит мне как-то составить функции инжектора, поэтому разрешение более глубоких уровней будет необязательным.

Ответы [ 3 ]

2 голосов
/ 25 апреля 2019

Вот один из возможных способов сделать это, используя folktale / возможно .Я собираюсь начать с высокоуровневой функции makeUser, а затем двигаться в обратном направлении -

const makeUser = (user = {}) =>
  injectTeam (user)
    .chain
      ( u =>
          injectRegion (u.team)
            .map
              ( t =>
                  ({ ...u, team: t })
              )
      )
    .getOrElse (user)

Каждый injector принимает функцию get, функцию setstore -

const injectTeam =
  injector
    ( o => o.teamId
    , o => team => ({ ...o, team })
    , store.teams
    )

const injectRegion =
  injector
    ( o => o.regionId
    , o => region => ({ ...o, region })
    , store.regions
    )

Универсальный injector пытается find, используя get затем set -

const injector = (get, set, store = []) => (o = {}) =>
  find (get (o), store) .map (set (o))

Теперь мы реализуем find так, чтобыон возвращает Может быть -

const { Just, Nothing } =
  require ("folktale/maybe")

const fromNullable = v =>
  v == null
    ? Nothing ()
    : Just (v)

const find = (id = "", vs = []) =>
  fromNullable (vs .find (v => v.id === id))

Собрав все это вместе, теперь мы просто называем makeUser для каждого элемента в вашем store.users -

store.users .map (makeUser)

Выход

[ { teamId: "team-1"
  , name: "user 1"
  , team: { id: "team-1"
          , regionId: "region-1"
          , name: "Team 1"
          , region: { id: "region-1"
                    , name: "Region 1"
                    }
          }
  }
, { teamId: "team-2"
  , name: "user 2"
  , team: { id: "team-2"
          , regionId: "region-2"
          , name: "Team 2"
          , region: { id: "region-2"
                    , name: "Region 2"
                    }
          }
  }
]
2 голосов
/ 25 апреля 2019

Я использую R.converge, чтобы извлечь users и создать поиск teams и regions, а затем сопоставить users, заменив teamId командой из поиска и внутреннеделает то же самое для региона.

const { pipe, pick, map, indexBy, prop, converge, assoc, identity, flip, evolve } = R

// create a lookup of id -> object from teams and regions
const createLookup = pipe(
  pick(['teams', 'regions']),
  map(indexBy(prop('id')))
)

// add the value from the idPath in the lookup to the resultPath of the current object 
const injectFromLookup = (idKey, lookup, resultKey) => 
  converge(assoc(resultKey), [
    pipe(
      prop(idKey),
      flip(prop)(lookup),
    ),
    identity,
  ])

// extract users, create lookup, and map users to the required form
const inject = converge(
  (users, lookup) => map(
    pipe(
      injectFromLookup('teamId', prop('teams', lookup), 'team'),
      evolve({
        team: injectFromLookup('regionId', prop('regions', lookup), 'region')
      })
    )
  , users),
  [prop('users'), createLookup],
)

const store = {"users":[{"teamId":"team-1","name":"user 1"},{"teamId":"team-2","name":"user 2"}],"teams":[{"id":"team-1","regionId":"region-1","name":"Team 1"},{"id":"team-2","regionId":"region-2","name":"Team 2"}],"regions":[{"id":"region-1","name":"Region 1"},{"id":"region-2","name":"Region 2"}]}

console.log(inject(store))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
1 голос
/ 25 апреля 2019

Рамда была написана за ES5 дней и нуждалась в поддержке ES3.С момента появления ES6 есть много вещей, которые Ramda действительно улучшил для старых JS, которые теперь проще в vanilla JS.Обратите внимание, что я основатель Ramda и большой поклонник, но благодаря деструктуризации, функциям стрелок, параметрам по умолчанию, строкам шаблонов и многим другим, пропуская Ramda часто проще.

Вот простой JSрешение:

const denormalize = (steps) => (store) => 
  steps.reduce(
    (s, [key, foreignKey, target, targetId, newKey]) => ({
      ...s, 
      [key]: s[key].map(({[foreignKey]: fk, ...rest}) => ({
        ...rest,
        [newKey]: s[target].find(x => x[targetId] == fk)
      }))
    })
    , store
  )

const updateStore = denormalize([
  ['teams', 'regionId', 'regions', 'id', 'region'],
  ['users', 'teamId', 'teams', 'id', 'team'],
])

const store = {users: [{teamId: "team-1", name: "user 1"}, {teamId: "team-2", name: "user 2"}], teams: [{id: "team-1", regionId: "region-1", name: "Team 1"}, {id: "team-2", regionId: "region-2", name: "Team 2"}], regions: [{id: "region-1", name: "Region 1"}, {id: "region-2", name: "Region 2"}]}

console.log(updateStore(store).users)

Обратите внимание, что это делает всю денормализацию, возвращая объект со всеми денормализованными данными.Мы просто извлекаем users из него.Очевидно, что мы могли бы добавить еще одну обертку, чтобы возвращать только ту часть, которую мы хотим, но, похоже, это все равно будет полезно.(Таким образом, вы можете получить денормализованное свойство teams, если хотите.)

Это идет на шаг дальше вашего запроса, опуская внешние ключи и заменяя их внешним объектом.Это было просто основано на неправильном понимании того, что вы хотели, или, возможно, на мысли, что это то, что я хочу (;-)).Таким образом, результаты выглядят следующим образом:

[
  {
    name: "user 1",
    team: {
      id: "team-1",
      name: "Team 1",
      region: {
        id: "region-1",
        name: "Region 1"
      }
    }
  }, /*... */
]

Если вы хотите сохранить эти внешние ключи, код будет немного проще:

const denormalize = (steps) => (store) => 
  steps.reduce(
    (s, [key, foreignKey, target, targetId, newKey]) => ({
      ...s, 
      [key]: s[key].map(t => ({
        ...t,
        [newKey]: s[target].find(x => x[targetId] == t[foreignKey])
      }))
    })
    , store
  )

Что означают все эти строки в stepsпараметр может быть неясным.Если это так, вы можете заменить его следующим:

const config = [
  {key: 'teams', foreignKey: 'regionId', target: 'regions', targetId: 'id', newKey: 'region'},
  {key: 'users', foreignKey: 'teamId', target: 'teams', targetId: 'id', newKey: 'team'},
]

и просто изменить первую строку reduce на

    (s, {key, foreignKey, target, targetId, newKey}) => ({

(Это просто изменение с [ ... ] на{ ... }.)

...