Основы 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, и, в итоге, с меньшим суммарным кодом для доступа в базу данных.

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

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

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

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

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

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

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

  • Таблица базы данных - Множественная форма со словами, разделенными знаком подчеркивания (т.е., book_clubs).
  • Класс модели - Единственное число с первой прописной буквой в каждом слове (т.е., BookClub).
Модель / Класс Таблица / Схема
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 как первичный ключ таблицы. Этот столбец будет автоматически создан при использовании миграций 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 - это зарезервированное слово для определения таблицы, использующей Single Table Inheritance (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)
);

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

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

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

6. Валидации

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

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

class User < ApplicationRecord
  validates :name, presence: true
end

user = User.new
user.save  # => false
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[5.0]
  def change
    create_table :publications do |t|
      t.string :title
      t.text :description
      t.references :publication_type
      t.integer :publisher_id
      t.string :publisher_type
      t.boolean :single_issue

      t.timestamps
    end
    add_index :publications, :publication_type_id
  end
end

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

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