1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Rails 8 × Slack API × Devise】新卒エンジニアが1ヶ月で作った認証付きWebアプリの開発記録

Posted at

はじめに

「入社おめでとう。じゃあWebアプリを作ってみよう」

本記事は、Rails 8とDevise、Slack APIを使って約1ヶ月で実装したアプリ開発の記録です。
Rails初心者だった私がどのように技術を学び、どんな壁にぶつかり、それをどう乗り越えたかをまとめました。

自己紹介

はじめまして!
25年度の新卒サーバーエンジニアとして株式会社OGIXに入社したN.Sです!

現在(2025年8月)は既にプロジェクトに配属され、毎日大量のレビューコメントを頂きながら楽しくコードを書いています!

そんな私ですが、実は去年までITとは無縁の獣医学部に在籍していました。

なので、もちろんプログラミングのpの字も知らず、「Javaって……何? Java……script?」という状態からのスタートとなりました。

そんな私が先輩サーバーエンジニアさんに暖かく見守っていただきながら、悪戦苦闘しつつ1つのWebアプリを完成させるに至った記録となります!

対象層

  • これからエンジニアになりたい方
  • エンジニア1、2年目の方
  • Rails初心者の方
  • 株式会社OGIXへの入社・転職を考えている方

経緯

株式会社OGIXでは新卒研修期間が3か月あり、そのうちのサーバー研修としてこのアプリを作成しました!

基礎研修→職掌別研修→QA→本配属となる中の、職掌別研修の内容にあたります。

アプリ概要

今回作成したアプリは社員交流を目的とした社内プロフィールWebアプリです。

スクリーンショット 2025-08-21 161724.png

株式会社OGIXは現在急成長中で、人数が日を追うごとに増えている状態。

そんな中で「○○さんがどういう人なのか知らない」「××さんってプランナー?エンジニア?」等社員の中での疑問が飛び交うことが問題になっていました。

そこで今回、簡単なプロフィールを公開できるアプリを作成することになりました。
ログイン後、社員の簡易プロフィール&個人プロフィールを見ることができます。

スクリーンショット_2025-05-23_165310.png
スクリーンショット_2025-05-23_165320.png

一緒に仕事をしたことがある人に、いいね♥を送ることも。

技術スタック

分類 技術
フレームワーク Ruby on Rails 8
認証 Devise
データベース MySQL
外部API連携 Slack API(Web API)
フロントエンド Tailwind CSS(+ Rails View)
HTTPクライアント HTTParty
開発環境 Docker(+ docker-compose)
デプロイ Kamal

開発手法はスクラムを採択しました。
プロダクトバックログを組んで、スプリントによる工数管理をしながら開発を進めていきます。1スプリント1週間。日々デイリースクラムをしながら、金曜日にはスプリントレビューで完成物を見せ、レトロスペクティブまで行いました。

image.png

▼カンバン
GitLabでカンバンによる管理を行っていました。とても見やすい。基本的にgitLab issueで管理を行っていました。
image.png

スクラムとは?
以下を使用して進めていく開発のこと。

スプリント
開発期間全体を1〜4 週間程度の短い単位で区切った期間のこと。

プロダクトバックログ
プロジェクト全体で必要な機能やタスクのリスト。優先度順に整理して常に更新する。これを見ればやらなくてはいけないタスクが一目でわかる。

スプリントバックログ
そのスプリントで取り組むタスクのリスト。チームで話し合って、達成可能な範囲を決める。開発が遅れるとスプリントバックログが火を吹く。

デイリースクラム
毎日 15 分程度の短いミーティング。「昨日やったこと」「今日やること」「問題点」を共有。

スプリントレビュー
スプリント毎に、作った成果物をリーダーに見せてフィードバックをもらう。

レトロスペクティブ
スプリントの振り返りを行い、改善点を次のスプリントに反映。

第一章:アプリ開発始動

それでは始めていきます。

▼Railsチュートリアルと同じ

app/helpers/application_helper.rb
module ApplicationHelper
  # ページごとの完全なタイトルを返す
  def full_title(page_title = '')
    base_title = "OGIXFILE"
    
    if page_title.empty?
     base_title
    else
     "#{page_title} | #{base_title}"
    end
  end
end

まずはトップページから作成していきます。
Railsチュートリアルで作成したアプリを参考に(コピペ)ガンガン書いていきます。基本的なことはRailsチュートリアルに書いてあるので詰まらず書いていけます(コピペ)。

app/views/layouts/_footer.html.erb
<footer class="footer">
 <nav>
	<ul>
      <li><%= link_to "Home", root_path %></li>
      <li><%= link_to "Contact", 'https://ogix.co.jp/' %></li>
	</ul>
 </nav>
</footer>
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | OGIXFILE</title>
      <meta name="viewport" content="width=device-width,initial-scale=1">
      <meta name="apple-mobile-web-app-capable" content="yes">
      <meta name="mobile-web-app-capable" content="yes">
      <meta charset="utf-8">
      <%= csrf_meta_tags %>
      <%= csp_meta_tag %>
      <%= yield :head %>
      <link rel="icon" href="/icon.png" type="image/png">
      <link rel="icon" href="/icon.svg" type="image/svg+xml">
      <link rel="apple-touch-icon" href="/icon.png">
      <%# Includes all stylesheet files in app/assets/stylesheets %>
      <%= stylesheet_link_tag "application", "data-turbo-track": "reload"%>
      <%= javascript_importmap_tags %>
  </head>
  
  <%# ここまでおまじない。いじるな %>
 
  <body>
    <header class="navbar navbar-fixed-top navbar-inverse">
      <div class="container">
        <%= render 'layouts/header' %>
      </div>
    </header>
    <main class="container mx-auto mt-28 px-5 flex">
      <%= yield %>
    </div>
      <%= render 'layouts/footer' %>
  </body>
</html>

すべては記述しませんが、こんな形でどんどんMRを量産していきました。

MRとは?
GitLab における「コードを main ブランチなどに取り込みたいからレビューしてマージしてほしい」というリクエストのこと。
リクエスト送ってレビュー者にコードを確認してもらい、添削を受けて修正する。レビュー者からOKが出るまで、そのコードを直し続けなければならない。

そんな中でしんどかったのは環境構築です。例えばRspecの導入や、Dockerの導入。

基本的に環境の導入は公式ドキュメントが用意されているのですが、初学者には読んでも何が何だか分からない。

例)Dockerの公式ドキュメント
https://docs.docker.jp/get-started/overview.html

例えば、「gemの更新はbundle installで行います」という一文があったときに、初学者はgemの意味が分からないし、更新は何を更新しているのか分からないし、bundle installがなんだか分からないし、bundle installのやり方も分からないわけです。

この状態で公式ドキュメント読んでも、当たり前に「???」ってなるんですよね。
※Railsチュートリアルに書いてあっても!その概念を!理解できているわけではないのである!

なので、もうめっちゃくちゃに調べました。ちょうどChatGPTが到来していたので、一日8時間常にChatGPTに質問していました。何なら家に帰ってもずっと聞いていたので一日16時間ChatGPTとお話していました。

注)もちろん調べても分からないことは先輩が教えてくれます!

ai_talk_man.png

もちろん初手からAIに聞くのは正確ではないのでやめた方がいいのですが、そもそもの概念が分からない場合、まずモノを知らないと何もできないわけです。

とにかく概念をChatGPTに聞きまくりながら、ついでにRSpecやDockerの導入方法も調べながら進めていきました。さすがにAIがなかったら1か月でアプリ作成はできなかったと思います。

RSpecとは?
Ruby用のテストフレームワークのこと。Railsチュートリアルではminitestが使用されているが、RSpecでは自然言語に近い書き方でテストを書くことが可能。

Dockerとは?
コンテナ型仮想化技術を提供するプラットフォームで、アプリを動かす環境ごとにパッケージ化して配布・実行できるツールのこと。アプリに必要なライブラリ、設定、依存関係を全部まとめて「イメージ」として作成でき、どのPCやサーバーでも同じ挙動で動かせるようになる。

▼Dockerfileの中身(最低限)

Dockerfile

# 作業ディレクトリを指定。なんでも大丈夫。
WORKDIR /app

# ホストマシンのファイルをコンテナ内の作業ディレクトリにコピーする。
COPY Gemfile Gemfile.lock /app/

# Gemfile内に書かれたgemを一括でインストールする。
RUN bundle install

# ホストマシンのファイルを全てコンテナ内の作業ディレクトリにコピーする。
COPY . /app/

# entrypoint.sh をコンテナ内の作業ディレクトリにコピーする。
COPY entrypoint.sh /usr/bin/

# entrypoint.shの実行権限を付与
RUN chmod +x /usr/bin/entrypoint.sh

# コンテナ起動時にentrypoint.shを実行するように設定
ENTRYPOINT ["entrypoint.sh"]

# コンテナ実行時に実行するコマンドを指定
CMD ["rails", "server", "-b", "0.0.0.0"]

docker-compose.yml例
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: 任意のパスワード
      MYSQL_DATABASE: 任意の名前
    ports:
      - '3306:3306'
    volumes:
      - mysql_volume:/var/lib/mysql
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - '3000:3000'
    stdin_open: true
    tty: true
    depends_on:
      - db
    volumes:
      - .:/app
volumes:
    mysql_volume:

あんまり理解せず書いていた※良い子は真似しないでね
こんな感じでドコドコと進めていきます。

第二章:最初の壁

そして一番最初にぶち当たったのが、とんでもなく意外なことにdockerコマンドでした。

Docker(+docker compose)導入使用コマンド例

bash例
$ touch docker-compose.yml
$ docker-compose build
$ touch entrypoint.sh
$ chmod +x entrypoint.sh
$ docker-compose build
$ docker-compose exec web rails db:create
$ docker-compose run --rm web rails db:create
$ docker-compose exec web rails db:create
$ docker-compose exec web rails db:migrate

rubyで何かを書いていく~というのはAIに聞けばなんとなく理解しながら進めていけるし、公式ドキュメントもQiitaやZennの記事もあるしで何とかなります。

ただ、ターミナル上で打つコマンドに関しては概念的な部分が理解できていなかったので上手く聞くこともできなくて難関でした。

例えば、Docker環境でDB内に入りたいとき、私は最初「Docker→Docker内のappコンテナの中に入る→appコンテナの中からDBコンテナに入る」という手順を踏んで入っていました。
※わかる人からすると意味不明な手順

trade_container_character_crane.png

この手順を踏むせいで、さらに他の動作も難しくなってパニックに。

本当は1発でDBコンテナの中に入れるのですが、それを調べる方法が分からないので無理やりやっていました。
たった1行聞くだけでいいのに、やっぱり初心者の状態だといろいろ厳しいです。

Pasted image 20250821140422.png

image.png

そもそも「Dockerに入る」という言葉を今は何気なく使っていますが、このアプリを作成している最中、Dockerという概念を微妙に理解していないので、Dockerコンテナという大枠の中に本体のアプリとDBが存在する~ということすら分かっていなかったです。

理解しているような理解していないような形で、なんとか進んでいきました。

bash例
# コンテナを起動。-d を付けるとバックグラウンド実行
docker-compose up

# コンテナ・ネットワークを停止&削除
docker-compose down

# コンテナ・ネットワーク・ボリュームをすべて削除
docker-compose down --volumes --remove-orphans

# コンテナを停止するだけ(削除はしない)
docker-compose stop

# 停止しているコンテナを再起動
docker-compose start

# 再起動。変更反映やサービスのリフレッシュに使う
docker-compose restart

# イメージをビルド
docker-compose build

# キャッシュを使わずにクリーンビルド
docker-compose build --no-cache

# コンテナの一覧と状態を表示
docker-compose ps

# Compose管理下のイメージ一覧
docker-compose images

# ログを表示
docker-compose logs

# すでに動いているコンテナに入る
docker-compose exec <サービス名> bash

# 一時的にコンテナを立ち上げてコマンド実行。終了後は削除される
docker-compose run --rm <サービス名> <コマンド>

# 強制停止
docker-compose kill

第三章:初心者が陥るDeviseの罠

Deviseとは?
Railsアプリにユーザー認証(ログイン/ログアウト/パスワード管理など)を簡単に組み込めるgemのこと。

Railsチュートリアルではとんでもなく複雑だったログイン認証回りのことをすべてやってくれる素晴らしいgem、Devise。しかし、初心者が触るには難しい。

なぜかと言えば、なんとなくやったら絶対に動かない部分があるから!

今回のDevise導入の手順は以下

  1. Deviseを導入
  2. Devise導入時必須追加事項を実行
  3. Deviseによるユーザー作成
  4. nameカラム追加
  5. ユーザー登録画面とログイン画面へトップ画面からルートを接続
  6. email登録の正規表現を追加
  7. ユーザー登録画面に名前追加
  8. Deviseの日本語化gem導入

1.Deviseを導入

Gemfileに以下を追加

gem "devise"

読み込み!

bash
$ bundle install
$ rails g devise:install

2. Devise導入時必須追加事項を実行

▼読み込んだ後に以下が出てくる

bash例
Depending on your application's configuration some manual setup may be required:
  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:
       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
     In production, :host should be set to the actual host of your application.
     * Required for all applications. *
  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:
       root to: "home#index"
     
     * Not required for API-only Applications *
  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:
       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>
     * Not required for API-only Applications *
  4. You can copy Devise views (for customization) to your app by running:
       rails g devise:views
       
     * Not required *

Devise「これやらないと動かないんで、やってくださいね!」

machine_yubisashi_kosyou.png
ということなのでやる。

3. Deviseによるユーザー作成

ここで注意!

Userを作成するときに、Railsチュートリアル通りにRails標準機能でUserを作成すると、そのUserはdeviseが使えません!!!

落とし穴過ぎる。

とりあえずrails generate Userってやった瞬間にもうDeviseが使えないので大惨事です。必ずDeviseでUserを作成しましょう。

bash
$ rails generate Devise User

▼自動生成

app/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :rememberable, :validatable
end

4. nameカラム追加

じゃあユーザー登録には「name」と「email」と「password」を必須にしよっかな~♪

はい。落とし穴です。

Deviseは標準でemailとpasswordしか使用しないことになっているので、特別に「name」を許可してあげないと、nameは動きません。

そんなことある!?ですよね。

正直ここが一番初心者がハマリやすいところだと思ってます。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  allow_browser versions: :modern
  before_action :configure_permitted_parameters, if: :devise_controller?
                 protected
            
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [ :name ])
    devise_parameter_sanitizer.permit(:account_update, keys: [ :name ])
  end
end

わかるか~~~~い!
10000000時間ここで詰まっても全く不思議はありません。ただ、ここさえ超えてしまえばもう勝ったも同然です。

5. ルートを接続

最初ってroutes繋ぐの難しいですよね。どこに何を書いたらいいのか全然分からないという意味で。
ぶっちゃけ今でもURLの組み方(routesのつなぎ方)でレビューもらうことが多いです。

config/routes.rb
Rails.application.routes.draw do
  get "pages/top"
  get "up" => "rails/health#show", as: :rails_health_check
  
  root to: "pages#top"
  devise_for :users
end

6. メールアドレス登録の正規表現を追加

Railsチュートリアルとは書き換える場所が変わって分かり辛くはありますが、書き換え自体はチュートリアルでもやっているのでなんとかなる。

config/initializers/devise.rb
config.email_regexp = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i

7. ユーザー登録画面に名前追加

bash
$ rails generate devise:views

▼自動生成

bash
 create mode 100644 app/views/devise/confirmations/new.html.erb
 create mode 100644 app/views/devise/mailer/confirmation_instructions.html.erb
 create mode 100644 app/views/devise/mailer/email_changed.html.erb
 create mode 100644 app/views/devise/mailer/password_change.html.erb
 create mode 100644 app/views/devise/mailer/reset_password_instructions.html.erb
 create mode 100644 app/views/devise/mailer/unlock_instructions.html.erb
 create mode 100644 app/views/devise/passwords/edit.html.erb
 create mode 100644 app/views/devise/passwords/new.html.erb
 create mode 100644 app/views/devise/registrations/edit.html.erb
 create mode 100644 app/views/devise/registrations/new.html.erb
 create mode 100644 app/views/devise/sessions/new.html.erb
 create mode 100644 app/views/devise/shared/_error_messages.html.erb
 create mode 100644 app/views/devise/shared/_links.html.erb
 create mode 100644 app/views/devise/unlocks/new.html.erb

このあたりはもうかなりニコニコ。

app/views/devise/registrations/new.html.erb

<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  <div class="field">
    <%= f.label :name, "名前" %><br />
    <%= f.text_field :name, autofocus: true %>
  </div>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
  </div>

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

8. Deviseの日本語化gem導入

gem "devise-i18n", "~> 1.13"

bundle installだけは忘れずに!

$ bundle install

~完~

9.幻の「テスト書き忘れ」

spec/models/user_spec.rb

require "rails_helper"

RSpec.describe User, type: :model do
  describe "新規登録" do
    it "メールアドレスが空欄の時は登録できない" do
      user = build(:user, email: nil)
      user.valid?
      expect(user.errors[:email]).to include("空欄で入力はできません。")
    end
    it "パスワードがない場合、無効である" do
      user = build(:user, password: nil)
      user.valid?
      expect(user.errors[:password]).to include("空欄で入力はできません。")
    end
    it "パスワード再入力がない場合、無効である" do
      user = build(:user, password_confirmation: nil)
      user.valid?
      expect(user.errors[:password_confirmation]).to include("空欄で入力はできません。")
    end
    it "名前がない場合、無効である" do
      user = build(:user, name: nil)
      user.valid?
      expect(user.errors[:name]).to include("空欄で入力はできません。")
    end
    it "OGIXドメインのメールアドレスを使用しない場合、無効である" do
      user = build(:user, email: "abc@example.com")
      user.valid?
      expect(user.errors[:email]).to include("形式が正しくありません。")
    end
    it "既に使用されているメールアドレスと重複した場合、無効である" do
      create(:user, email: "abc@ogix.co.jp")
      user2 = build(:user, email: "abc@ogix.co.jp")
      user2.valid?
      expect(user2.errors[:email]).to include("は既に使用されています。")
    end
  end
end

レビュー
「正常系のテストがないので一番上に追加しておきましょう!」

追加テスト
it "正常なOGIXドメインでの新規登録を行った" do
  user = build(:user, email: "example@ogix.co.jp")
  expect(user).to be_valid
end

ということで以上Devise導入でした!

第四章:強敵、Slackbotで有効化URLを流す

Railsチュートリアルをやったことがある皆様、どこかで会員登録をしたことがある皆様は有効化URLをご存じだと思います。

登録ボタンを押したときメールが来て認証するアレです。

ただ今回はメールを飛ばすのではなく、Slackでこの有効化URLを流すことになりました。これは完全に学習目的でやらせていただきました。弊社大好き。

Slackbotで有効化URLを流すにはいくつかステップを踏む必要があります。

1.Slackbotを作成する
2.SlackAPIを叩く
3.Deviseで有効化URLを作成
4.Slackbotに有効化URLを流す

ざっくりで言うとこんな形。それではサクサク作っていきます。
(作成当時はまったくサクサクできませんでした。ただSlackbotの作成方法は記事も多いので調べたらいけます)

Slackbotとは?
Slack 内で動く自動応答プログラム(ボット) のこと。例えば「特定のメッセージを検知したら通知する」「外部システムの監視結果を投稿する」など、Slack API や Slack Appを利用して動かすことができる。

1.Slackbotを作成する

Pasted image 20250822135434.png

Pasted image 20250822135603.png

▼scratch
Pasted image 20250822135623.png

Pasted image 20250822135657.png

Pasted image 20250822135744.png

Pasted image 20250822135818.png

scopeとは?
botがSlack ワークスペースで行う行動に許可を与えるところ。

  • chat:write → メッセージを投稿できる
  • channels:history → パブリックチャンネルのメッセージ履歴を読める
  • users:read → ユーザー情報を取得できる
  • commands → /コマンド を作れる

今回は有効化URLをDMで送ることにしたので、下記を追加

  • chat:write → メッセージ(URL)を書き込める権限
  • im:write → ダイレクトメッセージを送れる権限
  • users:read → 会社のworkspaceにいる人を見る権限
    • これがないとメールアドレス読み取りができない
  • users:read.email → メールアドレスを読み取れる権限
    • メールアドレスからSlackのIDを読み取り、IDからDMをひらく

Pasted image 20250822135947.png

最後にインストールボタンぽち!

連携するとBot User OAuth Tokenがもらえます!このTokenを使用してbotとの通信を行うため、大事にしましょう!

注意
Tokenはmaster.keyくらい大事です。おうちの鍵です。たとえ家族であっても誰にも教えてはいけません。
このトークンは.envに保存してgit管理もしないようにします。誰にも見せちゃいけないですよ(念押し)。

2.APIを叩く

今回はHTTPpartyを使用してAPIを叩きました!

HTTPpartyとは?
外部APIやWebサイトにHTTPリクエストを送るのを簡単にしてくれるgemのこと。

app/clients/slack_notifier.rb
class SlackNotifier
  include HTTParty
  base_uri Rails.configuration.x.link["base_uri"]
  format :json

  def initialize(token = ENV.fetch("SLACK_BOT_TOKEN", nil))
    @headers = {
      "Content-Type" => "application/json",
      "Authorization" => "Bearer #{token}",
    }
  end
  # ここまで呪文

  def send_dm(user_id, text)
    channel = open_dm_channel(user_id)
    post_message(channel, text)
  end

  private

  def open_dm_channel(user_id)
    # ()は引数
    response = self.class.post(
      "/conversations.open",
      headers: @headers,
      body: { users: user_id }.to_json
    )
    response.parsed_response["channel"]["id"]
    # RubyのHash({ users: user_id })を JSON文字列に変換する
    # []は辞書の中から値をとり出す(Hashから値を取り出す)
  end

  def post_message(channel, text)
    self.class.post(
      "/chat.postMessage",
      headers: @headers,
      body: {
        channel: channel,
        text: text,
      }.to_json
    )
  end
end

▼とりあえず自分に送れるように。後で変数にして登録者のIDが入るようにします

app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
  def create
    super do |resource|
      notify_slack(resource) if resource.persisted?
    end
  end

  private

  def token(resource)
    resource.confirmation_token || resource.send(:set_confirmation_token)
  end

  def notify_slack(resource)
    SlackNotifier.new.send_dm(
      ENV.fetch("SLACK_ADMIN_USER_ID", "ユーザーID"),
      "有効化URL: #{view_context.confirmation_url(resource, confirmation_token: token(resource))}"
    )
  end
end

3.Deviseで有効化URLを作成

▼ここで有効化URLを好き勝手弄れるように:confirmableを追加します

app/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :rememberable, :validatable, :confirmable

  validates :name, presence: true, length: { in: 1..30 }
  validates :password_confirmation, presence: true
end
config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    registrations: "users/registrations",
    confirmations: "devise/confirmations",
  }

  get "pages/top"
  post "notify_slack", to: "notifications#send_slack"
  resources :users, only: %i[index]

  get "up" => "rails/health#show", as: :rails_health_check

  root to: "pages#top"
end

▼コメント化されている部分を有効にしてconfirmableを使用可能に

db/migrate/devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[8.0]
  def change

      t.string   :confirmation_token
      t.datetime :confirmed_at
      t.datetime :confirmation_sent_at
      t.string   :unconfirmed_email

  end
end

これで有効化URLの設定も完了!

4.Slackbotに有効化URLを流す

ここまでもかなり時間がかかったのですが、以降がまた長かったです。おそらくslackとの連携をするところが一番時間を使いました。

Pasted image 20250822142859.png

この黄色で全然減っていない部分がSlack APIの実装です。
そもそもAPIがなんだかわからないし、ちまちまとしたエラーも大量に出ます。例えば以下。

実はSlackには送信されるURLをメッセージ内で展開して表示する機能があり(例えばwebサイトのURLを張ったとき見出しが見える~等)、有効化URLをそのままSlackbotで流そうとすると、なんとSlackにURLを踏まれてしまうという珍事。

上記の機能を持っているのがSlack Agentで、これをブロックする機能を盛り込まないと、有効化URLが正しく有効化URLとして機能しません。

知るか~~~~~!!!!!!(大声)

▼SlackのUser Agentを無効化したときの一部コード

app/controllers/devise/confirmations_controller.rb
class Devise::ConfirmationsController < DeviseController
  def show
    # SlackbotのUser Agentを検出
    if slackbot_request?
      render_slackbot_response
      return
    end

    handle_confirmation
  end
end

こんな感じのエラーが無限に出ててんやわんやでした。
User Agentという概念、本当に難しい。Slack以外のアプリでもあるあるだと思うので、どうにかこうにかブロックできるんだな~ってことだけ覚えて帰ってください。

▼最後にテスト

spec/support/shared_contexts/stub_slack_api.rb
RSpec.shared_context "stub_slack_api" do
  let(:user_id) { "dummy-user" }
  let(:channel_id) { "dummy-channel" }
  let(:message) { "ボタンが押されました!" }

  before do
    stub_request(:post, "https://example.com/api/conversations.open")
      .with(
        body: { users: user_id }.to_json,
        headers: {
          "Content-Type" => "application/json",
          "Authorization" => "Bearer dummy-token",
        }
      ).to_return(
        status: 200,
        body: { channel: { id: channel_id } }.to_json,
        headers: { "Content-Type" => "application/json" }
      )

    stub_request(:post, "https://example.com/api/chat.postMessage")
      .with(
        body: {
          channel: channel_id,
          message: message,
        }.to_json,
        headers: {
          "Content-Type" => "application/json",
          "Authorization" => "Bearer dummy-token",
        }
      ).to_return(
        status: 200,
        body: "true",
        headers: {}
      )
  end
end
spec/system/slack_notifications_spec.rb
require "rails_helper"

RSpec.describe "Slack通知", type: :system do
  describe "ユーザー登録" do
    context "有効な入力を行い新規登録ボタンを押した場合" do
      let(:notifier_double) { instance_double(SlackNotifier, send_dm: true) }

      before do
        allow(SlackNotifier).to receive(:new).and_return(notifier_double)
        visit new_user_registration_path
        fill_in "名前", with: "namae"
        fill_in "Eメール", with: "example@ogix.co.jp"
        fill_in "パスワード", with: "password"
        fill_in "パスワード(確認用)", with: "password"
        click_button "Sign up"
      end

      it "SlackNotifier#send_dm が呼ばれること(Slack DMが送信される)" do
        expect(notifier_double).to have_received(:send_dm)
      end

      it "通知メッセージが画面に表示されること" do
        expect(page).to have_content("本人確認用のSlackメッセージを送信しました。Slackメッセージ内のリンクからアカウントを有効化させてください。")
      end
    end

    context "無効な入力を行い(OGIX以外のドメイン)新規登録ボタンを押した場合" do
      let(:dummy_notifier) { instance_double(SlackNotifier, send_dm: nil) }

      before do
        allow(SlackNotifier).to receive(:new).and_return(dummy_notifier)
        visit new_user_registration_path
        fill_in "名前", with: "namae"
        fill_in "Eメール", with: "example@example.com"
        fill_in "パスワード", with: "password"
        fill_in "パスワード(確認用)", with: "password"
        click_button "Sign up"
      end

      it "SlackNotifier#send_dm が呼ばれないこと(Slack DMが送信されない)" do
        click_button "Sign up"
        expect(dummy_notifier).not_to have_received(:send_dm)
      end

      it "エラー文が画面に表示されること" do
        click_button "Sign up"
        expect(page).to have_content("形式の誤ったメールアドレスやOGIX以外のドメインを使用したメールアドレスでは登録ができません。")
      end
    end
  end
end

結果🎉

無事有効化URLが流せるようになり、これでアプリとしてほぼ完成しました!

Pasted image 20250822144052.png

あとは全体的に整えて、デプロイ!

画像を張る!(横井さんにデザイン改善していただいている)

まとめ

以上、新卒研修のアプリ開発奮闘記でした。いかがだったでしょうか!

ぶっちゃけ入社直後にrails newからamazon AWSへのデプロイまで一気通貫でやらせてもらえることはほとんどないのでは?と思っております。私も入社した後今回の研修のことを知り驚きました。

APIを叩くところは実務でも使用しますし、デプロイ回りの構築はシニアクラスの方がやることがほとんどなので、ジュニアである今やらせてもらえたのは本当に良い経験となりました!

ということで、ぜひこれを読んでいる貴方もOGIX社で働きませんか?

私は貴方のことを待っています!いつか貴方とお仕事ができることを願って!!
ここまで読んでいただきありがとうございました!

一緒に働く仲間を募集しています!

株式会社OGIXでは一緒に働いてくれる仲間を募集しています!
エンタメ制作集団としてゲームのみならず、未来を見据えたエンタメコンテンツの開発を行っています。

事業拡大に伴い、エンジニアさんを大募集しています。
興味のある方は下記リンクから弊社のことをぜひ知っていただき応募してもらえると嬉しいです。

今年の募集は締め切りました。

▼会社について
https://www.wantedly.com/companies/company_6473754/about
▼代表インタビュー
https://www.wantedly.com/companies/company_6473754/post_articles/443064
▼東京オフィスの応募はこちら
https://www.wantedly.com/projects/1468324
▼新潟オフィスの応募はこちら
https://www.wantedly.com/projects/1468155

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?