Основы Active Record

Это руководство является введением в Active Record.

После прочтения этого руководства, вы узнаете:

  • Что такое ORM (Object Relational Mapping) и Active Record, и как они используются в Rails.
  • Как Active Record вписывается в парадигму Model-View-Controller.
  • Как использовать модели Active Record для управления информацией, хранящейся в реляционной базе данных.
  • О соглашении по именованиям схемы Active Record.
  • О концепциях миграций базы данных, валидаций и колбэков.

1. Что такое Active Record?

Active Record это M в MVC - модель - которая является слоем в системе, ответственным за представление бизнес-логики и данных. Active Record упрощает создание и использование бизнес-объектов, данные которых требуют персистентного хранения в базе данных. Сама по себе эта реализация паттерна Active Record является описанием системы ORM (Object Relational Mapping).

1.1. Паттерн Active Record

Active Record был описан Martin Fowler в его книге Patterns of Enterprise Application Architecture. В Active Record объекты содержат и персистентные данные, и поведение, которое работает с этими данными. Active Record исходит из мнения, что обеспечение логики доступа к данным как части объекта покажет пользователям этого объекта то, как читать и писать в базу данных.

1.2. Object Relational Mapping (ORM)

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

Чтобы полностью понять Active Record, будут полезны базовые знания реляционных систем управления базой данных (RDBMS) или языка структурированных запросов (SQL). Если хотите узнать больше, обратитесь к этому учебному пособию (или к этому) или изучите их другими способами.

1.3. Active Record это фреймворк ORM

Active Record предоставляет нам несколько механизмов, наиболее важными из которых являются способности для:

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

2. Соглашения над конфигурацией в Active Record

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

2.1. Соглашения по именованию

По умолчанию Active Record использует некоторые соглашения по именованию чтобы узнать, как должна быть создана связь между моделями и таблицами базы данных. Rails образует множественное число для имен класса, чтобы найти соответствующую таблицу базы данных. Так, для класса Book следует создать таблицу базы данных с именем books. Механизмы образования множественного числа Rails очень мощные, они способны образовывать множественное (и единственное) число как для правильных, так и для неправильных форм слов. При использовании имен класса, созданных из двух и более слов, имя класса модели должно следовать соглашениям Ruby, используя форму CamelCase, тогда как имя таблицы должно использовать форму snake_case. Примеры:

  • Класс модели - Единственное число с первой прописной буквой в каждом слове (т.е., BookClub).
  • Таблица базы данных - Множественная форма со словами, разделенными знаком подчеркивания (т.е., book_clubs).
Модель / Класс Таблица / Схема
Article articles
LineItem line_items
Deer deers
Mouse mice
Person people

2.2. Соглашения схемы

Active Record использует соглашения о именовании для столбцов в таблицах базы данных, зависящих от назначения этих столбцов.

  • Внешние ключи - Эти поля должны именоваться по образцу singularized_table_name_id (т.е., item_id, order_id). Это поля, которые ищет Active Record при создании связей между вашими моделями.
  • Первичные ключи - По умолчанию Active Record использует числовой столбец с именем id как первичный ключ таблицы (bigint для PostgreSQL и MySQL, integer для SQLite). Этот столбец будет автоматически создан при использовании миграций Active Record для создания таблиц.

Также имеются некоторые опциональные имена столбцов, добавляющие дополнительные особенности для экземпляров Active Record:

  • created_at - Автоматически будут установлены текущие дата и время при изначальном создании записи.
  • updated_at - Автоматически будут установлены текущие дата и время всякий раз, когда создается или обновляется запись.
  • lock_version - Добавляет оптимистическую блокировку к модели.
  • type - Указывает, что модель использует Single Table Inheritance.
  • (association_name)_type - Хранит тип для полиморфных связей.
  • (table_name)_count - Используется для кэширования количества принадлежащих по связи объектов. Например, столбец comments_count в классе Article, у которого может быть несколько связанных экземпляров Comment, закэширует количество существующих комментариев для каждой статьи.

Хотя эти имена столбцов опциональны, фактически они зарезервированы Active Record. Избегайте зарезервированных ключевых слов, если вы не желаете дополнительной функциональности. Например, type - это зарезервированное слово для определения таблицы, использующей наследование с единой таблицей (STI). Если вы не используете STI, попытайтесь использовать аналогичное слово, такое как "context", которое также может аккуратно описать данные, которые вы моделируете.

3. Создание моделей Active Record

Чтобы создать модели Active Record, создайте подкласс ApplicationRecord, и готово:

class Product < ApplicationRecord
end

Это создаст модель Product, связав ее с таблицей products в базе данных. Сделав так, также появится способность связать столбцы каждой строки этой таблицы с атрибутами экземпляров вашей модели. Допустим, что таблица products была создана с использованием такого выражения SQL (или одно из его расширений):

CREATE TABLE products (
  id int(11) NOT NULL auto_increment,
  name varchar(255),
  PRIMARY KEY  (id)
);

Вышеуказанная схема объявляет таблицу с двумя столбцами: id и name. Каждая строка этой таблицы представляет собой определенный продукт с этими двумя параметрами. Таким образом, можно написать подобный код:

p = Product.new
p.name = "Some Book"
puts p.name # "Some Book"

4. Переопределение соглашений об именовании

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

ApplicationRecord наследуется от ActiveRecord::Base, который определяет ряд полезных методов. Можно использовать метод ActiveRecord::Base.table_name= для указания имени таблицы, которая должна быть использована:

class Product < ApplicationRecord
  self.table_name = "my_products"
end

Если так сделать, нужно вручную определить имя класса, содержащего фикстуры (my_products.yml), используя метод set_fixture_class в определении теста:

class ProductTest < ActiveSupport::TestCase
  set_fixture_class my_products: Product
  fixtures :my_products
  # ...
end

Также возможно переопределить столбец, который должен быть использован как первичный ключ таблицы, с помощью метода ActiveRecord::Base.primary_key=:

class Product < ApplicationRecord
  self.primary_key = "product_id"
end

Active Record не поддерживает использование столбцов c именем id, не являющихся первичными ключами.

5. CRUD: Чтение и запись данных

CRUD это сокращение для четырех глаголов, используемых для описания операций с данными: Create (создать), Read (прочесть), Update (обновить) и Delete (удалить). Active Record автоматически создает методы, позволяющие приложению читать и воздействовать на данные, хранимые в своих таблицах.

5.1. Создание

Объекты Active Record могут быть созданы из хэша, блока или из вручную указанных после создания атрибутов. Метод new возвратит новый объект, в то время как create возвратит объект и сохранит его в базу данных.

Например, для модели User с атрибутами name и occupation, вызов метода create создаст и сохранит новую запись в базу данных:

user = User.create(name: "David", occupation: "Code Artist")

Используя метод new, объект может быть инициализирован без сохранения:

user = User.new
user.name = "David"
user.occupation = "Code Artist"

Вызов user.save передаст запись в базу данных.

Наконец, если предоставлен блок и create, и new передадут новый объект в этот блок для инициализации:

user = User.new do |u|
  u.name = "David"
  u.occupation = "Code Artist"
end

5.2. Чтение

Active Record предоставляет богатый API для доступа к данным в базе данных. Ниже несколько примеров различных методов доступа к данным, предоставленных Active Record.

# возвратит коллекцию со всеми пользователями
users = User.all
# возвратит первого пользователя
user = User.first
# возвратит первого пользователя с именем David
david = User.find_by(name: 'David')
# найдет всех пользователей с именем David, занятостью Code Artists, и сортирует их по created_at в обратном хронологическом порядке
users = User.where(name: 'David', occupation: 'Code Artist').order(created_at: :desc)

Подробно о запросах в моделях Active Record можно узнать в руководстве Интерфейс запросов Active Record.

5.3. Обновление

Как только объект Active Record будет получен, его атрибуты могут быть модифицированы, и он может быть сохранен в базу данных.

user = User.find_by(name: 'David')
user.name = 'Dave'
user.save

Сокращенным вариантом для этого является использование хэша с атрибутами, связанными с желаемыми значениями, таким образом:

user = User.find_by(name: 'David')
user.update(name: 'Dave')

Это наиболее полезно, когда необходимо обновить несколько атрибутов за раз.

Если необходимо обновить несколько записей за раз без колбэков и валидаций, можно обновить базу данных напрямую с помощью update_all:

User.update_all max_login_attempts: 3, must_change_password: true

5.4. Удаление

Более того, после получения, объект Active Record может быть уничтожен, что уберет его из базы данных.

user = User.find_by(name: 'David')
user.destroy

Если необходимо удалить сразу несколько записей, можно использовать методы destroy_by или destroy_all:

# найти и удалить всех пользователей с именем David
User.destroy_by(name: 'David')

# удалить всех пользователей
User.destroy_all

6. Валидации

Active Record позволяет проверять состояние модели до того, как она будет записана в базу данных. Имеется несколько методов, которые могут быть использованы для проверки ваших моделей и валидации, что значение атрибута не пустое, уникальное (не существующее в базе данных), отвечает определенному формату, и многие другие.

Валидация - это очень важный вопрос, который нужно рассмотреть при сохранении в базу данных, поэтому методы save и update учитывают ее при запуске: они возвращают false, когда валидация проваливается, и фактически они не выполняют каких-либо операций с базой данных. Каждый из этих методов имеет пару с восклицательным знаком (save! и update!), которые строже в том, что они вызывают исключение ActiveRecord::RecordInvalid если валидация провалится. Краткий пример:

class User < ApplicationRecord
  validates :name, presence: true
end
irb> user = User.new
irb> user.save
=> false
irb> user.save!
ActiveRecord::RecordInvalid: Validation failed: Name can't be blank

Подробнее о валидациях можно прочитать в руководстве Валидации Active Record.

7. Колбэки

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

8. Миграции

Rails предоставляет DSL для управления схемой базы данных, называемый миграциями. Миграции хранятся в файлах, выполняемых для любой базы данных, которую поддерживает Active Record, с использованием rake. Вот миграция, создающая таблицу:

class CreatePublications < ActiveRecord::Migration[7.1]
  def change
    create_table :publications do |t|
      t.string :title
      t.text :description
      t.references :publication_type
      t.references :publisher, polymorphic: true
      t.boolean :single_issue

      t.timestamps
    end
  end
end

Rails отслеживает, какие файлы переданы в базу данных, и представляет возможность отката. Чтобы фактически создать таблицу, нужно запустить bin/rails db:migrate, а чтобы ее откатить bin/rails db:rollback.

Отметьте, что вышеприведенный код не зависит от базы данных: он выполнится в MySQL, PostgreSQL, Oracle и иных. Подробнее о миграциях можно прочитать в руководстве Миграции Active Record