# Racer's Model

Для обеспечения реактивности **StartupJS** использует возможности **Racer**. Такое определение модуля предоставляет [репозиторий Racer](https://github.com/derbyjs/racer):

**Racer** - это механизм синхронизации моделей в реальном времени для **Node.js**. Используя **ShareDB**, несколько пользователей могут взаимодействовать с одними и теми же данными в реальном времени с помощью Operational Transformation, сложного алгоритма разрешения конфликтов, который работает в реальном времени и с автономными клиентами. ShareDB также поддерживает PubSub на нескольких серверах для горизонтального масштабирования. Клиенты могут производить подписки и выборки данных в терминах запросов и конкретных документов, поэтому разные клиенты могут быть подписаны на разные перекрывающиеся наборы данных. Помимо этого сложного внутреннего интерфейса, Racer предоставляет простую модель и интерфейс событий для написания логики приложения.

Главные преимущества заключаются в следующем:

- **Обновления в реальном времени** - методы модели автоматически распространяют изменения среди клиентов браузера и серверов Node в реальном времени. Адаптер racer-browserchannel рекомендуется для подключения браузеров в реальном времени.

- **Подписки на запросы в реальном времени** - клиенты могут подписаться на ограниченный набор информации, относящейся к текущему сеансу. Поддерживаются подписки как на документы, так и на запросы в реальном времени. В настоящее время поддерживаются произвольные запросы Mongo.

- **Разрешение конфликтов** - используя алгоритм JSON Operational Transformation ShareDB, Racer будет генерировать события, которые приводят конфликтующие состояния клиентов в конечную согласованность. В дополнение к синхронному API у методов модели есть обратные вызовы для обработки разрешенного состояния после ответа сервера.

- **Немедленное взаимодействие** - методы модели вступают в силу немедленно. Тем временем Racer отправляет обновления на сервер и проверяет наличие конфликтов. Если обновления успешны, они сохраняются и транслируются другим клиентам.

- **Автономный режим** - поскольку методы модели применяются немедленно, клиенты продолжают работать в автономном режиме. Любые изменения локального клиента или глобального состояния автоматически синхронизируются при повторном подключении.

- **Единый серверный и клиентский интерфейс** - один и тот же интерфейс модели может использоваться на сервере для начального рендеринга страницы и на клиенте для взаимодействия с пользователем. Racer поддерживает объединение моделей, созданных на сервере, и их повторную инициализацию в том же состоянии в браузере.

- **Постоянное хранилище** - Racer использует ShareDB для ведения журнала всех операций с данными, публикации операций на нескольких внешних серверах и автоматического сохранения документов. В настоящее время он поддерживает MongoDB и может быть легко адаптирован для поддержки других хранилищ документов.

Вы уже работали с моделью в главе где рассматривалось создание приложения TodoList. Модель является основным понятием в startupjs. С ее помощью мы можем выполнить любую из CRUD операций очень легко. В startupjs для racer реализована [orm](https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping) (Object-Relational Mapping), чтобы связать базу данных с кодом. Если говорить простыми словами, то orm это система, которая предоставляет функционал для работы с базой данных в виде методов обьекта. То есть используя orm у вас есть обьект отвечающий за конкретный документ или коллекцию.

## Простыми словами

Представте себе структуру MongoDB. Здесь есть коллекции, каждая коллекция содержит документы, у каждого документа есть поля с какими-либо значениями.

Давайте смотреть на это как на город. Город - это база данных, квартал - это коллекция, дома это документы, а жильцы домов - поля. Чтобы понять как работает база данных давайте представим почтальона. Как он находит человека которому нужно отдать письмо? У него есть адрес где этот человек живет. Например Джонни Стоун живет по адресу квартал Независимости, дом 10. Давайте преобразуем это в обьектно-ориентированный вид: 'независимости.10'. Это его адрес, по нему его можно найти.

Теперь давайте попытаемся понять что такое модель. Здесь будет немного абстракции. Представьте себе воображаемую метку, которую вы можете ставить на нужный объект в городе. Например, вы хотите пометить квартал или конкретный дом в квартале, или даже конкретного человека. По этой метке вы можете быстро найти нужный вам объект. Вот такой меткой и является модель. Поставить метку называют "подпиской".

Теперь когда у нас есть модель какого либо обьекта мы можем взаимодействовать с ним. Вид взаимодействия зависит от типа данного объекта. Например, нельзя в модель человека добавить новый дом, это абсурд. А вот в квартал можно.

Теперь посмотрим на это с точки зрения программирования. Модель представляет собой объект, который имеет методы для взаимодействия с сущностью на которую он ссылается. Методы которые видоизменяют документ, называют - сетерами (от to set - задавать), методы которые возвращают значения - называют геттеры (от to get - получать). Все доступные [сеттеры](https://derbyjs.com/docs/derby-0.10/models/setters) и [геттеры](https://derbyjs.com/docs/derby-0.10/models/getters) есть в документции racer.

Важный момент заключается в том, что коллекция в racer является обьектом, следоватетельно и доступны ей методы объекта.

Есть одно важное исключение. Если в `model.query` вы использовали аггрегацию или запрашивали `{$count: true}`, то для того чтобы получить значение, нужно использовать `model.getExtra`, а не обычный `model.get`.

В startupjs модель принято именовать начиная с символа `$`. Например, `$todos, $todo` и т.д.

## Алгоритм получения модели

В данном примере мы ссылаемся на `todos`, что по сути означает что мы ссылаемся на коллекцию `todos` в `mongodb`.

1. Взять выбранную область для модели (она называется scope).

```js
  const $todos = model.scope('todos')
```

2. Теперь необходимо подписаться, обратите внимание на то, что это подписка возвращает Promise. (На текущем шаге важно понимать то, что мы всего лишь получили указатель на то, что нам нужно. То есть здесь еще нет данных из `mongodb`. Но это необходимое действие, например, для получеия данных при помощи `get()`)

```js
  await $todos.subscribe()
  // or you can use
  await model.subscribe($todos)
```

3. После подписки, можно производить необходимые взаимодействия с моделью.

```js
  // Add new document to collection
  $todos.add({
    title: 'understand the third step of the algorithm',
    completed: false
  })

  // Get all data from collection
  $todos.get()
```

4. Когда все действия произведены, нужно произвести отписку модели.

```js
  $todos.unsubscribe()
```

## Модель коллекции

Давайте рассмотрим несколько базовых примеров. Startupjs использует MongoDB в качестве бызы данных. Поэтому все примеры используют синтаксис этой базы данных.

```jsx
const [todos, $todos] = useQuery('todos', {})

<Content padding>
  <H1>TODOList</H1>
    {todos.map(todo => (
      <Card key={todo.id}>
        <Row align='between' vAlign='center'>
          <Span>{todo.title}</Span>
            <Row align='between' vAlign='center'>
              <Checkbox
                value={todo.completed}
                onChange={value => changeStatus(todo.id, value)}
              />
              <Button
                icon={faTimes}
                iconColor='error'
                size='s'
                onPress={() => removeTodo(todo.id)}
              />
            </Row>
        </Row>
      </Card>
    ))}
    <Row align='between' vAlign='center'>
      <TextInput />
      <Button>Add</Button>
    </Row>
</Content>
```

Уже знакомый вам пример. Здесь `$todos` является моделью коллекции `todos`, так как `useQuery` вторым значением возвращает модель коллекции к которой происходил запрос. Теперь мы можем работать с этой моделью. Например, чтобы добавить документ в коллекцию, необходимо использовать метод `add`.

```jsx
$todos.add({
  title: 'New todo',
  completed: false
})
```

Указывать id нет необходимости, так как мы работаем с MongoDB которая сделает это за нас. Таким образом мы говрим модели `$todos` добавить новый документ с указанными полями.

## Модель документа

Теперь давайте вспомним хук `useDoc`, он позволяет получить документ по `id`. Допустим у нас есть компонент `Todo`, который принимает `id` конкретной задачи.

```jsx
function Todo({id}) {
  const [todo, $todo] = useDoc('todos', id)

  return(
    <Card key={todo.id}>
      <Row align='between' vAlign='center'>
        <Span>{todo.title}</Span>
          <Row align='between' vAlign='center'>
            <Checkbox
              value={todo.completed}
              onChange={value => changeStatus(todo.id, value)}
            />
            <Button
              icon={faTimes}
              iconColor='error'
              size='s'
              onPress={() => removeTodo(todo.id)}
            />
          </Row>
      </Row>
    </Card>
  )
}
```

Здесь `$todo` является моделью на конкретный документ коллекции `todos`. С ее помощь можно модифицировать этот документ. Например, изменить определенное поле.

```js
  $todo.set('title', 'New title')
```

Можно добавить новое поле в документ. `setEach` добавит каждое поле из объекта в документ, а если такое поле уже существует, то оно будет перезаписано на новое значение.

```js
  $todo.setEach({ newField: 'NewField' })
```

Представте себе, что этот метод проходит по каждому полю переданного объекта и делает метод `set`, где первый параметр это ключ элемента, а второй - значение элемента. То есть:
```js
  const newFields = { newField: 'NewField' }
  $todo.setEach(newFilds)
  // То же самое что:
  for (let key in newFields) {
    $todo.set(key, newFilds[key])
  }
```

Или удалить его.

```js
  $todo.del()
```

## Потренируемся

1. **НЕ ПОЛЬЗУЙТЕСЬ КАСТОМНЫМИ ХУКАМИ В ЭТОМ ЗАДАНИИ!** Необходимо использовать `model` из startupjs, который предоставляет нам доступ к глобальной модели. У вас есть переменная `userId` в которой содержится `id` юзера из коллекции `users`. Подпишитесь на него, выведите поля `name` и `age`, а затем добавьте новое поле `hobby=football`.

  ```jsx
    const [user, setUser] = useState()
    const userId = '123123123'

    function getUser() {
      const $user = model.at('users.'+userId)
      await $user.subscribe()
      const user = $user.get()
      console.log({...user})
      $user.set('hobby', 'football')
      $user.unsubscribe()
      setUser(user)
    }

    return pug`
      Div
        Button(onPress=getUser) Get User
        Div
          Span= user.name
          Span= user.age
    `
  ```

1. Есть коллекция `users`. Реализуйте функции `addUser`, `removeUser`.

  ```jsx
    const [name, setName] = useState('')
    const [age, setAge] = useState('')
    const [users, $users] = useQuery('users', {})

    function addUser() {
      // write here
    }

    function removeUser() {
      // write here
    }

    return pug`
      Div
        each user in users
          Div
          Span= user.name
          Span= user.age
          Button(onPress=removeUser) Remove user
        Div
          TextInput(value=name label='name' onChangeText=setName)
          TextInput(value=age label='age' onChangeText=setAge)
          Button(onPress=addUser) Add user
    `
  ```

1. Теперь давайте реализуем простой пример счетчика, чтобы отработать методы модели для числа. Напишите функции addCounter, reduceCounter. Но так как это 3е задание, то давайте еще реализуем функцию addAsync, которая будет увеличивать счетчик на 1 через 2 секунды после нажатия.

  ```jsx
    const [counter, $counter] = useValue()

    function addCounter() {
      // write here
    }

    function reduceCounter() {
      // write here
    }

    function addAsync() {
      // write here
    }

    return pug`
      Div
        Span= counter
        Row
          Button(onPress=addCounter) Add
          Button(onPress=reduceCounter) Reduce
          Button(onPress=addUser) Add async
    `
  ```
