浏览代码

SVI Добавление занятия 4

SVI 2 年之前
父节点
当前提交
e8d4c12a5a
共有 2 个文件被更改,包括 325 次插入11 次删除
  1. 11 11
      docs/lesson03.md
  2. 314 0
      docs/lesson04.md

+ 11 - 11
docs/lesson03.md

@@ -73,18 +73,18 @@ func rewrite(rw IWriterReader){
 
 Вызову на запись -- не интересна возможность чтения.
 
-Вызову на запись и чтение одновременно требует обоих методов.
+Вызов на запись и чтение одновременно требует обоих методов.
 
-Объект передаваемый во все три вызова может __удовлетворять__ как конкретным интерфейсу, так и все сразу.
+Объект передаваемый во все три вызова может __удовлетворять__ как конкретным интерфейсу, так и всем сразу.
 
 Конкретный вызов с требованием интерфейса:
 
 - нужное поведение требует;
-- ненужно поведение проигнорирует и обратиться к такому поведению из интерфейса невозможно.
+- лишнее поведение проигнорирует и обратиться к такому неописанному поведению из интерфейса невозможно.
 
 ## Сохранение алгоритма
 
-Алгоритм может быть расположен в вендоринге. Алгоритм сложный, специфичный, обобщённый и покрыт тестами. Как передать туда объект нового типа -- интерфейс.
+Алгоритм может быть расположен в вендоринге. Алгоритм сложный, специфичный, обобщённый и покрыт тестами. Как передать туда объект нового типа -- только через интерфейс.
 
 Типичный пример: пакет `sort` из стандартной библиотеки:
 
@@ -97,7 +97,7 @@ type Sort interface {
 }
 ```
 
-_Не важно_ какой тип реализует этот интерфейс -- он быдет имет ьвозможность сортировки _оптимальным_ способом.
+_Не важно_ какой тип реализует этот интерфейс -- он будет иметь возможность сортировки _оптимальным_ способом.
 
 **Объекты разные -- алгоритм один**.
 
@@ -105,8 +105,8 @@ _Не важно_ какой тип реализует этот интерфей
 
 Интерфейсы пригодятся:
 
-- нужна БД, а она недоступна но локальной машине разработчика;
-- нужно имитировать отстутсвие связи с шиной данных;
+- нужна БД, а она недоступна на локальной машине разработчика;
+- нужно имитировать отсутствие связи с шиной данных;
 - нужно имитировать внезапный сбой.
 
 Пример:
@@ -129,7 +129,7 @@ func (sf *Link)Get()(string, error){
 }
 
 type MockLink struct{
-    IsBadLink_ bool // Устанавливаемый извне признак отсутствия связи для тесто
+    IsBadLink_ bool // Устанавливаемый извне признак отсутствия связи для тестов
 }
 
 func (sf *MockLink)Get()(string, error){
@@ -140,9 +140,9 @@ func (sf *MockLink)Get()(string, error){
 }
 ```
 
-Подставляя во время тестов `MockLink` можно сымитировать любую ситуацию сбоя реального клиента.
+Подставляя во время тестов `MockLink` можно смоделировать любую ситуацию сбоя реального клиента.
 
-Подставляя любые структуры мок-клиента БД -- можно сымитировать любую ситуацию с данными в БД
+Подставляя любые структуры мок-клиента БД -- можно смоделировать любую ситуацию с данными в БД
 (в том числе -- обрыв связи, испорченные данные, неожиданные данные, задержки при перегрузках базы и т.п.)
 
 ## Скрытие деталей реализации объекта
@@ -204,4 +204,4 @@ func Get(link ILink)(string, error){
 - нужно выработать привычку делать _узкие_ интерфейсы;
 - можно _случайно_ удовлетворить требованиям интерфейса.
 
-**Интерфейс -- это полезный инструмент, позволяющий решать проблемы связности, развития, изоляции.**
+**Интерфейс -- это полезный инструмент, позволяющий решать проблемы связности, архитектурного развития, изоляции.**

+ 314 - 0
docs/lesson04.md

@@ -101,4 +101,318 @@ type Model struct {
   - `-:all` no read/write/migrate permission
   - `comment`	add comment for field when migration
 
+## Создание записи
 
+```golang
+user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
+
+result := db.Create(&user) // ОБЯЗАТЕЛЬНО передавать ссылку на Create
+
+users := []*User{
+  User{Name: "Jinzhu", Age: 18, Birthday: time.Now()},
+  User{Name: "Jackson", Age: 19, Birthday: time.Now()},
+}
+
+result := db.Create(users) // Передача нескольких записей сразу
+
+// Создать только с указанными полями
+db.Select("Name", "Age", "CreatedAt").Create(&user)
+
+// Инорировать при создании указанные поля
+db.Omit("Name", "Age", "CreatedAt").Create(&user)
+```
+
+Более сложный пример при вставке с решением конфликтов (и др.) есть в документации.
+
+## Получение записи
+
+```golang
+
+// Получить первую запись
+db.First(&user)
+
+// Получить последнюю запись
+db.Last(&user)
+
+// Получить по индексу
+db.First(&user, 10)
+
+// Получить несколько по указанным индексам в список
+var users []User
+db.Find(&users, []int{1,2,3})
+
+// Сделать выборку по условию
+db.Where("name <> ?", "jinzhu").Find(&users)
+db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
+db.Where("name LIKE ?", "%jin%").Find(&users)
+db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
+// С доаолнительным условием
+db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
+
+// По указанным полям
+db.Select("name", "age").Find(&users)
+
+// Отсортировать по полям
+db.Order("age desc, name").Find(&users)
+
+// Ограничить тремя записями
+db.Limit(3).Find(&users)
+
+// Ограничить 10 записей со смещением 5
+db.Limit(10).Offset(5).Find(&users)
+```
+
+Также можно объединять данные из разных таблиц
+
+```golang
+type result struct {
+  Name  string
+  Email string
+}
+
+// Из таблицы users выбрать имя, из таблицы emails выбрать email, где user.id совпал
+db.Model(&User{}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{})
+// SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id
+
+// Решение объединения данных с предзагрузкой данных
+db.Joins("Company").Find(&users)
+// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id`;
+
+// Внутреннее объединение
+db.InnerJoins("Company").Find(&users)
+// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name`
+```
+
+Есть гораздо более сложные примеры, можно найти в документации
+
+## Цикл по записям
+
+```golang
+
+rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
+defer rows.Close()
+
+for rows.Next() {
+    var user User
+    // ScanRows метод в `gorm.DB`, может быть использован для сканирования записей
+    db.ScanRows(rows, &user)
+
+    // Что-то делаем
+}
+
+// работа почками записей по 100 штук
+result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
+    for _, result := range results {
+        // пакетная обработка найденных записей
+    }
+
+    tx.Save(&results)
+
+    // tx.RowsAffected // число записей в этой пачке
+
+    // batch // Пачка 1, 2, 3
+
+    // returns error will stop future batches
+    return nil
+})
+```
+
+## Перехватчики/хуки
+
+```golang
+func (u *User) AfterFind(tx *gorm.DB) (err error) {
+  if u.Role == "" {
+    u.Role = "user"
+  }
+  return
+}
+```
+
+## Обновление записей
+
+```golang
+
+db.First(&user)
+
+user.Name = "jinzhu 2"
+user.Age = 100
+db.Save(&user)
+
+// Принудительное обновление поля
+db.Save(&User{Name: "jinzhu", Age: 100})
+
+// Обновление по условию
+db.Model(&user).Where("active = ?", true).Update("name", "hello")
+// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;
+
+
+// Обновление нескольких полей
+db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
+
+// Обновление только указанных полей (в SELECT)
+db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
+
+// Обновление с подзапросом (обновить имя копании у пользователя)
+db.Table("users as u").Where("name = ?", user.Name).Update("company_name", db.Table("companies as c").Select("name").Where("c.id = u.company_id"))
+```
+
+Перехватчик перед обновлением:
+
+```golang
+func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
+  if u.Role == "admin" {
+    return errors.New("admin user not allowed to update")
+  }
+  return
+}
+```
+
+## Удаление записи
+
+```golang
+// Простое удаление. где ID=`10` (уже в структуре)
+db.Delete(&email)
+
+// Удаление с условием
+db.Where("name = ?", user.Name).Delete(&email)
+
+// Удаление с явным указанием ID
+db.Delete(&User{}, 10)
+
+// Удаление пачкой
+var users = []User{{ID: 1}, {ID: 2}, {ID: 3}}
+db.Delete(&user
+
+// Удаление пачкой
+db.Where("age = ?", 20).Delete(&User{})
+// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
+
+// Физическое удаление
+db.Unscoped().Delete(&order)
+// DELETE FROM orders WHERE id=10
+```
+
+Перехватчик перед удалением:
+
+```golang
+func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
+  if u.Role == "admin" {
+    return errors.New("admin user not allowed to delete")
+  }
+  return
+}
+```
+
+## Встроенные функции
+
+```golang
+db.Table("deleted_users").Count(&count)
+// SELECT count(1) FROM deleted_users;
+```
+
+## Сырые запросы
+
+```golang
+type Result struct {
+  ID   int
+  Name string
+  Age  int
+}
+
+// Выборка по условию
+var result Result
+db.Raw("SELECT id, name, age FROM users WHERE id = ?", 3).Scan(&result)
+
+// Выполнить что-то на севере по условию
+db.Exec("UPDATE orders SET shipped_at = ? WHERE id IN ?", time.Now(), []int64{1, 2, 3})
+
+// Посмотреть какой запрос сгенерирован
+sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
+  return tx.Model(&User{}).Where("id = ?", 100).Limit(10).Order("age desc").Find(&[]User{})
+})
+// SELECT * FROM "users" WHERE id = 100 AND "users"."deleted_at" IS NULL ORDER BY age desc LIMIT 10
+```
+
+## Предзагрузка
+
+```golang
+type User struct {
+  gorm.Model
+  Username string
+  Orders   []Order
+}
+
+type Order struct {
+  gorm.Model
+  UserID uint
+  Price  float64
+}
+
+// Прямая предзагрузка
+db.Preload("Orders").Find(&users)
+
+// Предзагрузка с помощью левого соединения
+db.Joins("Company").Joins("Manager").Joins("Account").Find(&users, "users.id IN ?", []int{1,2,3,4,5})
+
+// Предзагрузка с условиями
+db.Where("state = ?", "active").Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
+```
+
+## Использование контекста
+
+```golang
+ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
+defer cancel()
+
+db.WithContext(ctx).Find(&users)
+```
+
+## Автомиграция
+
+У автоиграции масса методов, все можно найти в документации.
+
+Ниже только часть из них:
+
+```golang
+// Создать таблицу, если не существует (самое простое)
+db.AutoMigrate(&User{})
+
+// Автомиграция по нескольким таблицам сразу (самое простое)
+db.AutoMigrate(&User{}, &Product{}, &Order{})
+
+// Создать таблицу для структуры `User`
+db.Migrator().CreateTable(&User{})
+
+// Переименовать таблицу
+db.Migrator().RenameTable(&User{}, &UserInfo{})
+
+// Добавить колонку
+db.Migrator().AddColumn(&User{}, "Name")
+
+// Удалить колонку
+db.Migrator().DropColumn(&User{}, "Name")
+```
+
+## Отображения (views)
+
+```golang
+// Запрос на создание отображения
+query := db.Model(&User{}).Where("age > ?", 20)
+db.Migrator().CreateView("users_pets", gorm.ViewOption{Query: query})
+db.Migrator().DropView("users_pets")
+```
+
+## Пользовательские типы
+
+```golang
+type User strict{
+  gorm.Model
+  Name string
+}
+
+// SetName -- устанавливает имя пользователя
+func (sf *User)SetName(name string){
+  sf.Name = name
+  db.Model(sf).Update("name", sf.Name)
+}
+```