🔑

【Rails】primary_key(主キー)をランダムな文字列にする

2024/09/17に公開

はじめに

お疲れ様です。
おおくまです。

今回は、「【Rails】primary_key(主キー)をランダムな文字列にする」ということで、Ruby on Railsにおいて、primary_keyをランダムな文字列にする方法についてまとめてみました。

少しでも皆様の参考になりますと幸いです。

対象読者

注意点

環境

primary_key(主キー)とは

primary_keyとは、データベースのテーブルにおいて、各レコードを一意に識別するためのキーのことです。
そのため、primary_keyは、重複する値を持つことができません。

Ruby on Railsでは、特に指定しない場合、idカラムがprimary_keyとして設定され、1、2、3、4、5、・・・というように、順にidが振られていきます。

https://www.sejuku.net/blog/52356

なぜprimary_key(主キー)をランダムな文字列にするのか

Ruby on Railsでは、/posts/:id/editのようなURLを生成する際に、idカラムの値を使用します。
先ほども述べた通り、idカラムは通常、1、2、3、4、5、・・・というように、順にidが振られていきますので、パスが推測されやすくなります。

app/controllers/posts_controller.rb
def edit
  @post = current_user.posts.find(params[:id])
end
app/controllers/posts_controller.rb
def edit
  @post = Post.find(params[:id])
end

1つ目の例のように、current_user起点で、Postテーブルからレコードを取得する場合は、アドレスバーのidの値を打ち変えたとしても、他のユーザーの投稿を編集することはできません。

しかし、万が一、2つ目の例のように、Postテーブルから直接レコードを取得するようにコードを書いてしまうと、アドレスバーのidの値を打ち変えることで、他のユーザーの投稿を編集することができてしまいます。

開発を進める中で、アドレスバーのidを打ち変えたとしても、他のユーザーの投稿を編集することができないようにするために、1つ目の例のような対策をすることも重要ですが、そもそも推測されにくいようなパスを生成することも重要です。

そこで、primary_keyをランダムな文字列にすることで、パスを推測されにくくすることができます。

primary_key(主キー)をランダムな文字列にする方法

環境構築が済んでいる段階からスタートします。

ランダムな文字列を生成するモジュールを定義する

まずは、app/models/concernsディレクトリにid_generate_module.rbというランダムな文字列を生成するためのモジュールを作成します。

app/models/concerns/id_generate_module.rb
module IdGenerateModule
  extend ActiveSupport::Concern

  included do
    before_create :generate, if: -> { has_attribute?(:id) }
  end

  def generate
    self.id ||= loop do
      id = SecureRandom.hex(5)
      break id unless self.class.exists?(id: id)
    end
  end
end

id = SecureRandom.hex(5)の部分で、ランダムな文字列を生成しています。
括弧内の数字を変更することで、生成される文字列の長さを変更することができます。
今回は、10文字のランダムな文字列を生成するようにしています。

次に、app/models/application_record.rbIdGenerateModuleをインクルードします。

app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
+ include IdGenerateModule
  primary_abstract_class
end

これで、primary_keyをランダムな文字列にする準備が整いました。

Postテーブルを作成する

では、実際にテーブルを作成してみます。

ターミナル
docker compose run app rails g scaffold Post title:string content:text

次に、マイグレーションファイルを編集します。

db/migrate/20240914035018_create_posts.rb
class CreatePosts < ActiveRecord::Migration[7.0]
  def change
+   create_table :posts, id: :string, limit: 10 do |t|
-   create_table :posts do |t|
      t.string :title
      t.text :content

      t.timestamps
    end
  end
end

このように編集することで、Postテーブルのidカラムの型をstringに変更することができます。

次に、マイグレーションを実行します。

ターミナル
docker compose run app rails db:migrate

db/schema.rbを確認してみると、Postテーブルのidカラムの型がstringになっています。

db/schema.rb
ActiveRecord::Schema[7.0].define(version: 2024_09_14_035018) do
  create_table "posts", id: { type: :string, limit: 10 }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
    t.string "title"
    t.text "content"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

end

Postテーブルにレコードを作成する

では、実際にレコードを作成してみます。

このようにルーティングを定義し、 http://localhost:3000/ にアクセスします。

config/routes.rb
Rails.application.routes.draw do
  root 'posts#index'
  resources :posts
end

ここから、New Postをクリックし、新規投稿を作成します。

無事にレコードが作成されました。

では、コンソールでidを確認してみます。

ターミナル
docker compose run app rails c
コンソール
irb(main):001> Post.first
  Post Load (0.7ms)  SELECT `posts`.* FROM `posts` ORDER BY `posts`.`id` ASC LIMIT 1
=>
#<Post:0x0000ffff66c0d7b0
  id: "6494ac5f80",
  title: "タイトル",
  content: "本文",
  created_at: Sat, 14 Sep 2024 05:01:39.153767000 UTC +00:00,
  updated_at: Sat, 14 Sep 2024 05:01:39.153767000 UTC +00:00>

無事にidがランダムな文字列になっていることが確認できました。

また、詳細ページ、編集ページのURLも、idの部分が同じようにランダムな文字列になっていました。

foreign_key(外部キー)をランダムな文字列にする

次に、Postテーブルと1対多の関係にあるCommentテーブルにも同様の設定を適用してみます。
ここでのポイントは、Commentテーブルのforeign_keyであるpost_idカラムの型をstringに変更することです。

Commentテーブルを作成する

まずは、Commentテーブルを作成します。

ターミナル
docker compose run app rails g scaffold Comment post:references content:text

次に、マイグレーションファイルを編集します。

db/migrate/20240914052451_create_comments.rb
class CreateComments < ActiveRecord::Migration[7.0]
  def change
+   create_table :comments, id: :string, limit: 10 do |t|
-   create_table :comments do |t|
+     t.references :post, type: :string, null: false, foreign_key: true
-     t.references :post, null: false, foreign_key: true
      t.text :content

      t.timestamps
    end
  end
end

このように編集することで、Commentテーブルのidカラム、post_idカラムの型をstringに変更することができます。

次に、マイグレーションを実行します。

ターミナル
docker compose run app rails db:migrate

db/schema.rbを確認してみると、Commentテーブルのidカラム、post_idカラムの型がstringになっています。

db/schema.rb
ActiveRecord::Schema[7.0].define(version: 2024_09_14_052451) do
  create_table "comments", id: { type: :string, limit: 10 }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
    t.string "post_id", null: false
    t.text "content"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["post_id"], name: "index_comments_on_post_id"
  end

  create_table "posts", id: { type: :string, limit: 10 }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
    t.string "title"
    t.text "content"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  add_foreign_key "comments", "posts"
end

Commentテーブルにレコードを作成する

では、実際にレコードを作成してみます。

このようにルーティングを定義します。

config/routes.rb
Rails.application.routes.draw do
  root 'posts#index'
  resources :posts do
    resources :comments, only: [:create]
  end
end

次に、app/models/post.rbを編集します。

app/models/post.rb
class Post < ApplicationRecord
  has_many :comments, dependent: :destroy
end

次にapp/controllers/comments_controller.rbを編集します。

app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  before_action :set_post

  def create
    @comment = @post.comments.build(comment_params)
    if @comment.save
      redirect_to @post, notice: 'Comment was successfully created.'
    else
      render :new
    end
  end

  private

  def set_post
    @post = Post.find(params[:post_id])
  end

  def comment_params
    params.require(:comment).permit(:content)
  end
end

最後に、app/views/posts/show.html.erbを編集します。

app/views/posts/show.html.erb
<p style="color: green"><%= notice %></p>

<%= render @post %>

<div>
  <%= link_to "Edit this post", edit_post_path(@post) %> |
  <%= link_to "Back to posts", posts_path %>

  <%= button_to "Destroy this post", @post, method: :delete %>
</div>

<hr>

<p>Comments</p>

<% @post.comments.each do |comment| %>
  <div>
    <strong>Comment:</strong>
    <p><%= comment.content %></p>
  </div>
<% end %>

<p>Add a new comment</p>
<%= form_with(model: [@post, Comment.new], local: true) do |form| %>
  <div>
    <%= form.label :content, style: "display: block" %>
    <%= form.text_area :content %>
  </div>
  <div>
    <%= form.submit %>
  </div>
<% end %>

先ほど作成したPostレコードの詳細ページにアクセスし、Comenntレコードを作成してみます。

無事にレコードが作成されました。

では、コンソールでidを確認してみます。

ターミナル
docker compose run app rails c
コンソール
irb(main):001> Comment.first
  Comment Load (0.6ms)  SELECT `comments`.* FROM `comments` ORDER BY `comments`.`id` ASC LIMIT 1
=>
#<Comment:0x0000ffff8e274a78
  id: "3d6cb29b00",
  post_id: "6494ac5f80",
  content: "コメント",
  created_at: Sat, 14 Sep 2024 06:03:32.050693000 UTC +00:00,
  updated_at: Sat, 14 Sep 2024 06:03:32.050693000 UTC +00:00>

無事にid、post_idがランダムな文字列になっていることが確認できました。

このように、foreign_keyも、primary_keyと同じようにstring型に変更することができます。

注意点

一覧画面などで、レコードが作成された順に表示したい場合、idカラムを使用して並び順を指定することがあると思います。

app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    @posts = Post.all.order(id: :asc)
  end
end

しかし、idカラムをランダムな文字列に変更すると、idカラムを使用して並び替えても、レコードが作成された順に表示されなくなります。

この場合は、created_atカラムを使用して並び替えるようにします。

app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    @posts = Post.all.order(created_at: :asc)
  end
end

まとめ

Ruby on Railsで、primary_keyforeign_keyをランダムな文字列にする方法についてまとめてみました。

少しでも皆様の参考になりましたら幸いです。

最後までお読みいただき、ありがとうございました。

参考文献

https://www.sejuku.net/blog/52356

https://utouto97.hatenablog.com/entry/2021/06/29/230440

https://zenn.dev/yusuke_docha/articles/1b162804f7ea12

https://qiita.com/decoy0318/items/81bdb0bc01302d17bf3a

https://qiita.com/belion_freee/items/8f8f1d1e5333da561fd8

https://qiita.com/HeRo/items/2816e27fb3066db6c4e6

https://qiita.com/at-946/items/f495442a8a9bfdae5a6b

GitHubで編集を提案
株式会社リンクエッジ

Discussion