どうも、最近Ruby on Railsでサービスを作ろうと思って絶賛手探り中、たくチャレ(@takuchalle)です。

ツイートや記事のように何かを投稿するようなアプリを考えた場合、Rails でそのまま何も考えずに作るとhttps://hogehoge.com/posts/1https://hogehoge.com/posts/4みたいに連番のURLでアクセスするようになると思います。

今回は、この URL を連番ではなくランダムになるように実装する方法を紹介します。

ランダムにしたい理由

連番だと URL が容易に推測されて、アクセス制御に漏れがあった場合セキュリティリスクになってしまいます1。 特に個人情報を扱っているサイトでは致命的です。

また、サービスを立ち上げたばかりの時に URL が若い番号になってしまい、新規ユーザに対してあまり盛り上がっていない印象を与えてしまいます。

この2つの理由から僕は投稿のURLやユーザのURLは連番ではなく、ランダムな文字列にしたほうが良いと考えています。

対象バージョン

  • Ruby 2.5.1
  • Rails 5.2.1

想定アプリ

Twitter のような短文を投稿できるアプリで、簡易のためログインなしで匿名で投稿できることを想定しています。

次の流れで実装していきます。

  • Postモデルを作成
  • PostsControllerを作成
  • 投稿して連番の URL のままアクセスできることを確認
  • ランダムな URL を生成するように修正
  • 生成したランダムな URL でアクセスできることを確認

最終的なコードはGitHubに置いておきます。

Contribute to takuyaohashi/rails_random_url development by creating an account on GitHub.

Post モデルの作成

contentというテキストフィールドのみを持つPostモデルを生成して、マイグレーションします。

$ bundle exec rails g model Post content:text
$ bundle exec rails db:migrate

PostsController の作成

次にコントローラの作成です。

今回は投稿と閲覧だけできればいいので、new,showアクションを持つコントローラを作成します。

$ bundle exec rails g controller Posts new show

上記コマンドでルーティングが書き換わります。投稿された時のcreateアクションも必要なので以下のようにconfig/routes.rbを書き換えます。

Rails.application.routes.draw do
  resources :posts, only: %i[new create show]
end

この状態でローカルサーバを立ち上げて、localhost:3000/posts/newにアクセスできることを確認してください。

投稿フォーム作成

次に投稿フォームを作って投稿できるようにし、投稿内容を確認できるようにします。

このあたりの内容は分かってるものとして進みます。基本的に最低限のことしかやっておらず、エラーハンドリングは何も考えてないので突っ込まないでください。

修正するファイルは3つです。

まずコントローラを修正します。app/controllers/posts_controller.rbを開いて次のように修正します。

class PostsController < ApplicationController
  def new
    @post = Post.new
  end

  def create
    @post = Post.new(post_params)
    if @post.save
      redirect_to @post
    end
  end
  
  def show
    @post = Post.find(params[:id])
  end

  private
    def post_params
      params.require(:post).permit(:content)
    end
end

次に投稿フォームの作成です。app/views/posts/new.html.erbを開いて次のように修正します。本当に最低限の実装のフォームです。

<%= form_for(@post) do |f| %>

<%= f.label :content %>
<%= f.text_area :content, placeholder: "Type anything..." %>

<%= f.submit "Post" %>
<% end %>

最後に投稿した内容を表示します。app/views/posts/show.html.erbを開いて次のように修正します。投稿した内容だけ表示します。

<%= @post.content %>

この状態でローカルサーバを立ち上げて、localhost:3000/posts/newにアクセスし、投稿できることを確認してください。この時にリダイレクトされる URL も確認してみてください。localhost:3000/posts/1になってるはずです。

ランダムな文字列の生成

ランダムな文字列を URL にしてアクセスできるようにPostモデルにtokenを追加します。モデルの生成時にtokenにランダムな文字列を入れて、URL として使います。

まずPostモデルにtokenカラムを追加してマイグレーションします。

$ bundle exec rails g migration add_token_to_posts token:string
$ bundle exec rails db:migrate

SecureRandomモジュールのurlsafe_base64を使ってランダムな文字列を生成します。生成タイミングとしてはデータベースに保存される前ならいつでもいいのでbefore_createコールバックに指定しておきます。

require 'securerandom'

class Post < ApplicationRecord
  before_create :generate_token

  private

  def generate_token
    self.token = SecureRandom.urlsafe_base64
  end
end

本来ならtokenがユニークになってるかのチェックが必要です。どのタイミング・どのレイヤでやるべきかまだ分からないのでいつか追記します。2

この状態でローカルサーバを立ち上げて、localhost:3000/posts/newにアクセスし、投稿できることを確認してください。ブラウザ上からtokenは分かりませんので、rails consoleを使って投稿したデータを見るとtokenが保存されていることが分かります。

irb(main):003:0> Post.find(2)
  Post Load (0.2ms)  SELECT  "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  => #<Post id: 2, content: "aaaaa", created_at: "2018-11-22 06:37:57", updated_at: "2018-11-22 06:37:57", token: "4HfKquKEbAE79dW95chhYA">

この場合、token"4HfKquKEbAE79dW95chhYA"が入ってることが分かります。

ランダムなURLに変更

先ほど生成したtokenでアクセスできるようにします。以下のようにconfig/routes.rbを書き換えます。差分が分かりにくいですが、末尾に, param: :tokenがつきました。

Rails.application.routes.draw do
  resources :posts, only: %i[new create show], param: :token
end

これでURLの文字列をparams[:token]で取得できるようになります。それを使ってapp/controllers/posts_controller.rbを以下のように書き換え、検索とリダイレクトを行うようにします。ハイライトされている部分が変わりました。

class PostsController < ApplicationController
  def new
    @post = Post.new
  end

  def create
    @post = Post.new(post_params)
    if @post.save
      redirect_to post_url(token: @post.token)
    end
  end
  
  def show
    @post = Post.find_by(token: params[:token])
  end

  private
    def post_params
      params.require(:post).permit(:content)
    end
end

この状態でローカルサーバを立ち上げて、localhost:3000/posts/newにアクセスし、投稿できることを確認してください。この時にリダイレクトされた URL が連番の ID ではなく、ランダムな文字列になっていたら成功です!

まとめ

Ruby on Railsで URL にランダムな文字列を使う方法を紹介しました。

ここで紹介したのは本当に最低限の実装ですので、セキュリティ面や性能面で気を付ける必要がある部分は残っていますが、ランダムな文字列を URL に使う方法の雰囲気は掴めたのではないでしょうか。

GitHubにコードを登録してあるので試してみてください。

Contribute to takuyaohashi/rails_random_url development by creating an account on GitHub.

プルリクでも Twitter のリプライ(@takuchalle)でもフィードバックお待ちしています。


  1. もちろんテストをしっかり行って漏れがないようにすべきですし、ランダムだから安全というわけではありません。
  2. SecureRandom のランダム具合でどこでチェックすべきか変わりそう

同じカテゴリの記事