Сегодня мы познакомимся с таким мощным и полезным инструментом Руби и компонентом RoR, как Active Record. Как часть RoR мы знаем Active Record из первого шага статей о рельсах (что-то эта серия не очень хорошо пошла, правда). Рассмотрим практическое применение компонента в Руби, что очень пригодится и в RoR.
Active Record придерживается принципов ORM: базы данных могут быть представлены в виде объектно-ориентированного кода, где таблицы — это классы, строки таблицы — объекты, а поля — атрибуты объекта. Однако Active Record отличается от других ORM библиотек, например, от
Когда вы создаете новую запись в базу, обновляете существующую или просто считываете данные, все проиходит в три шага: создание объекта Active Record, доступ и манипулирование атрибутами объекта и сохренение атрибутов в виде записей в БД.
Как и обычно в Руби, установка — плевое дело, достаточно установить необходимый джем:
Начнем с такой таблицы
В первую очередь подключаемся к БД:
Как видим, для этого надо передать методу
В соответствие с таблицей создаем класс-наследник:
Здесь стоит обратить особое внимание на название класса. Как обычно, здесь работает принцип conventions over configuration, по которым название таблицы во множественном числе, название класса — в единственном. Можно, конечно, обходить это ограничение, но делать этого не стоит — работайте в Ruby/Rails way, и вы поймете, насколько это удобно.
Итак, как мы знаем, чтобы создать запись в таблице, мы должны работать с объектами Руби. Для создания новой строки в БД
Здесь как раз мы видим одно из ключевых преимуществ Руби — метапрограмминг. Налету для нас были созданы методы, одноименные со столбцами таблицы, для редактирования атрибутов экземпляра. Очень удобно, но это только начало, дальше — больше. Однако это только лишь экземляр класса, таблица еще не изменилась (о чем говорилось выше). Дла того, чтобы «синхронизировать» экземляр с таблицей, создав новую строку, используем метод
Другой способ внесения записи:
Записи сохранять мы научились, теперь нужно их извлечь. Понимаем, что при этом мы должны создать экземпляр класса и заполнить его атрибутами из строки таблицы. На деле это проще. Если мы знаем
Довольно тривиально, а что, если мы хотим искать по столбцам/атрибутам? Тут метапрограммная основа Руби опять приходит нам на помощь — используем динамические методы поиска:
Это небольшой кусочек магии, которую предоставляет нам Active Record — снова методы сгенерировались для нас на лету. Думаю, что структура названия их понятна. Пример покруче:
И в этом случае название метода говорит само за себя: «найди или создай username». Соответственно, если при поиске элемент (говорим элемент — подразумеваем строку таблицы, но, на самом деле, это), попадающий под условие поиска не будет найден, то он будет создан и сохранен в таблицу (
Итак, нужный экземпляр/строку мы создали, нашли, теперь смотрим, как обновлять данные и удалять их совсем:
Здесь стоит отметить, что несмотря на то, что мы удалили строку из БД, экземпляр продолжает существовать read-only, пока непосредственно не будет удален сам экземпляр или не выйдет за пределы области видимости (scope — отдельная тема).
Связь между объектами — это не только невероятно важная часть функциональности Active Record, это просто необходимо в реальных приложениях. Существует несколько видов связей, мы рассмотрим один.
Теперь мы хотим к каждому пользователю привязать определенную группу, как то Администратор или Гость. Начнем с того, что создадим таблицу
Связь же мы должны определить в классе
Теперь у нас есть односторонняя связь — аккаунт знает, что у него есть группа, а группа не знает об ее аккаунтах. Давайте создадим группу администраторов и назначим в нее аккаунт:
Как видим, для класса Account появился новый метод, записывающий ему группу. Мы просто «приравняли» атрибут (на самом деле не атрибут, но для конечного пользователя это выглядит так) аккаунта ко всему экземпляру группы, Active Record понимает это, делая то, что нам нужно.
Вот такое введение в эту мощную систему. Прежде всего, она, конечно, будет использоваться при работе с web-приложениями, Rails, в частности. Если у вас есть желание, то мы можем дальше углубиться в Active Record или, например, Rake. Как обычно — комментарии и замечания приветствуются на самом высоком уровне ;)
Active Record придерживается принципов ORM: базы данных могут быть представлены в виде объектно-ориентированного кода, где таблицы — это классы, строки таблицы — объекты, а поля — атрибуты объекта. Однако Active Record отличается от других ORM библиотек, например, от
Hibernate
из Java
, тем, что здесь нет необходимости начальной конфигурации — все работает «из коробки». Active Record написан на чистом Руби, что позволяет нам работать с ним, как с обычным классом: использовать наследование, переписывать методы, использовать метапрограмминг и др. Да, атрибуты объекта в большинстве случаев заполняются с помощью SQL-запросов и точно также атрибуты записываются назад в таблицу. Но все, что происходит между этими двумя процессами — обычная работа с объектом Руби. Стоит понимать «отдельность» существования записей базы данных и объектов Руби: подчас база данных будет отличаться от класса Руби. Когда вы создаете новую запись в базу, обновляете существующую или просто считываете данные, все проиходит в три шага: создание объекта Active Record, доступ и манипулирование атрибутами объекта и сохренение атрибутов в виде записей в БД.
Установка Active Record
Как и обычно в Руби, установка — плевое дело, достаточно установить необходимый джем:
gem install activerecord
. Подключение к базе данных происходит с помощью адаптеров, большинство которых уже встроены (DB2, Firebird, FrontBase, MySQL, Open-Base, Oracle, PostgreSQL, SQLite, SQL Server
и Sybase
), необходимо только передать параметры подключения к БД. Также следует убедиться, что у вас установлен/запущен сервер БД и установлен нужный джем (mysql
или sqlite3-ruby
, например).In use
Начнем с такой таблицы
Accounts:
id (integer; auto-incremented; primary key)
username (text)
password (text)
В первую очередь подключаемся к БД:
require "rubygems"
require "activerecord"
ActiveRecord::Base.establish_connection(
:adapter => "mysql",
:host => "localhost",
:username => "user",
:password => "pass",
:database => "project_development")
Как видим, для этого надо передать методу
establish_connection
параметры в виде хэша. Для SQLite нужны только :adapter => "sqlite"
и :database
.В соответствие с таблицей создаем класс-наследник:
Class Account < ActiveRecord::Base
end
Здесь стоит обратить особое внимание на название класса. Как обычно, здесь работает принцип conventions over configuration, по которым название таблицы во множественном числе, название класса — в единственном. Можно, конечно, обходить это ограничение, но делать этого не стоит — работайте в Ruby/Rails way, и вы поймете, насколько это удобно.
Итак, как мы знаем, чтобы создать запись в таблице, мы должны работать с объектами Руби. Для создания новой строки в БД
Accounts
, создаем экземпляр класса Account
, используя стандартный метод new
:acc1 = Account.new
acc1.username = "maxelc"
acc1.password = "maxspassword"
Здесь как раз мы видим одно из ключевых преимуществ Руби — метапрограмминг. Налету для нас были созданы методы, одноименные со столбцами таблицы, для редактирования атрибутов экземпляра. Очень удобно, но это только начало, дальше — больше. Однако это только лишь экземляр класса, таблица еще не изменилась (о чем говорилось выше). Дла того, чтобы «синхронизировать» экземляр с таблицей, создав новую строку, используем метод
save
:acc1.save
Другой способ внесения записи:
acc2 = Account.new(
:username => "hbuser",
:password => "habrapass")
acc2.save
Записи сохранять мы научились, теперь нужно их извлечь. Понимаем, что при этом мы должны создать экземпляр класса и заполнить его атрибутами из строки таблицы. На деле это проще. Если мы знаем
id
строки, то используем встроенный (core) метод find
:result1 = Account.find(1)
Довольно тривиально, а что, если мы хотим искать по столбцам/атрибутам? Тут метапрограммная основа Руби опять приходит нам на помощь — используем динамические методы поиска:
result2 = Account.find_by_username("hbuser")
Это небольшой кусочек магии, которую предоставляет нам Active Record — снова методы сгенерировались для нас на лету. Думаю, что структура названия их понятна. Пример покруче:
result3 = Account.find_or_create_by_username("superuser")
И в этом случае название метода говорит само за себя: «найди или создай username». Соответственно, если при поиске элемент (говорим элемент — подразумеваем строку таблицы, но, на самом деле, это), попадающий под условие поиска не будет найден, то он будет создан и сохранен в таблицу (
save
не нужен в данном случае).Итак, нужный экземпляр/строку мы создали, нашли, теперь смотрим, как обновлять данные и удалять их совсем:
Account.update(1, {:password => 'newpass'})
puts "запись сохранена"
Account.delete(2)
Здесь стоит отметить, что несмотря на то, что мы удалили строку из БД, экземпляр продолжает существовать read-only, пока непосредственно не будет удален сам экземпляр или не выйдет за пределы области видимости (scope — отдельная тема).
Связи Active Record
Связь между объектами — это не только невероятно важная часть функциональности Active Record, это просто необходимо в реальных приложениях. Существует несколько видов связей, мы рассмотрим один.
Теперь мы хотим к каждому пользователю привязать определенную группу, как то Администратор или Гость. Начнем с того, что создадим таблицу
Groups
, содержащую id
и name
(группы). Также нужно добавить в таблицу Accounts
новый столбец group_id
для использования в качестве внешнего ключа (опять работают договоренности об именовании). В нашем случае можно представить связь как «каждый аккаунт принадлежит какой-то роли». Для такого вида связей используется метод belongs_to
. Но сначала создадим новый класс для Groups
в нашем скрипте:class Group < ActiveRecord::Base
end
Связь же мы должны определить в классе
Account
, дописав класс так:class Account < ActiveRecord::Base
belongs_to :group
end
Теперь у нас есть односторонняя связь — аккаунт знает, что у него есть группа, а группа не знает об ее аккаунтах. Давайте создадим группу администраторов и назначим в нее аккаунт:
admin_group = Group.find_or_create_by_name("admin")
account.group = admin_group
account.save
Как видим, для класса Account появился новый метод, записывающий ему группу. Мы просто «приравняли» атрибут (на самом деле не атрибут, но для конечного пользователя это выглядит так) аккаунта ко всему экземпляру группы, Active Record понимает это, делая то, что нам нужно.
Эпилог
Вот такое введение в эту мощную систему. Прежде всего, она, конечно, будет использоваться при работе с web-приложениями, Rails, в частности. Если у вас есть желание, то мы можем дальше углубиться в Active Record или, например, Rake. Как обычно — комментарии и замечания приветствуются на самом высоком уровне ;)