Вставить в таблицу отношений, используя идентификатор, созданный при регистрации пользователя - PullRequest
2 голосов
/ 09 октября 2019

У меня есть две таблицы, как показано ниже

tables in database

Первая таблица предназначена для пользователей и заполняется через регистрационную форму на стороне клиента. Когда создается новый пользователь, мне нужно заполнить вторую таблицу «квот» датой, суммой и связать с идентификатором пользователя. 'User_id' используется для извлечения информации о квотах в GET и отображения на стороне клиента. У меня проблемы с использованием идентификатора для заполнения второй таблицы во время создания. Я использую knex, чтобы сделать все запросы. Буду ли я использовать объединение, чтобы связать их в knex?

сервер

hydrateRouter   // get all users
    .route('/api/user')
    .get((req, res) => {
        knexInstance
            .select('*')
            .from('hydrate_users')
            .then(results => {
                res.json(results)
            })
    })
    .post(jsonParser, (req, res, next) => {  // register new users
        const { username, glasses } = req.body;
        const password = bcrypt.hashSync(req.body.password, 8);
        const newUser = { username, password, glasses };
        knexInstance
            .insert(newUser)
            .into('hydrate_users')
            .then(user => {
                res.status(201).json(user);
            })
            .catch(next);
    })

клиент

export default class Register extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      username: '',
      password: '',
      glasses: 0
    }
  }

  handleSubmit(event) {
    event.preventDefault();
    fetch('http://localhost:8000/api/user', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(this.state) 
    })
    .then(response => response.json())
    .then(responseJSON => {
      this.props.history.push('/login');
    })
  }

серверный маршрут для отображения количества воды

hydrateRouter
    .route('/api/user/waterconsumed/:user_id')  // display water consumed/day
    .all(requireAuth)
    .get((req, res, next) => {
        const {user_id} = req.params;
        knexInstance
            .from('hydrate_quotas')
            .select('amount')
            .where('user_id', user_id)
            .first()
            .then(water => {
                res.json(water)
            })
            .catch(next)
    })

Спасибо!

1 Ответ

2 голосов
/ 09 октября 2019

Получение идентификатора вставленной строки

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

В Postgres вы можете просто использовать функцию .returning Knex, чтобы явно указать, что вы хотите, чтобы возвращался столбец id новой строки. Вам после успешной вставки. Таким образом, первая часть вашего запроса будет выглядеть так:

knexInstance
  .insert(newUser)
  .into('users')
  .returning('id')

Примечание: не все базы данных поддерживают это одинаково. В частности, если вы разрабатываете локально с использованием SQLite, он вернет количество строк, затронутых запросом , а не идентификатор, поскольку SQLite не поддерживает SQL RETURNING. Лучше всего разрабатывать локально, используя Postgres, чтобы избежать неприятных сюрпризов.

Хорошо, теперь мы знаем, какая курица нам нужна. Теперь нам нужно убедиться, что мы дождались правильного идентификатора, а затем использовать его:

.then(([ userId ]) => knexInstance
  .insert({ user_id: userId,
            date: knex.fn.now(),
            amount: userConstants.INITIAL_QUOTA_AMOUNT })
  .into('quotas')
)

Или как бы вы ни выбрали заполнить эту вторую таблицу.

Примечание: DATE является ключевым словом SQL. По этой причине, это не делает хорошее имя столбца. Как насчет created или updated вместо этого?

Отвечая разумными данными

Итак, это базовая стратегия "У меня есть идентификатор, давайте вставим в другую таблицу". Тем не менее, вы действительно хотите иметь возможность отвечать с созданным пользователем ... это похоже на разумное поведение API для ответа 201.

Что вы не хотите сделатьотвечает всей записью пользователя из базы данных, которая предоставит хэш пароля (как вы делаете в первом блоке кода из вашего вопроса). В идеале, вы, вероятно, хотели бы ответить с некоторой дружественной к UI комбинацией обеих таблиц.

К счастью, .returning также принимает аргумент массива. Это позволяет нам передавать список столбцов, на которые мы хотели бы ответить, уменьшая риск случайного раскрытия чего-либо поверхности API, которую мы бы не хотели передавать.

const userColumns = [ 'id', 'username', 'glasses' ]
const quotaColumns = [ 'amount' ]

knexInstance
  .insert(newUser)
  .into('users')
  .returning(userColumns)
  .then(([ user]) => knexInstance
    .insert({
      user_id: user.id,
      date: knex.fn.now(),
      amount: userConstants.INITIAL_QUOTA_AMOUNT
    })
    .into('quotas')
    .returning(quotaColumns)
    .then(([ quota ]) => res.status(201)
      .json({
        ...user,
        ...quota
      })
    )
  )

Асинхронизация / ожидание для удобочитаемости

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

try {
  const [ user ] = await knexInstance
    .insert(newUser)
    .into('users')
    .returning(userColumns)
  const [ quota ] = await knexInstance
    .insert({
      user_id: userId,
      date: knex.fn.now(),
      amount: userConstants.INITIAL_QUOTA_AMOUNT
    })
    .into('quotas')
    .returning(quotaColumns)

  res
    .status(201)
    .json({
      ...user,
      ...quota
    })
} catch (e) {
  next(Error("Something went wrong while inserting a user!"))
}

Примечание о транзакциях

Здесь есть несколько предположений, но одно большое: мы предполагаем, что обе вставки будут успешными. Конечно, мы обеспечиваем некоторую обработку ошибок, но все еще существует вероятность того, что первая вставка будет успешной, а вторая - по какой-либо причине или время ожидания истекло.

Как правило, мы выполняем несколько вставок в блок транзакции. Вот как Knex справляется с этим:

try {
  const userResponse = await knexInstance.transaction(async tx => {  
    const [ user ] = await tx.insert(...)
    const [ quota ] = await tx.insert(...)
    return {
      ...user,
      ...quota
    }
  })

  res
    .status(201)
    .json(userResponse)
} catch (e) {
  next(Error('...'))
}

Это хорошая общая практика для множественных вставок, которые зависят друг от друга, поскольку он устанавливает подход «все или ничего»: если что-то не получается, база данных возвращаетсяв прежнее состояние.

...