#Active Record#

##Введение##

Вы здесь, вероятно, для того, чтобы изучать веб-разработку (а иначе… вы, видимо, ошиблись с выбором места…). Независимо от того, в чем ваша цель – создать собственный сайт или начать карьеру веб-разработчика, самым важным навыком, который вы в итоге получите, является возможность мыслить логически и разбивать проблему на составляющие компоненты. После этого вы сможете обращаться к этим кусочкам по отдельности в разное время. В этом и заключается сущность проектирования и разработки.

Возможно, самая важная область при создании сайта, где требуется логическое мышление, это правильное построение вашей модели данных. Данные - это основа почти всех крупных веб-приложений, от простого блога до сложнейшей сети данных Facebook. Невнятная или слишком усложненная модель данных может серьезно помешать, когда вы пытаетесь расти, и даже сделать вашу жизнь как разработчика чрезвычайно мучительной. Если вы работаете с неверными инструментами, что-то «простое» вроде запроса отобразить все комментарии, которые один пользователь оставил к сообщениям другого пользователя, может стоить вам слишком большого количества умственных усилий и циклов работы процессора.

Так как данные – это наиболее важная часть веб-приложения, вам должно быть очень интересно и то, как Rails взаимодействует с данными. К счастью, это одна из наиболее значимых вещей, которые Rails существенно улучшил по сравнению с опциями, доступными всего несколько лет назад. Active Record – это интерфейс, предоставляемый Rails для взаимодействия между базой данных и вашим приложением. Он позволяет вам структурировать модели таких данных, как пользователи, сообщения в блогах, комментарии, последователи и др., причем осуществляется это очень логичным путем, очень похожим на простой английский язык. Если он покажется вам сложным (что иногда бывает), просто представьте себе жизнь до Active Record.

Твердое понимание Active Record поможет вам сравнительно легко освоить оставшийся Rails. Вспомните, как несколько занятий назад Model в MVC была именно той частью, которая выполняет всю тяжелую работу. В этом уроке мы изучим основы работы с моделями от их установки до построения простых ассоциаций между ними. Как обычно, мы подразумеваем, что этот материал представляет собой высокоуровневый обзор, а материалы к прочтению дадут более глубокое понимание. Более продвинутые темы будут изучены в последующих уроках.

##Пункты для размышления##

Постарайтесь ответить на предложенные вопросы. После выполнения задания попробуйте ответить на них ещё раз

  • Что такое ORM?
  • Почему Active Record более удобен, чем использование SQL?
  • Какие два шага необходимы для того, чтобы создать с помощью Active Record новую строчку в таблице вашей базы данных?
  • Что такое «генераторы» (generators) в Rails?

##Что такое ORM?##

И все-таки что такое Active Record? Вспомните, что Rails – это на самом деле 7 гемов, написанных на Ruby, которые гармонично работают вместе. Active Record – это, грубо говоря, пакет, который заботится обо всем, что связано с базами данных. А это и называется “ORM” .

ORM расшифровывается как Object-Relational-Mapping (объектно-реляционное отображение). В сущности это обозначает, что Active Record берет данные, хранящиеся в строках и столбцах таблицы базы данных, которые должны быть модифицированы или возвращены с помощью SQL инструкций (если вы используете базу данных SQL), и позволяет вам взаимодействовать с этими данными так, будто это обычный Ruby объект.

Таким образом, если я хочу получить массив, содержащий список всех пользователей, вместо написания кода по инициализации соединения с базой данных, а потом отправки запроса в виде чего-то вроде SELECT * FROM users, и потом уже конвертации полученных результатов в массив, я просто пишу User.all и Active Record выдает мне массив, заполненный объектами User (пользователь) и я могу играть ими, как хочу. Вау!

Еще более впечатляющим является то, что на самом деле не имеет никакого значения, каким типом базы данных вы пользуетесь (пока вы правильно задаете файл config/database.yml), Active Record сглаживает все различия между базами данных так, что вам уже не надо об этом думать. Вы сосредотачиваетесь на написании кода приложения, а Active Record будет думать о мельчайших подробностях того, как соединить его с вашей базой данных. Это также обозначает, что если вы переключаетесь с одной базы данных на другую, вам не нужно менять код любого крупного приложения, только некоторые конфигурационные файлы. Звучит логично, не так ли?

##Модели Rails##

Немного забежим вперед, потому что сначала имеет смысл подумать, какие отношения существуют между Rails и базой данных. На самом деле, они достаточно очевидны – вы хотите сохранить информацию о пользователях, поэтому создаете таблицу базы данных, называемую users. Вы хотите иметь доступ к данным из своего приложения, поэтому вы создаете модель User, которая представляет собой Ruby файл, наследующий от Active Record и, следовательно, использующий все те удобные методы, которые я упоминал ранее (такие как all, find, create). Одна таблица соответствует одной модели, которая наследует от Active Record.

###30 секунд о работе между моделями###

Если коротко, то Active Record позволяет вам создавать Ruby объект, который представляет собой строку в одной из таблиц базы данных, например User. Создание нового User-a это процесс, состоящий из двух шагов. Во-первых, вам нужно сделать User.new и возможно передать ему набор данных (хэш) в виде атрибутов:

 u = User.new(name: "Sven", email: "sven@theodinproject.com")

Если вы не передаете данные (hash), тогда вам нужно будет вручную добавить атрибуты, задавая их как для любого другого Ruby объекта u.name = "Sven". Вторым шагом будет сохранение этого экземпляра модели в базу данных. До этого момента все это просто сидит в памяти и испарится, если вы ничего не будете с ним делать. Чтобы сохранить, просто вызовите u.save. Вы можете одновременно осуществить оба шага, используя метод #create:

 u = User.create(name: "Sven", email: "sven@theodinproject.com")

Это сэкономит вам немного времени, но, как вы увидите позже, иногда вам захочется разделить их в своем приложении.

###Ваше задание:###

Это все было всего лишь вступлением на тему, что может делать Active Record. Дальше вы научитесь взаимодействовать с Active Record в ваших моделях.

  • Думаю, вы уже прочитали Rails для начинающих и сделали упражнение по созданию приложения из вводного раздела Rails Guides. Если еще нет, сделайте это!
  • Прочитайте раздел Основы Active Record Rails Guides.
  • Более подробно Миграции и Валидации (Migrations and Validations) мы изучим в следующем разделе и в уроке о функциях обратного вызова (Callbacks).
  • Файлы моделей (model files) в Rails находятся в папке app/models и являются обычными .rb файлами. Ключевым моментом является то, что имя класса дается по имени таблицы в вашей базе данных (но в ед.ч.), и этот класс наследует Active Record::Base и получает возможность использовать его методы.

##Миграции##

###Когда они нужны###

Представьте, что вы смотрите в пустой монитор и вам нужно начать новый Rails проект. С чего вы начнете? Вы пишете $ rails new MyProjectName, затем cd в эту директорию… Что же дальше?

Подумайте о моделях данных, которые вам понадобятся для первой итерации вашего сайта и начинайте их настраивать. Для наших целей, предположим, что все что вам нужно, универсальная (повсеместно распространенная) модель User, чтобы отслеживать десятки пользователей, которые когда-то придут на ваш сайт (шучу, вы обязательно добьетесь успеха). После того, как вы сначала создали базу данных (используя $ rake db:create), вам необходимы 2 шага, чтобы создать эту модель:

  1. Создайте файл модели в app/models как вы делали в задании выше.
  2. Создайте таблицу базы данных, именуемую “users”, с соответствующими колонками. Это делается с помощью файла миграции (migration file) и запуском процесса миграции (running the migration)

Лучше всего то, что Rails знает, что вы хотите сделать, и предоставляет для этого удобную команду, которая сгенерирует все необходимое, $ rails generate model YourModelNameHere. Когда вы ее вписываете, вы видите в выводе терминала, какие файлы были созданы. Не беспокойтесь о спецификациях и тестовых файлах, которые также были созданы, самыми важными являются файлы модели и файлы миграции. У Rails есть много подобных генераторов, которые предназначены для того, чтобы создавать новые файлы в правильных местах вашего приложения. Выходной поток будет выглядеть примерно так:

 invoke  active_record
 create    db/migrate/20131223154310_create_testmodels.rb
 create    app/models/testmodel.rb
 invoke    rspec
 create      spec/models/testmodel_spec.rb

Файл модели, который был создан генератором, это только скелет файла модели в директории app/models (вы вполне могли бы создать его сами). Другим ключевым файлом является файл миграции в папке db/migrations, который начинается со сложно выглядящей меткой времени (timestamp) 20130924230504_create_users.rb.

Если вы изучите этот файл, то убедитесь, что и это не более чем скелет Ruby класса, который наследует от ActiveRecord::Migration и несколько временных меток[*]. Метки времени только создают колонки created_at и updated_at, чтобы вы могли отслеживать, когда были созданы или изменены записи в базе данных. Эти 2 колонки достаточно полезны, так что их включили в стандартную практику.

Если вы хотите создать только файл миграции базы данных (без файла модели или тестовых файлов), просто используйте $ rails generate migration NameYourMigration. Скорей всего вам придется использовать это еще раз, когда вы все настроите, т.к. вы возможно будете изменять вашу таблицу данных вместо того, чтобы создать новую. Существует некий синтаксис для задания дополнительных параметров при этом вызове, однако нет никакой необходимости запоминать этот синтаксис, т.к. вы всегда можете изменить файл миграции вручную.

[*] если только вы не передали Rails генератору названия необходимых колонок, в этом случае они появятся автоматически в полях миграции. Генераторы позволяют передавать им очень многое в качестве аргументов.

###Что же это такое?###

Итак, что такое миграция? Миграции – это в сущности скрипт, который сообщает Rails, как вы хотите настроить или изменить базу данных. Это другая сторона магии Active Record, которая позволяет вам избежать написания SQL-кода вручную, чтобы создать таблицу базы данных. Вы просто задаете определенный Ruby метод (как нельзя лучше названный create_table) и его параметры и получаете все готовым к использованию.

Миграции – это только скрипт, так как же сказать Rails, что вы хотите запустить этот скрипт и выполнить код по созданию таблицы и обновлению (update) схемы базы данных? С помощью команды $ rake db:migrate, она запускает любую миграцию, которая еще не была запущена. Rails знает об этом, потому что в фоновом режиме отслеживает, какие миграции были запущены (используя метки времени). Когда вы запускаете эту команду, Rails выполнит соответствующий SQL-код, чтобы настроить таблицу базы данных, а вы можете вернуться к непосредственному процессу создания всайта.

Почему это полезно? Очевидно, что это позволяет вам настроить базу данных, используя дружелюбный код Ruby вместо SQL, но это еще не все. Через какое-то время вы построите целую кучу таких файлов миграции. Если однажды вы решите, что хотите выбросить свою базу данных и создать новую с нуля, вы легко сможете это сделать и затем перезапустить свои миграции. Если вы решите развернуть проект в сети, вы запустите те же самые миграции и итоговая база данных уже будет ждать вас… даже если база данных другого типа! И еще раз, Active Records делает за вас всю тяжелую работу, а вам остается только сфокусироваться на построении сайта.

Наиболее используемая, особенно когда вы где-то напортачили, особенность миграций – это то, что они (обычно) носят обратимый характер. Допустим, вы только что мигрировали, создав при этом новую колонку в базе данных, но забыли колонку для хранения e-mail-ов пользователей… Упс! Вы можете просто написать $ rake db:rollback, произойдет откат последней серии миграций, которую вы запускали, и вы снова окажетесь там, где были. Затем вы просто редактируете файл, перезапускаете миграции и дальше делаете то, что хотели.

Это представляет последний нюанс миграций, который мы хотели бы здесь обсудить – обратимость (reversibility). Для каждого метода, который используется в миграции, хочется задать то, как будет осуществляться обратный ход, если возникнет такая необходимость. Обратимость добавления таблицы – это удаление этой таблицы, добавления колонки – удаления колонки и т.д. Многие методы имеют очевидные случаи для отката назад (обратимости), поэтому нет необходимости явным образом задавать их, можно настроить весь файл миграции с помощью метода change. Но некоторые из них не настолько очевидны, отсюда необходимость отдельно задать методы up и down. Вы прочитаете о них больше в своем задании.

И последнее, никому не захочется отката миграций, если только вы что-то не испортили. В ситуациях, когда у вас есть разумный повод для удаления колонки (потому что она вам больше ни для чего не нужна), вы создаете новую миграцию, которая удаляет колонку с помощью метода remove_column. Это сохраняет базу данных. А как только вы разберетесь со всем этим, вы сможете создавать базы данных, используя только файл схемы… Но пока еще мы до этого не добрались :)

###Сколько я должен знать о базах данных?###

Миграции не требуют написания SQL, однако вам необходимо понимать достаточно о базах данных, чтобы знать, как вы хотите структурировать свою базу. Какие колонки вам нужны? Какие должны быть индексированы (и почему)? Нужно ли задавать значения по умолчанию? Какой тип данных будет храниться в ваших колонках – строка (string) или текст (text)?

Это серьезные вопросы, и нужно чувствовать себя комфортно, задавая их, даже если вы не слишком уверены в ответах. Если вы не понимаете, о чем я, то вам нужно вернуться назад и прочитать об основах баз данных в предыдущем уроке.

###Задание###

  1. Прочитайте Миграции Active Record.
    • Не беспокойтесь о пунктах 3.6-3.8
    • Проглядите вскользь секции 7 и 8
    • Сиды полезны и вы будете использовать их позднее. Они сэкономят вам много усилий, особенно когда вы учитесь и в итоге много раз будете выбрасывать базы данных и создавать новые.

##Базовые валидации (Basic Validations)##

Представьте, что у вас есть работающая база данных и вы хотите проверить, являются ли данные, пересылаемые другими людьми в вашу базу, хорошими. Например, чтобы создать аккаунт на сайте, пользователю нужно ввести имя пользователя (username) и адрес электронной почты. Как обеспечить соблюдение этого?

Существуют три уровня валидации, чье выполнение вы можете обеспечить, каждый последующий более строгий и безопасный, чем предыдущий. На верхнем уровне, вы можете писать код с использованием Javascript в вашем браузере и выяснить, правильно ли заполнена форма, потребовать ее верного заполнения до того, как двигаться дальше. Мы изучим это более подробно в курсе о Javascript. Преимущество здесь в том, что это практически мгновенно, а значит хорошо для пользователя. А проблема здесь в том, что Javascript легко перехитрить и пользователь легко может отправить вредоносный или дефектный запрос.

Второй уровень обеспечения валидации данных пользователя (которым вы никогда не должны слепо доверять) – это валидация на уровне сервера. Это означает написание кода в вашем Rails приложении (особенно в модели, чей экземпляр вы пытаетесь сохранить, например, User), который будет проверять входные данные пользователя, сверять их с ограничениями, заданными вами, и возвращать ошибки, если они имеют место быть.

Это более безопасно, чем javascript, но обладает таким недостатком, как требования полного двустороннего HTTP запроса для проверки. Валидации моделей обычно достаточно эффективны и это именно то, на чем мы заострим внимание.

Другая проблема возникает, когда ваше приложение разрастается до того, что на множестве серверов запущено сразу несколько его экземпляров, и все эти серверы соединены с одной и той же центральной базой данных. Допустим, вы хотите проверить, является ли имя пользователя уникальным… Что происходит, когда два пользователя одновременно отправляют одинаковые имена пользователей, и получают их отдельные параллельно работающие экземпляры вашего приложения? Когда каждый из экземпляров приложения сверяется с базой данных, уникально ли имя пользователя, оба раза все выглядит прекрасно, оба продолжают работу, сохраняют модель и… упс! Звучит маловероятно, но что если подумать об оперативно возникающих автоматизированных транзакциях? Такой «режим соперничества» становится очень правдоподобным.

Поэтому единственным способом действительно проверить соответствие ограничениям – это уровень базы данных, т.к. только ваша база данных может точно сказать, что является уникальным или валидным в этом мире. Вы можете использовать дополнительные параметры, передаваемые некоторым теперь уже известным вам методам миграции, таким как add_index, скажем add_index :users, :username, unique: true, чтобы проверить самым надежным способом, что колонка уникальна. Еще раз, несмотря на все это, большинство валидаций может быть осуществлено в модели вашего Rails приложения.

###Ваше задание###

  1. Прочитайте Валидации Active Record
    • Секция 2 о вспомогательных функциях (хелперы) может быть просмотрена по диагонали – она поможет вам быть более специфичными с валидациями, более подробно вы изучите их позже.
    • Так же можете просмотреть секцию 6 о задаваемых пользователем валидаторах (custom validators)
    • Секция 8 скорей всего заинтересует вас только если вы уже видели ERB в Rails видах (rails views). Мы изучим их позже.

##Базовые ассоциации / связи (Basic associations)##

В разделе о базах данных, вы изучили, как реляционные базы данных, такие как sqlite3 или PostgreSQL позволяют связать две таблицы вместе с помощью первичных ключей (primary keys) (называемых внешними (foreign) ключами в каждой отдельной таблице, которая ссылается на другую). В этом и заключается сила реляционных баз данных, позволяющих эксплуатировать эти связи. Active Record так же обладает этой особенностью и предоставляет возможность использовать это любым нужным способом. Вы хотите получить все сообщения вашего первого пользователя? Попробуйте User.first.posts. Это действительно так просто.

Это функциональность идет не «из коробки» - вам нужно сообщить Rails, что сообщения собственно принадлежат пользователю. На уровне таблицы базы данных это означает, что каждая строка в таблице сообщений будет содержать в себе колонку для user_id, которая и будет сообщать, какому пользователю принадлежит это сообщение. Таблице пользователей совсем необязательно знать о сообщениях, в конце концов, у пользователя может быть бесконечное число сообщений. Если нам интересны сообщения пользователя, нам нужно запросить таблицу сообщений обо всех сообщениях, которые помечены нужным нам ID пользователя. Rails позволяет очень легко задавать эти отношения. То, о чем мы только что говорили, метко называется ассоциациями «has many / belongs to» (имеет много / принадлежит одному) (объект User ассоциирован со многими (has_many) объектами-сообщениями Post, а объект Post принадлежит (belongs_to) одному объекту User).

Первый шаг в понимании всего этого - это просто размышление о возможных различных типах отношений. Помните, половина успеха в настраивании вашего приложения это понимание того, как будут выглядеть отношения между вашими данными, поэтому подумайте немного и упорно продолжайте размышлять, даже если не все понятно. Если вы чувствуете, что мозги закипают, вернитесь в реальный мир и не думайте об этом на уровне базы данных - помните, все эти вещи это попытка отразить все виды отношений, которые могут возникнуть в реальном мире.

Отношение has_many / belongs_to, или «один-ко-многим», достаточно распространенное и обычно легче всего думать о нем в терминах существующих объектов... У ребенка (Child) может быть много (has_many) объектов Мраморный шарик (Marble), каждый из которых принадлежит (belongs_to) этому ребенку. Так же это применимо и к менее очевидным случаям, где один объект принадлежит (belongs_to) нескольким другим объектам. Примером может служить местная франшиза (FranchiseLocation) МакДональдса, которая (belongs_to) принадлежит корпорации Макдональдс, но так же может принадлежать (belongs_to) городу Сан Франциско.

Принадлежность материнской корпорации не вызывает сомнений, но почему она принадлежит и городу? Будет проще, если мы посмотрим на это с другой стороны - конечно же в городе может быть много объектов местного франчайзинга (FranchiseLocation Objects). А так как местная франшиза (FranchiseLocation) может существовать только в одном конкретном городе, то соответственно она принадлежит (belongs_to) этому городу таким способом, как его понимает Ruby.

Другим распространенным отношением является отношение многие-ко-многим (many-to-many), которое так же называется has_and_belongs_to_many в терминах ruby. С этим легко столкнуться в повседневной жизни - у Человека (Human) может быть много любимых объектов Собака (Dog), а у каждого объекта Собака может быть много любимых объектов Людей. В этом случае, как вы определите, какой из объектов-собак относится к вашим любимым? Это требует создания другой таблицы (соединительная таблица (join table) или таблица «through»), которая будет отслеживать эти отношения. Это немного трудно для понимания, когда вы учитесь, но уже достаточно быстро это станет вашей второй натурой.

Ключевое отличие здесь в том, что мы говорим не о том, сколько в данный момент сообщений у пользователя или сколько объектов местного франчайзинга в городе прямо сейчас, мы пытаемся смоделировать, сколько всего они МОГЛИ БЫ ИМЕТЬ в течение всего жизненного цикла вашего приложения. Конечно, если сегодня у вашего Пользователя только одно сообщение, вы можете вписать ID этого сообщения в вашу таблицу пользователей. Потом еще одно сообщение (Post) и еще один столбец с ID. И еще один.... И еще... Но в этом нет смысла, и именно поэтому мы говорим, что Пользователь (User) has_many :posts и оставляем таблицу сообщений (Post table) жестко закрепленной за ID пользователя (User ID), в то время как каждое сообщение в отдельности будет ассоциировано только с одним Пользователем (допустим, у нас есть только один автор).

Очень скоро вы начнете видеть окружающий мир через призму этих отношений (особенно если не будете делать достаточное количество перерывов). А их истинную силу ощутите тогда, когда необходимо будет их использовать - когда захотите получить данные обо всех объектах, ассоциированных с другим. Хотите посмотреть список всех ваших подписчиков в Twitter? Хотите пересчитать всех своих одноклассников, с которыми живете сейчас в одном городе? Хотите посмотреть все комментарии одного из пользователей, которые он оставил на ленте у другого пользователя? Все эти вещи относительно просты и очевидны, когда вы разберетесь с подходящими отношениями между данными. Поэтому сфокусируйтесь на этих взаимоотношениях.

###Задание###

Если вы нормальный человек, то вы сейчас, вероятно, находитесь где-то между «чего?» и «ненавижу тебя, перестань учить меня всякой ерунде!». Не бросайте это, суть в том, чтобы приучить вас думать, как смоделировать отношения и дать вам представление о них. Проект даст вам возможность на практике построить то, что вы изучаете.

  1. Прочитайте начало раздела Связи Active Record до секции 2.7. Остальное мы можем оставить на потом... Важнейшим моментом тут является то, что вы увидите отношения между данными и то, как они работают.

##Заключение##

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

Проще начинать думать о конкретных отношениях в реальном мире, а потом уже превращать их в ассоциации Active Record. Когда вы почувствуете себя уверено в том, какая модель обаладает (has_many) какими другими моделями и кто из них кому принадлежит (belongs_to), вы можете начинать моделировать более абстрактные концепции, скажем, приглашения на какое-то мероприятие (event invitations) или принятые приглашения (invitation acceptances), которые не так очевидны как ребенок и его мраморные шарики (пример, который мы использовали раньше).

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

##Дополнительные ресурсы##

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

Поделиться уроком: