- Подготовка
- Что такое модель
- Миграции
- schema.rb
- Модели
- Валидация модели
- Методы модели для работы с БД
- Сиды
- Скоупы
- Rails Console
- Выводы
В этом уроке мы познакомимся с моделями в 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 играют ключевую роль в управлении данными и бизнес-логикой, соответствуя таблицам в базе данных и определяя структуру данных.
Миграции предоставляют разработчикам возможность изменять структуру базы данных с помощью кода, что упрощает процесс управления схемой и обеспечивает версионирование изменений.
Валидация данных перед их сохранением гарантирует целостность и корректность информации, предотвращая ошибки. Методы работы с данными, такие как создание, чтение, обновление и удаление записей, делают взаимодействие с базой данных более удобным.
Сиды позволяют загружать начальные данные в базу для тестирования и разработки, а скоупы упрощают создание часто используемых запросов, делая код более читаемым и поддерживаемым
Самостоятельная работа
- Повторите примеры из теории, создайте одну или несколько моделей. Например, Пользователя, Статьи, Комментарии.
- Добавьте валидацию в модели
- Создайте несколько сущностей с помощью консоли Rails
- В файл db/seeds.rb добавьте код для создания моделей, перед этим добавьте удаление всех записей в БД. Этот файл содержит seeds - данные, которые можно использовать для разработки. Заполните базу с помощью команды
bin/rails db:seed
В итоге у вас в приложении будут одна или несколько моделей для работы с БД, а также тестовые данные в сидах
Дополнительные материалы
- Официальный гайд по Ruby On Rails — Active Record Basics
- annotate_models - полезный гем для аннотаций моделей
- Какие есть методы у моделей в Rails?
- Чем отличается метод destroy от delete в Rails?
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.