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
.
$ 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 как сделать миграцию с нуля.)
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.
Мы можем запустить миграцию, известную как “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, чтобы идентифицировать каждую строку уникально.
Вы, вероятно, заметили, что запуск 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