Рубин на рельсах

Следующая запись в блоге представляет собой краткое вводное руководство по обработке ошибок и исключений в Rails. Это ни в коем случае не исчерпывающее руководство, и я не утверждаю, что приведенные ниже фрагменты кода являются лучшими практиками или что-то в этом роде. практика помогла мне сделать мой код СУХИМ* и красивым, лол.

Я предполагаю, что одной из основных целей обработки ошибок в Rails является предоставление клиенту/пользователю на внешнем интерфейсе некоторого индикатора того, что его попытка действия или запроса была, в некотором смысле, получена. и/или казнены. Даже если кто-то попытается ввести «плохие» данные или попытается сделать несанкционированный запрос к определенному серверу, весьма полезно уведомлять клиентское приложение о результатах, так как это позволяет нам встроить некоторые функции или условные запросы. рендеринг. Это кажется жизненно важным в нашем современном мире, поскольку разница даже между 5-секундным рендерингом и 10-секундным рендерингом имеет большое значение.

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

Установка

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

Мы могли бы создать таблицу с именем читатели:

Наша модель может выглядеть примерно так:

class Reader < ApplicationRecord 
  has_many :books
  validates :username, uniqueness: true
  validates :age, inclusion: 16..95
end 

И наш контроллер может выглядеть так:

class ReadersController < ApplicationController
  def create 
    new_reader = Reader.create(reader_params)
    render json: new_reader, status: :created
  end 
  def show 
    reader = Reader.find(params[:id])
    render json: reader, status: :ok
  end
  private
  def reader_params
    params.permit(:username, :full_name, :age, :bio)
  end
end

В нынешнем коде ничего не настроено для обнаружения ошибок. Предположим, новый пользователь пытается создать новый профиль с уже занятым именем пользователя. Так как мы заложили проверку уникальности в нашу модель Reader, это действие по сути сломает наш код, не будет работать и т.д. А если пользователь попытается посмотреть профиль несуществующего читателя? Нет ничего, что могло бы перехватить ошибку таким образом, чтобы сообщить нашему интерфейсу о том, что происходит (Rails автоматически выдаст ошибку и отправит серверу статус «запись не найдена», но наша клиентская сторона этого не видит!).

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

Предлагаемые решения

Перво-наперво, давайте отметим, что наш ReadersController наследуется от нашего стандартного Application Controller выше — имея это в виду, мы можем использовать родословную.

1. Учитывая установленное наследование, давайте определим метод в нашем родительском контроллере, который затем будет просачиваться как к контроллерам Books, так и к контроллерам Readers.

class ApplicationController < ActionController::API
  def invalid_records(instance)
    render json: {errors: instance.record.errors.full_messages},
    status: :unprocessable_entity
  end 
end 

Затем мы можем сослаться на этот недавно определенный метод, используя метод escape_from, предоставляемый Rails и Active Record:

class ApplicationController < ActionController::API
  rescue_from ActiveRecord::RecordInvalid, with: :invalid_records
  private
  def invalid_records(instance)
    render json: {errors: instance.record.errors.full_messages},
    status: :unprocessable_entity
  end
end

С этой настройкой все контроллеры, унаследованные от нашего базового, теперь будут иметь определенный маршрут, по которому пользователь попытается создать или обновить запись, которая не соответствует тому, как мы определили остальные наши данные. Поскольку неверный код состояния часто является результатом создания и обновления, мы можем передать этот объект экземпляра, который пытался быть создан или обновлен, и с помощью приведенного выше синтаксиса вернуть сообщения об ошибках во внешний интерфейс, указывая на ошибку (т.е. имя пользователя должно быть уникальный и др.).

Последнее, что нам нужно сделать, это добавить восклицательный знак в конец того места, где мы вызываем create себя, чтобы контроллер знал, как обрабатывать исключение (плохой запрос, который невозможно сохранить в базе данных):

class ReadersController < ApplicationController
  def create 
    new_reader = Reader.create!(reader_params)
    render json: new_reader, status: :created
  end
end

2. Для нашей второй проблемы я считаю, что лучше всего написать метод escape_from внутри самого указанного контроллера, таким образом, мы можем специализировать возвращаемое сообщение об ошибке, подробнее об этом чуть позже. Давайте определим метод not_found:

class ReadersController < ApplicationController
  . . .
  private
  . . . 
  def not_found
    render json: {error: "Reader Not Found:-("},
    status: :not_found
  end
end

Что касается определения этого в указанном контроллере, вы также можете полностью определить это в контроллере приложений! Однако здесь, если бы мы хотели сделать отдельный для контроллера книг (не изображенный, а воображаемый), мы могли бы также создать указанный там, чтобы сказать что-то вроде «Книга не найдена». Затем мы добавляем в метод relay_from:

class ReadersController < ApplicationController
  rescue_from ActiveRecord::RecordNotFound, with: :not_found
  def create 
    new_reader = Reader.create!(reader_params)
    render json: new_reader, status: :created
  end
  def show 
    reader = Reader.find(params[:id])
    render json: reader, status: :ok
  end
  private
  def reader_params
    params.permit(:username, :full_name, :age, :bio)
  end
  def not_found
    render json: {error: "Reader Not Found:-("},
    status: :not_found
  end
end

Здесь также важно отметить, что мы использовали find (неявно ищет id), а не find_by. В то время как find автоматически вернет код ошибки 404, find_by просто вернет nil, если запись не будет найдена. Используя здесь метод escape_from, мы можем указать, какую информацию json мы хотели бы вернуть, и можем сделать ответ как можно более конкретным! Потрясающий.

Теперь, если пользователь пытается отправить данные, не соответствующие нашим спецификациям, клиентский интерфейс получает уведомление, имы отправляем специальное сообщение, если читатель не найден. Затем это также будет работать с определением действия уничтожения, и может быть очень полезным!

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

Спасибо за чтение и удачного кодирования!

ps, я предполагаю, что если вы читаете это, вы знаете, что означает DRY, но на всякий случай подумайте Dне Rповторяйте Y сами :-)

pps, или повторяйтесь, мне все равно