# Работа с БД из кода

## Sequel для выполнения запросов

Настало время для работы с SQL прямо из Ruby кода. Для того, чтобы это было возможно, необходимо установить библиотеку sequel

```ruby
gem 'sequel', '~> 5.69'
```

Также СУБД бывают различные и каждая из СУБД предоставляет свой собственный API для различных языков программирования. В Ruby для работы с SQLite необходим дополнительный гем

Теперь мы можем получить доступ до Sequel в нашем коде.

Для того, чтобы начать посылать запросы, нам нужно установить соединение с базой данных:

```ruby
require 'sequel'

DB = Sequel.connect('sqlite://shop.db') # указываем путь до файла БД относительно .rb файла
# Также можно использовать именованные параметры на основе Hash. В данном случае необходимо обязательно укзаать значение для adapter
# DB = Sequel.connect(adapter: :sqlite, user: 'user', password: 'password', host: 'host', port: port, database: 'database_name.db')
```

### DDL операции

Sequel предоставляет нам удобный DSL (domain-specific language) для выполнения DDL операций. Например, так будет выглядеть операция для создания таблицы items:

```ruby
# В качестве аргумента передает название таблицы в виде символа, а также блок кода
DB.create_table :items do
  primary_key :id # внутри которого с помощью методов описываем нашу таблицу
  column :manufacture, String, null: false
  column :model, String, null: false 
  column :color, String, null: false
  column :price, Integer null: false,
  column :date_added, Time, null: false
end
```

В данном примере мы вызывали метод `.column` в который передавали:

1. название колонки
2. класс Ruby, который имеет сопоставление с типами SQL
3. параметры, передаваемые в виде хэша

## DML операции

После того как таблица создана, рассмотрим пример запроса для получения товаров стоимостью выше 10 000 рублей отсортированные по цене в порядке убиывания

```ruby
table = DB[:items]

result = table
  .where{price > 10_000} # создаем блок кода с передачей условия в него
  .order(Sequel.desc(:price)) # вызываем метод для сортировки

p result # Получаем массив значений в виде хэша
```

Мы можем также добавлять значения в нашу таблицу:

```ruby
table.insert(
  manUfacture: "apple", 
  model: "Iphone 14", 
  color: "Red", 
  price: 120_000,
  date_added: Time.now
) 
```

Точно также как и удалять:

```ruby
table.where{id == 1}.delete
```

## Sequel как ORM

Чаще всего встречается использование ORM для работы с базой данных. ORM - Object Relational Mapping. По сути, это работа с таблицами как с Ruby объектами, где каждый атрибут объекта - атрибут таблицы. Давайте создадим такой класс для нашей таблицы items.

```ruby
require 'sequel'

# Sequel обязывает нас называть наши классы как таблицы в единственном числе
# Он использует название класса для определния названия таблицы
# если нам нужно изменить это поведение, 
# то необходимо наследоваться с использованием названия в качестве аргумента (Sequel::Model(:items))
class Item < Sequel::Model
end

Item.where{price > 10_000}.order(Sequel.desc(:price)) 
```

Важно при этом создать объект соединения с помощью `Sequel.connect(...)`, так как при создании модели будет использоваться неявно(без использования переменной) ранее созданное соеднинение. Сначала создаем соединение, потом инициализируем модели с помощью `require`.

### Ассоциации

Кроме того, в моделях нам доступны ассоциации. Ассоциации позволяют одной модели ссылаться на другие в случае, если между ними есть связь в виде FK.

Рассмотрим основные связи:

* many\_to\_one - ассоциация описывает отношение "многие к одному" между двумя таблицами в базе данных. Это означает, что одна запись в "родительской" таблице может быть связана с множеством записей в "дочерней" таблице, но каждая запись "дочерней" таблицы имеет связь только с одной записью из "родительской" таблицы.

Пример использования ассоциации many\_to\_one для двух таблиц: `students` и `classrooms`. Здесь один класс (classroom) может иметь много студентов (students), но каждый студент может быть только в одном классе.

Структура двух таблиц:

`students`

| id | name  | classroom\_id |
| -- | ----- | ------------- |
| 1  | Alice | 1             |
| 2  | Bob   | 1             |

`classrooms`

| id | name       |
| -- | ---------- |
| 1  | Math Class |

Объявление моделей с ассоциацией в Sequel:

```ruby
class Student < Sequel::Model
  many_to_one :classroom
end

class Classroom < Sequel::Model
  one_to_many :students
end
```

Теперь вы можете получить класс (classroom) каждого студента с помощью ассоциации many\_to\_one:

```ruby
student = Student.where(id: 1).first
classroom = student.classroom
puts classroom.name # Выводит: "Math Class"
```

В данном случае, ассоциация many\_to\_one позволила установить связь между таблицами таким образом, что для каждого объекта Student можно легко получить соответствующий объект `Classroom`, без необходимости писать дополнительные SQL-запросы.

* one\_to\_many - ассоциация описывает отношение "многие ко многим" между двумя таблицами в базе данных. Это означает, что одна запись в первой таблице может быть связана с несколькими записями во второй таблице, и наоборот, одна запись во второй таблице может быть связана с несколькими записями в первой таблице.

Для реализации такого отношения обычно используется третья таблица, называемая "связующей" (join) таблицей, которая содержит внешние ключи (foreign keys) для обеих таблиц и связывает их записи.

Пример использования ассоциации many\_to\_many для таблиц `students`, `courses`, и связующей таблицы `enrollments`. Здесь один студент может быть записан на несколько курсов, и каждый курс может иметь несколько студентов.

Структура таблиц:

`students`

| id | name  |
| -- | ----- |
| 1  | Alice |

`courses`

| id | name    |
| -- | ------- |
| 1  | Math    |
| 2  | History |

`enrollments`

| student\_id | course\_id |
| ----------- | ---------- |
| 1           | 1          |
| 1           | 2          |

Объявление моделей с ассоциацией в Sequel:

```ruby
class Student < Sequel::Model
  many_to_many :courses, join_table: :enrollments
end

class Course < Sequel::Model
  many_to_many :students, join_table: :enrollments
end
```

Теперь вы можете получить все курсы, на которые записан студент, или все студенты, которые записаны на курс, с помощью ассоциации many\_to\_many:

```ruby
student = Student.where(id: 1).first
courses = student.courses
courses.each { |course| puts course.name } # Выводит: "Math", "History"

course = Course.where(id: 1).first
students = course.students
students.each { |student| puts student.name } # Выводит: "Alice"
```

Таким образом, ассоциация many\_to\_many позволяет установить связь многие ко многим между двумя таблицами с использованием связующей таблицы, и предоставляет удобный способ получения и работы с этими связями на уровне объектов Ruby и моделей Sequel.

Обратите внимание на следующие правила и рекомендации по составлению названий таблиц, классов и ассоциаций в контексте таблиц базы данных и объектно-реляционного отображения, такого как Sequel:

1. Таблицы:
   * Обычно названия таблиц должны быть во множественном числе и в нижнем регистре (например, `students`, bo\`oks).
   * Используйте подчеркивания для разделения слов в названиях таблиц (например, book\_authors, order\_items).
   * Старайтесь делать названия короткими и ясными, чтобы они отражали назначение таблицы.
2. Классы и модели:
   * Имена классов и моделей должны быть в единственном числе и в формате CamelCase (например, Student, Book).
   * Каждое слово в имени класса должно начинаться с заглавной буквы и не содержать подчеркиваний (например, BookAuthor, OrderItem).
3. Ассоциации:
   * Названия ассоциаций `one_to_many` и `many_to_many` должны быть во множественном числе и в нижнем регистре, чтобы они соответствовали названиям таблиц (например, `students`, `books`).
   * Названия ассоциации `many_to_one` должно быть в единственном числе и в нижнем регистре, чтобы оно соответствовали названию класса и модели в нижнем регистре (например, `student`, `book`).

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

## Задание university

## Задание todo\_api

Напишите используя Sinatra и Sequel приложение для работы со списком дел, которое:

* Добавляет новое дело в базу данных
* Изменяет дело по его идентификатору
* Удаляет дело по его идентификатору
* Показывает список дел **постранично**

Рекомендации:

* Требований как должна выглядеть модель дела здесь нет, поэтому можете включить свою фантазию и придумать
* Для того, чтобы модель Sequel могла бы быть преобразована в JSON, вам необходимо [включить JSON сериализацию](https://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/JsonSerializer.html)
