Pull to refresh

Rails переадресация старых url

Reading time 3 min
Views 3.6K
Здесь опишу как я боролся с переездом страниц на новые url.
Эта заметка рассчитана на новичков в Ruby On Rails.

Изначально имею свой проект на Ruby on Rails, структура url в нем выглядит следующим образом: /locale/group/product
пример: /ru/bar-code-scanners/datalogic-magelan-1100i

group и product это permalink — строка по которой осуществляется поиск в DB, в место id.

Проблема в том, что пользователи которые добавляют контент на сайт иногда допускают ошибки в permalink.
Вот пример: /ru/bar-code-scanners/datalogic-magelan-1100i
Ошибка в том, что магелан пишется с двумя ll — magellan.
Но товар был добавлен относительно давно и страница уже проиндексирована поисковиками, по этому стоит задача исправить permalink и настроить пере адресацию на новый URL.

Конечно эту задачу можно решить на уровне nginx или apache. Но к web серверу обычно есть доступ только у администраторов и плюс править redirect еще и для нескольких locale довольно рутинная задача.
По этому я решил это дело автоматизировать, для этого создал простенькую модель Redirection с полиморфной связью, то есть она может принадлежать как Product так и Group.

Файл миграции 20131113223332_create_redirections.rb
class CreateRedirections < ActiveRecord::Migration
  def change
    create_table :redirections do |t|
      t.references :redirectable, polymorphic: true
      t.string :permalink

      t.timestamps
    end
  end
end


А вот как выглядит сама модель Redirection:
class Redirection < ActiveRecord::Base
  attr_accessible :permalink

  belongs_to :redirectable, polymorphic: true

  def self.product(permalink)
    redirection = Redirection.where(permalink: permalink, redirectable_type: "Product").first
    redirection.redirectable if not redirection.nil?
  end

  def self.group(permalink)
    redirection = Redirection.where(permalink: permalink, redirectable_type: "Group").first
    redirection.redirectable if not redirection.nil?
  end

end


Теперь осталось подправить каждую модель, для которой нужно настроить пере адресацию Group и Product.
Добавляем связь с моделью Redirection.
  has_many   :redirections, as: :redirectable, :dependent=>:destroy


Теперь нужно реализовать отслеживать изменения атрибута permalink и при необходимости создавать запись Redirection.
К счастью все модели которые имею permalink наследованны от одного моего промежуточного класса AbstractContent. Так что достаточно добавить код отслеживания изменение permalink только в этом классе, не нарушая принципов DRY.

И вот за что я люблю Rails — само отслеживание реализовать оказалось элементарно. Rails расширяет нашу модель и ее атрибуты очень удобными методами _changed? и _was.

Все что нужно, это добавить callback after_save, который и будет отслеживать изменения и в случае необходимости создаст новую запись модели Redirection

class AbstractContent < ActiveRecord::Base

  self.abstract_class = true

  after_save :check_permalink_changes

  def check_permalink_changes
    if self.permalink_changed?
      if self.permalink_was
        self.redirections.create!(permalink: self.permalink_was)
      end

    end
  end

end



Осталось только настроить саму переадресацию в контроллере

class GroupsController < ApplicationController

  def show
    @group = Group.find_by_permalink(params[:id]) 
    if @group.nil?
      @group = Redirection.group(params[:id]) || not_found
      redirect_to group_path(@group), status: 301
    end
  end
end


И не забываем добавить status 301 — moved permanently. По умолчанию возвращается 302 — moved temporarily.

Думаю, что приводить код второго контроллера здесь нет смысла потому, что он аналогичен.
Так же замечу, что в реальном коде проекта поиск find_by_permalink осуществляется через кеш, find_in_cache отсюда убрал для упрощения примера.

Для удобного вызова ошибки 404 добавил в ApplicationController метод not_found вызывающий Exception.

  def not_found
    raise ActionController::RoutingError.new('Not Found')
  end


К стати, если не охота рыться в production.log, а есть желание просматривать все ошибки по запросам URL именно нужной нам структуры /group/product. То здесь удобно добавить логирование всех вызовов not_found в отдельный файл.

P.S. Ну и можно еще дополнительно добавить RedirectionsController для управления и отслеживания всех переадресаций.
Tags:
Hubs:
+5
Comments 4
Comments Comments 4

Articles