Я создал базовый веб-интерфейс API с помощью Sinatra и Active Record для поддержки фронтенда React. Здесь я расскажу о некоторых ключевых моментах, связанных с созданием серверной части этого приложения.
Отношения «один ко многим
Таблицы в этой базе данных имеют отношения «один ко многим». Я создал две таблицы, маршруты и спортсмены. Одна тропа «принадлежит» многим спортсменам. Ниже на диаграмме отношений сущностей показано, что внешний ключ trail_id
в таблице athletes связан с id
в trails.
Диаграмма отношений между сущностями:
Активная запись
Я наследую от Ruby gem Object Relational Mapper, Active Record, чтобы определить отношения между двумя классами, спортсменами и трейлами в файлах приложения/модели:
class Athlete < ActiveRecord::Base
belongs_to :trail
end
class Trail < ActiveRecord::Base
has_many :athletes
end
Я использую команды Rake для создания файлов миграции:
Я определяю структуру таблиц базы данных в файлах db/migrate:
class CreateAthletes < ActiveRecord::Migration[6.1]
def change
create_table :athletes do |t|
t.string :name
t.string :time
t.integer :trail_id
t.boolean :unsupported
t.timestamps
end
end
end
Важно помнить о необходимости следовать соглашениям Active Record об именовании, используя snake_case для имен файлов миграции и CamelCase для имен классов, а также сохраняя временную метку файлов миграции.
Также важно определить имена таблиц как множественное число, а имена классов — как единственное. При определении отношений таблиц следует использовать троп единственного числа belongs_to
и множественного числа has_many
при связывании таблиц.
Active Record ожидает эти соглашения об именовании, чтобы распознать ассоциации между данными; «конвенция над конфигурацией». Временные метки файла миграции позволяют Active Record управлять контролем версий/схемой.
В файле app/controllers/application_controller я наследую от Sinatra(Controller), чтобы фронтенд (View) мог подключаться к базе данных (Model).
Файл schema.rb показывает текущую версию базы данных. Номер версии соответствует временным меткам в файлах миграции — именно так Active Record поддерживает контроль версий.
ActiveRecord::Schema.define(version: 2022_07_20_184513) do
Я использую динамическую маршрутизацию для обработки действий CRUD. Например, со стороны клиента POST-запрос будет выглядеть следующим образом:
const handleAddTrail = (newTrail) => {
fetch("http://localhost:9292/trails", {
method: 'POST',
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(newTrail),
})
.then(r => r.json())
.then((newTrail) => handleAddTrailToTrails(newTrail))
}
Со стороны сервера:
post '/trails' do
trail = Trail.create(
name: params[:name],
location: params[:location],
distance: params[:distance],
elevation_gain: params[:elevation_gain]
)
trail.to_json(include: :athletes)
end
В приведенном выше примере POST-запроса важно помнить об использовании include:
для доступа к связанным данным между таблицами. В противном случае в данном примере возвращаемый объект newTrail
не будет включать массив спортсменов [].
Общие мысли об Active Record и Sinatra
Изучение кода и создание базы данных с помощью этих инструментов не было слишком сложным процессом. Проблемы, на которых я зацикливался, были связаны с файловой структурой бэкенда и спецификой соглашений об именах.
Я также начал с большого количества кода в файле application_controller для обработки запросов, прежде чем понял, что связанные данные означают, что мне нужна только первоначальная выборка из таблицы trails, чтобы также получить все данные, которые мне нужны от спортсменов.