# coding: utf-8

6.1.1 Миграции базы данных

Можно вспомнить из Раздела 4.4.5 что мы уже встречали, в сделанном нами классе User объекты user с атрибутами name и email Тот класс служил полезным примером, но он испытывал недостаток в критическом свойстве персистентности: когда мы создали объект User в консоли Rails, он исчез, как только мы вышли. Наша цель в этом Разделе состоит в том, чтобы создать модель для пользователей, которые не будут исчезать так легко.

Как и с классом User в Разделе 4.4.5, мы начнем с моделирования пользователя с двумя атрибутами, a name и email адрес, последний мы будем использовать в качестве уникального имени пользователя.6 (Мы добавим атрибут пароля в Разделе 7.1.) В Листинге 4.8, мы сделали это с attr_accessor методом Ruby:

class User
  attr_accessor :name, :email
  .
  .
  .
end

Напротив, при использовании Rails, чтобы смоделировать пользователей мы не должны идентифицировать атрибуты явно. Как отмечено кратко выше, чтобы хранить данные, Rails по умолчанию использует реляционную базу данных, которая состоит из таблиц составленных из строк, данных, где у каждой строки есть столбцы атрибутов данных. Например, чтобы сохранить пользователей с именами и адресами электронной почты, мы составим таблицу users со столбцами name и email (с каждой строкой, соответствующей одному пользователю). Называя столбцы таким образом, мы позволим Active Record выводить атрибуты объектов User для нас.

Давайте посмотрим, как это работает. (Если это обсуждение становится слишком абстрактным на Ваш вкус, будте терпеливы; консольные примеры, начинающиеся в Разделе 6.1.3 и скриншоты браузера базы данных в Рис. 6.3 и Рис. 6.8 должны многое прояснить. Вспомните из Раздела 5.3.1 (Листинг 5.23), что мы создавали контроллер Users (наряду с new действием) используя команду

$ rails generate controller Users new

Есть аналогичная команда для того, чтобы сделать модель: generate model; Листинг 6.1 показывает команду для генерации модели User с двумя атрибутами, name и email.

Листинг 6.1. Генерирование модели User.
$ rails generate model User name:string email:string
      invoke  active_record
      create    db/migrate/<timestamp>_create_users.rb
      create    app/models/user.rb
      invoke    rspec
      create      spec/models/user_spec.rb

(Отметьте, что, в отличие от множественного соглашения для имен контроллеров, названия моделей - в ед. числе: контроллер Users, но модель User), передавая дополнительные параметры name:string и email:string, мы говорим Rails о двух желаемых атрибутах, наряду с тем, какогои типа эти атрибуты должны быть (в данном случае, string). Сравните это с включением имен действий в Листинге 3.4 и Листинге 5.23.

Одним из результатов generate команды в Листинге 6.1 является новый файл, названный migration. Миграции обеспечивают возможность постепенного изменения структуры базы данных, так, чтобы наша модель данных могла адаптироваться к изменяющимся требованиям. В случае модели User миграция создается автоматически сценарием генерации модели; что создает таблицу users с двумя столбцами, name и email, как показано в Листинге 6.2. (Мы увидим в Разделе 6.2.4 как сделать миграцию с нуля.)

Листинг 6.2. Миграция для модели User (чтобы составить таблицу users).
db/migrate/<timestamp>_create_users.rb
class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.string :name
      t.string :email
      t.timestamps
    end
  end

  def self.down
    drop_table :users
  end
end

Отметьте, что имя миграции снабжается префиксом с временнОй отметкой, обозначающей время когда была сгенерирована миграция. В первые годы миграций, имена файлов были снабжены префиксом постепенно увеличивающихся целых чисел, которые вызвали конфликты у сотрудничающих команд, если у разных програмистов были миграции с тем же самым номером. Запрет очень невероятной одновременной работы на уровне миллисекунды, использующий временнЫе метки удобно избегает таких коллизий.

Давайте сосредоточимся на self.up методе, который использует метод Rails, называемый create_table чтобы создать таблицу в базе данных для хранения пользователей. (Использование self (сам) в self.up идентифицирует его как метод класса. Это не имеет значения сейчас, но мы узнаем о методах класса, когда сделаем собственный в Разделе 7.2.4.) Сreate_table метод принимает блок (Раздел 4.3.2) с одной блоковой переменной, в данном случае названной t (от “tаблица”). Внутри блока create_table метод использует t объект для создания name и email столбцов в базе данных, оба типа string.7 Здесь имя таблицы является множественным (users) даже при том, что название модели в ед. числе (User), что отражает лингвистическое соглашение, которому следует Rails: модель представляет единственного (отдельного) пользователя, тогда как таблица базы данных состоит из многих пользователей. Заключительная строка в блоке, t.timestamps, является специальной командой, которая создает два волшебных столбца, называемые created_at и updated_at, которые являются временнЫми отметками, которые автоматически записывают, когда данный пользователь создается и обновляется. (Мы увидим конкретные примеры волшебных столбцов в Разделе 6.1.3.) Полная модель данных, представленная этой миграцией, показана в Рис. 6.2.

Модель данных пользователи, произведенная Листингом 6.2.

Рис. 6.2: Модель данных "пользователи", произведенная Листингом 6.2.

Мы можем запустить миграцию, известную как “migrating up”, используя команду rake (Блок 2.1) следующим образом:8

$ rake db:migrate

(Можно вспомнить, что мы запускали эту команду прежде, в Разделе 1.2.5 и еще раз в Главе 2.) При первом запуске db:migrate она создает файл db/development.sqlite3, который является базой данных SQLite 9. Мы можем увидеть структуру базы данных, используя превосходный SQLite Database Browser чтобы открыть файл db/development.sqlite3 (Рис. 6.3); сравните со схемой в Рис. 6.2. Вы могли отметить, что есть один столбец в Рис. 6.3 неучтенный в миграции: id столбец. Как отмечено кратко в Разделе 2.2, этот столбец создается автоматически, и используется Rails, чтобы идентифицировать каждую строку уникально.

Рисунок 6.3: Браузер Базы данных SQLite с нашей новой users таблицей.

Рис. 6.3: SQLite Database Browser с нашей новой users таблицей. (полный размер)

Вы, вероятно, заметили, что запуск db:migrate выполняет self.up команду в файле миграции. Что, тогда делает self.down? Как Вы могли бы предположить, down миграции down, ликвидируя последствия migrating up. В нашем случае это означает сброс users таблицы в базе данных:

class CreateUsers < ActiveRecord::Migration
  .
  .
  .
  def self.down
    drop_table :users
  end
end

Вы можете выполнить down с rake используя аргумент db:rollback:

$ rake db:rollback

Это часто бывает полезным, если Вы понимаете, что есть другой столбец, который Вы хотите добавить, но при этом не желаете связываться с новой миграцией: можно откатить миграцию, добавить требуемый столбец, и затем мигрировать back up. (Это не всегда удобно, и мы узнаем, как добавить столбцы к существующей таблице в Разделе 7.1.2.)

Если Вы откатывали базу данных, migrate up снова перед продолжением:

$ rake db:migrate
# coding: utf-8