Зарегистрируйтесь, чтобы продолжить обучение

Модели Ruby On Rails

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

Подготовка

Для работы Rails с базой данных, необходимо ее подготовить. По умолчанию Rails использует SQLite для работы с БД. Настройки подключения к базе данных хранятся в файле config/database.yml

Что такое модель

Модель — это один из трех основных компонентов архитектуры MVC (Model-View-Controller), используемой в Ruby on Rails и других фреймворках. Model отвечает за управление данными, бизнес-логикой и правилами приложения. Модель взаимодействует с базой данных, выполняет операции CRUD (создание, чтение, обновление, удаление) и обеспечивает валидацию данных. Она определяет структуру данных и их взаимосвязи, а также содержит методы для работы с этими данными.

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

Связь класса модели с таблицей

Class Table name
Article articles
LineItem line_items
Deer deers
Mouse mice
Person people

Модели в Ruby On Rails используют Active Record — встроенный ORM (Object-Relational Mapping) в Rails, который упрощает взаимодействие с базой данных. Active Record позволяет разработчикам выполнять запросы к базе данных, не написав ни одной строки SQL, что делает код более читаемым и поддерживаемым.

# Получение списка пользователей
#  SELECT users.* FROM users
User.all

Миграции

Если у нас еще нет структуры базы данных, ее необходимо подготовить. Для этого используют миграции. Миграции в Ruby on Rails представляют собой механизм для управления схемой базы данных. Они позволяют разработчикам изменять структуру базы данных с помощью кода, что делает процесс управления схемой более удобным и безопасным. Миграции обеспечивают версионирование изменений в базе данных, позволяя легко откатывать или применять изменения по мере необходимости. Это особенно полезно в командной разработке, где несколько разработчиков могут вносить изменения в базу данных одновременно.

Создадим таблицу пользователей с помощью миграции

bin/rails g migration create_users first_name last_name active:boolean

Эта команда создаст миграцию /tmp/static_pages_app/db/migrate/xxxxxxxxxxxxxx_create_users.rb. Где xxxxxxxxxxxxxx - это дата создания миграции:

class CreateUsers < ActiveRecord::Migration[6.1]
  def change
    create_table :users do |t|
      t.string :first_name
      t.string :last_name
      t.boolean :active
      t.timestamps
    end
  end
end

Применим миграцию

bin/rails db:migrate

Проверим, что все было выполнено успешно:

bin/rails db:migrate:status

database: storage/development.sqlite3

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20241223183647  Create users

schema.rb

После первой миграции будет создан файл db/schema.rb. Он отображает текущую структуру базы данных, включая таблицы, столбцы и индексы. Файл обновляется при создании и применении миграций, позволяет восстанавливать структуру базы данных, синхронизировать ее в командной разработке и служит документацией для разработчиков.

# db/schema.rb
ActiveRecord::Schema[7.1].define(version: 2024_12_23_183647) do
  create_table "users", force: :cascade do |t|
    t.string "first_name"
    t.string "last_name"
    t.boolean "active"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

end

Модели

Также, как и контроллеры, модели можно создать с помощью генератора. Он принимает имя модели, атрибуты и их тип

bin/rails g model User first_name last_name active:boolean
      invoke  active_record
   identical    db/migrate/20241225105116_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml

Команда создаст модель и миграции, если их нет:

class User < ApplicationRecord
end

Если же они есть, и модель совпадает со схемой таблицы, то команда миграции не затронет

# Миграция уже была создана командой bin/rails g migration create_users ...
ActiveRecord::Schema[7.1].define(version: 2024_12_23_183647) do
  create_table "users", force: :cascade do |t|
    t.string "first_name"
    t.string "last_name"
    t.boolean "active"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end
end

Валидация модели

Валидация в Ruby on Rails — это процесс проверки данных перед их сохранением в базе данных. Она обеспечивает целостность и корректность данных, предотвращая сохранение некорректной или неполной информации. Валидации помогают гарантировать, что данные соответствуют определенным критериям, таким как обязательные поля, уникальность значений и формат данных. Это особенно важно для поддержания качества данных и предотвращения ошибок, которые могут возникнуть при работе с некорректной информацией.

Rails предоставляет встроенные валидации, которые можно использовать в моделях. Пример модели с различными валидациями:

# app/models/user.rb
class User < ApplicationRecord
  validates :first_name, presence: true
  validates_inclusion_of :role, in: %w( customer admin superuser )
  validates_length_of :last_name, maximum: 15, message: "less than 15 if you don't mind"
  validates :age, numericality: { only_integer: true, greater_than: 17 }
  validates :email, uniqueness: true
  validates :email, format: { with: /\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/ }
  validate :custom_validation

  attribute :active, :boolean, default: -> { false }

  def custom_validation
    errors.add :first_name, 'You are not welcome here, John'  if first_name == 'John'
  end
end

Мы можем добавить в нашу модель валидацию и проверить ее работу:

class User < ApplicationRecord
  validates :first_name, presence: true
  validates :last_name, presence: true
  attribute :active, :boolean, default: -> { false }
end

Метод validate() выполняет валидацию и если все проверки прошли, то он вернет true.

user = User.new(first_name: 'John')
# last_name обязателен
user.validate # false

У метода validate() есть аналогичный bang-метод validate!(). В отличии от validate() он выбрасывает исключение ActiveRecord::RecordInvalid. Это позволяет вам обрабатывать ошибки валидации более явно.

begin
  user = User.new(first_name: 'John')
  user.validate!
rescue ActiveRecord::RecordInvalid => e
  # Validation failed: First name can't be blank (ActiveRecord::RecordInvalid)
  puts e.message # => "Validation failed: Last name can't be blank"
  # ...
end

в ActiveRecord у моделей есть еще метод valid?(), который тоже выполняет валидацию. Но так как функция - предикат, следует использовать метод по назначению - для проверки валидности модели и явно проверять, валидна модель или нет

user = User.new(first_name: 'John')
user.validate

if user.valid?
  # ...
else
  # ...
end

Получение ошибок валидации

После выполнения валидации мы можем получить сообщения об ошибках. Для их получение необходимо использовать метод errors() у модели.

user = User.new(first_name: 'John')
user.validate

puts user.errors
# #<ActiveModel::Errors [#<ActiveModel::Error attribute=last_name, type=blank, options={}>]>

Если валидация прошла без ошибок, то и ошибок не будет:

user = User.new(first_name: 'John', last_name: 'Doe')
user.validate # true
# Ошибок нет
user.errors # <ActiveModel::Errors []>

Методы messages() и full_messages() позволяют получить тексты ошибок:

user = User.new(first_name: 'John')
user.validate

user.errors.messages # {:last_name=>["can't be blank"]}
user.errors.full_messages # ["Last name can't be blank"]

Перед вызовом метода validate или validate!, вы можете получить доступ к объекту errors, но он будет пустым, так как валидация еще не была выполнена:

user = User.new(first_name: 'John')
user.errors.full_messages # []
user.validate

user.errors.full_messages # ["Last name can't be blank"]

Методы модели для работы с БД

Методы моделей Active Record позволяют выполнять операции CRUD (Create, Read, Update, Delete) и управлять данными в БД

Создание записей

Модели Rails предоставляют методы создания объектов в БД:

Метод save() сохраняет модель и возвращает булево значение:

# Создание
user = User.new(first_name: 'John', last_name: 'Doe', active: true)
user.save # true

# Изменяем атрибут и сохраняем модель
user.first_name = 'Joan'
user.save # true

if user.save
  # ...
else
  # ...
end

Метод create() позволяет создать и сохранить модель сразу:

# Создание и сохранение в БД
user = User.create(first_name: 'John', last_name: 'Doe', active: true)

Валидация работает и на методах создания, перед сохранением в БД. Метод save() выполняет валидацию перед сохранением, и если объект не проходит валидацию, он не будет сохранен, а метод вернет false. Если валидация проходит успешно, объект будет сохранен, и метод вернет true.

user = User.new(first_name: 'John', last_name: 'Doe')

if user.save
  puts "User saved successfully!"
else
  puts "User not saved. Errors: #{user.errors.full_messages.join(", ")}"
end

user.errors.full_messages # ["First name can't be blank"]

Метод create() при неуспешной валидации не сохранит модель

user = User.create(first_name: 'John', active: true)
user.errors.messages #  ["Last name can't be blank"]

У методов создания есть аналогичные save!(), create!(), которые выбрасывают исключение

begin
  user.save!
  puts "User saved successfully!"
rescue ActiveRecord::RecordInvalid => e
  puts "User not saved. Errors: #{e.message}"
end

begin
  user = User.create!(first_name: 'John', last_name: 'Doe')
  puts "User created successfully: #{user.id}"
rescue ActiveRecord::RecordInvalid => e
  puts "User not created. Errors: #{e.message}"
end

Методы чтения

Различные методы чтения у моделей позволяют находить объекты, сортировать их

  • all() возвращает все записи из таблицы
  # Все пользователи
  users = User.all

  # SELECT "users".* FROM "users"
  puts users # [...]
  • find() находит запись по ее ID. Если запись не найдена, выбрасывает исключение
user = User.find(1) # User

begin
  user = User.find(100500)
rescue ActiveRecord::RecordInvalid => e
  puts e.message #  Couldn't find User with 'id'=100500 (ActiveRecord::RecordNotFound)
end
  • find_by() находит первую запись, соответствующую заданным условиям. Возвращает nil, если запись не найдена.
user = User.find_by(first_name: 'John') # User

user2 = User.find_by(first_name: 'Anton') # nil

Методы обновления

Методы обновления позволяют изменять существующие записи в базе данных. Метод update() обновляет атрибуты объекта и сохраняет изменения в базе данных. Если валидация не проходит, изменения не сохраняются.

user = User.find(1)
user.update(first_name: 'Jane', last_name: 'Doe') # true
  • Метод update! работает аналогично update, но выбрасывает исключение ActiveRecord::RecordInvalid, если валидация не проходит.
user = User.find(1)
user.update!(first_name: 'Jane', last_name: nil)
# Validation failed: Last name can't be blank (ActiveRecord::RecordInvalid)
  • Метод update_all() обновляет все записи, соответствующие заданному условию, и не вызывает валидацию. Он используется для массового обновления.
User.update_all(active: false)

Методы удаления

Для удаления используют метод Model.destroy(), он удаляет экземпляр объекта из БД. Метод Model.destroy_all() удаляет все записи

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

User.destroy_all

Query Builder

Query Builder — это часть Active Record, которая позволяет строить SQL-запросы с помощью методов. Методы можно комбинировать с помощью fluent interface, создавая выборки.

Выбор всех пользователей по имени John

# SELECT users.* FROM users WHERE users.first_name = 'John'
users = User.where(first_name: 'John')

Поиск без условия с лимитом

# SELECT users.* FROM users LIMIT 5
users = User.limit(5)

Можно использовать и агрегатные функции SQL

user_count = User.count

Более сложный запрос

# SELECT users.* FROM users WHERE users.name = 'John' AND users.active = 1 ORDER BY users.created_at DESC LIMIT 5
users = User.where(name: 'John', active: true).order(created_at: :desc).limit(5)

Сиды

Сиды (или seed data) в Ruby on Rails представляют собой начальные данные, которые загружаются в базу данных при первом запуске приложения или при необходимости заполнить базу данными для тестирования.

Как использовать сиды в Rails

В Rails файл сидов находится по умолчанию в файле db/seeds.rb. Этот файл используется для определения начальных данных, которые будут загружены в базу данных.

Внутри файла db/seeds.rb мы можем использовать методы ActiveRecord для создания записей. Чтобы не создавать тестовые данные вручную, можно использовать специальный гем faker:

User.create(first_name: 'John', last_name: 'Doe')
User.create(first_name: 'John', last_name: 'Smith')

100.times do
  User.create(
    first_name: Faker::Name.first_name,
    last_name: Faker::Name.last_name,
    active: [true, false].sample
  )
end

После того как мы определили начальные данные в файле db/seeds.rb, их можно загрузить их в базу данных с помощью команды:

rails db:seed

Если нужно обновить сиды, то можно просто изменить файл seeds.rb и снова запустить команду rails db:seed. Однако, если мы хотим избежать дублирования данных, то нужно очистить таблицы, используя метод destroy_all:

User.destroy_all
User.create(first_name: 'John', last_name: 'Doe')
User.create(first_name: 'John', last_name: 'Smith')

100.times do
  User.create(
    first_name: Faker::Name.first_name,
    last_name: Faker::Name.last_name,
    active: [true, false].sample
  )
end

Скоупы

Скоупы (scopes) в Ruby on Rails — это удобный способ определения часто используемых запросов к базе данных в моделях. Они позволяют создавать методы, которые возвращают наборы записей, соответствующие определенным условиям, и делают код более читаемым и поддерживаемым.

Синтаксис:

class ModelName < ApplicationRecord
  scope :scope_name, -> { where(condition) }
end

Определим скоуп активных пользователей:

class User < ApplicationRecord
  # ...
  scope :active, -> { where(active: true) }
end

Применение:

# SELECT users.* FROM users WHERE users.active = 1 LIMIT 1
active_user = User.active.first

# SELECT users.* FROM users WHERE users.active = 1
active_users = User.active

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

# SELECT users.last_name FROM users WHERE users.active = 1 AND users.first_name = 'John'
User.active.where(first_name: 'John').select(:last_name) # массив моделей

# SELECT users.last_name FROM users WHERE users.active = 1 AND users.first_name = 'John'
User.active.where(first_name: 'John').pluck(:last_name) # массив значений last_name

Rails Console

Rails Console — это REPL позволяющий работать с приложением Ruby on Rails.

Ее можно вызвать командой bin/rails c:

# bin/rails c
bin/rails console
# Находимся внутри irb

Внутри консоли можно обратиться к методам моделей, создать их, прочитать и так далее. При выполнении запросов к БД выводятся SQL запросы

irb(main):088> User.first
  User Load (0.4ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=>
#<User:0x0000723b36d86420
 id: 1,
 first_name: "Jane",
 last_name: "Doe",
 active: true,
 created_at: Wed, 25 Dec 2024 11:02:47.596244000 UTC +00:00,
 updated_at: Wed, 25 Dec 2024 11:47:47.427777000 UTC +00:00>
irb(main):089>

Консоль можно использовать для отладки:

irb(main):089> puts user.inspect
#<User first_name: "Jane", last_name: "Doe", id: 1, active: true, created_at: "2024-12-25 11:02:47.596244000 +0000", updated_at: "2024-12-25 11:47:47.427777000 +0000">
=> nil
irb(main):090>

Или загрузить данные из сидов:

irb(main):090> load 'db/seeds.rb'
=> true
irb(main):091>

Если мы что-то изменим в коде, то изменения не подтянутся автоматом. Для этого нужно использовать метод reload!

irb(main):091> reload!
Reloading...
=> true
irb(main):092>

Для выхода из консоли необходимо ввести exit.

Выводы

Модели в Ruby on Rails играют ключевую роль в управлении данными и бизнес-логикой, соответствуя таблицам в базе данных и определяя структуру данных.

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

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

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


Самостоятельная работа

  1. Повторите примеры из теории, создайте одну или несколько моделей. Например, Пользователя, Статьи, Комментарии.
  2. Добавьте валидацию в модели
  3. Создайте несколько сущностей с помощью консоли Rails
  4. В файл db/seeds.rb добавьте код для создания моделей, перед этим добавьте удаление всех записей в БД. Этот файл содержит seeds - данные, которые можно использовать для разработки. Заполните базу с помощью команды bin/rails db:seed

В итоге у вас в приложении будут одна или несколько моделей для работы с БД, а также тестовые данные в сидах


Дополнительные материалы

  1. Официальный гайд по Ruby On Rails — Active Record Basics
  2. annotate_models - полезный гем для аннотаций моделей
  3. Какие есть методы у моделей в Rails?
  4. Чем отличается метод destroy от delete в Rails?

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff