Ruby on Railsで記事のURLをカスタマイズする方法

✏️️ 2019/08/15 👍️2019/08/16 🔗

概要

http://localhost:3000/posts/1 でアクセスしていたのを、http://localhost:3000/posts/slug でアクセスできるようにします。

Model

# app/models/post.rb

 # validates
validates :slug,
          uniqueness: { case_sensitive: false },
          length: { minimum: 3, maximum: 25 }, 
          format: { with: /\A[A-Za-z][\w-]*\z/ }

def to_param
    slug? ? slug : id.to_s
end

def self.find_by_slug_or_id(arg)
   find_by(slug: arg) || find(arg)
end

Controller

# app/controllers/posts_controller.rb
def set_post
  @post = Post.find_by_slug_or_id(params[:id])
end

Error処理

http://localhost:3000/posts/abcdefg のような存在しないURLにアクセスしたときに、404に飛ばすのではなく、
関連する可能性のあるPostを表示するようにします。

Concernsを作成

# app/controllers/concerns/search_fallback.rb

 module SearchFallback
  extend ActiveSupport::Concern

   included do
    rescue_from ActiveRecord::RecordNotFound do |_exception|
      @query = params[:id].gsub(/[^\w-]/, '')
      @posts = Post.where('title LIKE ? OR slug LIKE ?', "%#{@query}%", "%#{@query}%")
      render '/search/show'
    end
  end
end

Controller

Controllerで先程作成した app/controllers/concerns/search_fallback.rb を読み込みます。

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  include SearchFallback

View

<div class="container">
  <% if @posts.any? %>
    <p class="title is-4">記事は見つかりませんでした。お探しの記事はこちらでしょうか?</p>
    <div class="tile is-ancestor is-vertical">
      <%=render partial: 'layouts/post', collection: @posts %>
    </div>
  <% else %>
    <p class="title is-4">お探しの記事は見つかりませんでした。</p>
    <p><%= link_to '記事一覧で確認', posts_path %></p>
  <% end %>
</div>

akito
日本のスタートアップで主にRuby on Railsを使ってプロダクト開発をしています。