rescue_from: a única linha deste app, de um slug errado até uma página customizada
Onde este app levanta a exceção
As páginas de curso e de aula deste app são achadas pelo slug, não por um id numérico. Os query objects por trás delas:
# app/queries/catalog/find_course_by_slug_query.rb
Result.new(course: ::Catalog::Course.active.find_by!(slug: slug))
# app/queries/catalog/find_lesson_by_slug_query.rb
lesson = course.lessons.find_by(slug: slug)
return Result.new(lesson: lesson, renamed: false) if lesson
Result.new(lesson: course.lessons.where("? = ANY(previous_slugs)", slug).take!, renamed: true)
O find_by!, a versão com exclamação, levanta ActiveRecord::RecordNotFound quando nenhuma linha casa: Catalog::FindCourseBySlugQuery.call(slug: "does-not-exist") levanta ActiveRecord::RecordNotFound: Couldn't find Catalog::Course .... A query de aula tenta primeiro o slug atual, depois os slugs antigos da aula, para que um link velho de uma aula renomeada ainda possa ser redirecionado. Só quando nenhum dos dois casa o take! levanta a mesma exceção. Uma URL digitada errado, um favorito antigo para um curso removido ou um slug que nunca existiu terminam todos nessa classe de exceção.
A única linha que a captura
O app/controllers/application_controller.rb tem uma chamada rescue_from, e todo controller deste app herda dele:
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
end
Uma linha. Toda ação de todo controller, do Catalog::CoursesController ao Enrollment::LessonCompletionsController e ao Public::ProfilesController, tem o mesmo comportamento quando levanta ActiveRecord::RecordNotFound, sem que nenhum deles escreva o próprio begin/rescue.
O render_not_found, como está escrito
O with: :render_not_found nomeia um método privado, também no ApplicationController:
def render_not_found
respond_to do |format|
format.html do
@not_found = not_found_context
@suggested_courses = Public::NotFoundSuggestionsQuery.call.courses
render "errors/not_found", status: :not_found
end
format.any { head :not_found }
end
end
def not_found_context
if params[:username].present?
{ kind: :profile, name: params[:username] }
elsif params[:course_slug].present? && params[:slug].present?
{ kind: :lesson, name: params[:slug].to_s.tr("-", " ") }
elsif params[:slug].present? || params[:course_slug].present?
{ kind: :course, name: (params[:slug] || params[:course_slug]).to_s.tr("-", " ") }
else
{ kind: :page, name: nil }
end
end
O not_found_context lê os params que a requisição que falhou tinha e decide o que sumiu. Pode ser um perfil (params[:username]), uma aula (course_slug e slug), um curso (só um dos dois) ou uma página genérica. O render_not_found separa por formato. Uma requisição HTML recebe a view estilizada errors/not_found, com esse contexto e uma lista de cursos sugeridos. Qualquer outra, como um cliente de API pedindo JSON, recebe um head :not_found seco: um status 404 sem corpo.
A view que ele renderiza
O app/views/errors/not_found.html.erb usa @not_found[:kind] para escolher um ícone: uma pessoa para um perfil que falta, um livro para o resto. Ele também busca o título, o corpo e a dica com t("errors.not_found.heading.#{kind}", name: name) e os irmãos dele, uma chave de i18n por tipo. Abaixo disso, o @suggested_courses, quando há algum, aparece como uma grade de cards de curso com os mesmos links catalog_course_path e selos de nível usados no resto do app.
O que uma linha rescue_from substitui
Sem ela, toda ação que pode levantar ActiveRecord::RecordNotFound precisaria do próprio begin/rescue em volta dos mesmos passos: decidir o tipo, carregar sugestões, renderizar a view, definir o status. O rescue_from, registrado uma vez no ApplicationController, mantém essa lógica num lugar só em vez de copiada em todo controller que busca algo pelo slug.
O que este curso cobre
Sete aulas, sobre o código deste app e o código-fonte instalado do Rails. Esta aula trata do uso neste app, do começo ao fim. A aula 2 trata das duas formas que o rescue_from aceita, with: e um bloco. A aula 3 trata do que acontece com mais de um rescue_from: ordem de declaração e herança, testadas contra o algoritmo de casamento do Rails. A aula 4 trata do is_a? e da família de exceções do ActiveRecord que este app nunca precisou nomear uma a uma. A aula 5 trata do mapeamento padrão do Rails de exceções para códigos de status, e do que um rescue_from próprio acrescenta. A aula 6 trata dos dois jeitos de uma exceção ainda passar: nenhum handler casa, ou o próprio handler falha. A aula 7 fecha com um pequeno simulador do algoritmo de casamento.
Continue com a aula 2
with: ou um bloco: as duas formas que rescue_from aceita
Compara as formas with: e bloco, que ocupam o mesmo espaço, e mostra como a aridade controla o argumento.
Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.