Посты / Ruby + Rails API + ReactJS + PostgreSql

19.08.2018 13:45
Привет друзья. Обещал написать много интересного и опять пропал. Снова я работаю на проекте, очень даже интересном. Поскольку, помимо PHP, я неплохо знаю Ruby и RoR, а также JS + ReactJS, то новый проект я разрабатываю на приведенных выше языках программирования и фреймворках. В этой статье немного расскажу о том, как создать базовое приложение и связать все воедино.

И так, почему выбран RoR API - ответ очевиден. Проект - это новый стартап, который создается уже на современной платформе с использованием полного разделения бэкенд части и фронтенд части. Ruby для создания бэкэнда подходит своей лаконичность, простотой и выразительностью. Я с PHP проработал 15 лет, и самый главный ужас - это читать чужой код и пытаться понять, что хотел сказать разработчик. PHP - это бесконечный рефакторинг кода и 100500 вариантов сделать одно и тоже разными способами. При этом, все они будут не очевидны. С Ruby все намного проще. Не буду вдаваться в преимущества и недостатки, все это есть в интернете. Самое главное - это скорость разработки, отличная читаемость кода, а сам язык построен таким образом, что можно создавать безумно мощные и изящные конструкции (те же блоки, лямбды и т.д.)

Для базы данных был выбран PostgreSql 10 версии. Почему он, ответ тоже очевиден - это огромная мощь, надежность и отказоустойчивость. Постоянная работа с Jsonb форматом, по которому можно строить индексы, полнотекстовый поиск, работа с географией и геометрией, огромное количество форматов, ltree деревья с мощным поиском и много еще чего. С PostgreSql большую часть логики по работе с данными можно переложить с кода на саму базу. Что скажется и на быстродействии приложения и читаемости самого кода.

И наконец, для фронтенда был выбран ReactJS + Route + MobX. Почему именно ReactJS. Все просто - это самый популярный и очень динамично развивающийся проект от Facebook. Писать код на JSX просто и быстро. Компонентная структура позволяет организовать неплохую архитектуру приложения.

Теперь о том, как все это связать вместе и заставить работать. Я думаю, что для читателя нет никаких проблем с тем, чтобы установить все проекты по отдельности и настроить базу. Поговорим о нюансах, которые могут скрываться при создании полноценного SPA (Single Page Application).

Первый момент - это безопасность вашего бэкенд приложения. На Ruby on Rails мы построили API и хотим его обезопасить. Для этого очень неплохо использовать в каждом запросе к API токен безопасности. Многие разработчики для этого используют всевозможные Ruby gem для авторизации и аутентификации, например, Device или CanCanCan. Но на самом деле тут нет ничего сложного, все можно сделать самому.

Например, можно создать хелпер для аутентификации:
module Auth
# Setter/Getter for authorized user
attr_accessor :current_user

# Check login. Error message should be first and error status - second
def require_login
render json: { message: ['Требуется авторизация'], error: { auth: false } }, status: :unprocessable_entity unless user_auth?
end

# Check user is authenticate
def user_auth?
@token = params[:token] || false
return false unless @token

@current_user = User.select(:id, :name, :token).where(token: @token).first
@current_user.nil? ? false : @current_user
end
end
Мы создали модуль с несколькими методами. Это частный случай для примера, у вас методы могут немного отличаться. Что здесь происходит:
для контроллеров, где нужно будет проверять наличие и достоверность токена, будет вызываться метод require_login, который, в свою очередь вызовет метод user_auth? В user_auth? попытается получить токен из запроса. Если его нет, то метод вернет ошибку, и require_login в ответе вернет текст с ошибкой, что 'Требуется авторизация'.

Если токен есть в запросе, то попробуем по этому токену в таблице пользователей найти пользователя (пытаемся получить из базы только три поля: идешник, имя и сам токен). Если такого пользователя нет, то опять возвращается ошибка, а если есть, то присваиваем в сеттере current_user полученного пользователя.

Позднее, вы сможете в своем коде обратиться к @current_user и получить идентификатор текущего пользователя, который сделал запрос к API.

Далее, этот модуль нужно подключить к базовому классу контроллера application_controller.rb:
class ApplicationController < ActionController::API
include Auth
end
Теперь во всех контроллерах, которые унаследованы от данного контроллера будут доступны методы аутентификации. Для того, чтобы какие-то методы в контроллере защитить аутентификацией, нужно вызвать метод before_action в начале класса:
module Api::V1
class MyController < ApplicationController
# Need have auth token
before_action :require_login

...
Такая запись защитит весь класс. Если нужно защитить только некоторые методы, то можно указать какие именно методы нужно защитить:
before_action :require_login, only: %i[show update] 
Для авторизации пользователя тоже все просто. Вы получаете запрос на авторизацию, проверяете логин и пароль. И если все правильно, то отдаете в ответе токен, имя и идентификатор пользователя. А если что-то неверно, то отдаете ошибку.

Еще один немаловажный момент, который касается роутов. По умолчанию Rails будет пытаться вызвать класс контроллера при любом запросе на роут, включая несуществующие пути. В итоге будет генерироваться ошибка, что необходимого класса не существует. Это нам совсем не нужно. Поэтому сделаем так, чтобы при запросе на несуществующие адреса сразу генерировался ответ с 404 ошибкой.

В файле роутов добавим такую строчку (можно в самый конец):
# If not correct routes then redirect to error class
match '*path', to: 'errors#not_found', via: :all
Что здесь происходит. Мы создаем условие, которое говорит о том, что мы пытаемся отловить все пути, которые не совпали с описанными выше роутами, при этом мониторим все виды запросов (get, post и т.д.). И если есть неизвестные пути в запросе, то вызываем метод not_found в классе-контроллере errors.

Сам контроллер выглядит так:
class ErrorsController < ApplicationController
# Create error exception for route
def not_found
raise ActionController::RoutingError, params[:path]
end
end
В этом контроллере, в методе not_found, мы вызываем исключение ошибку роута, куда передаем путь, по которому пришел запрос.
Но это еще не все. Нам нужно отловить ошибку неверного роута и сформировать свой ответ. Для этого немного изменим базовый контроллер, который станет в итоге выглядеть так:
class ApplicationController < ActionController::API
include Auth

# Catch router error
rescue_from ActionController::RoutingError do |exception|
logger.error "Routing error path: #{exception}"
render json: { error: 'Not found' }, status: :not_found
end

end
Здесь мы отлавливаем ошибку роута и создаем блок, в котором меняем поведение, а именно записываем неправильный путь в файл лога и рендерим Json ответ с ошибкой "Not found".

После всех этих небольших манипуляций на бэкенде ваш Rails API проект уже умеет аутентифицировать и авторизовать пользователей, а также умеет обрабатывать неправильные запросы к вашему API (например от СПАМ ботов и тому подобное).

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

0