約400企画が出店する学園祭を支えるWebサービスを開発した話

筑波大学の学園祭「[雙峰祭](https://www.sohosai.tsukuba.ac.jp/)」のためのRailsアプリケーション「[雙峰祭オンラインシステム](https://online.sohosai.com/)」を学園祭実行委員として開発しました。このアプリケーションや利用した技術を紹介します。

## 自己紹介
筑波大学 [情報メディア創成学類](http://www.mast.tsukuba.ac.jp/) でプログラミングなどを学んでいる大学2年生です。
筑波大学学園祭実行委員会 情報システム局の平成30年度の局長をしていました。
雙峰祭はその年の2年生の学園祭実行委員が中心となって運営をします。今年、2年生になった私も情報システム局の一員として、このアプリケーションの開発をすることになりました。

## 雙峰祭オンラインシステムとは
[雙峰祭オンラインシステム](https://online.sohosai.com/) とは雙峰祭に企画を出店する団体と学園祭実行委員会の間の申請や調査のためのアプリケーションです。

筑波大学の学園祭実行委員会では電子化が進んでおり、2004年頃からオンラインシステムによる企画の受付をするアイデアが生まれていました。例年、情報システム局ではそのためのアプリケーションを開発しています。

![Screenshot](https://qiita-image-store.s3.amazonaws.com/0/245363/31712c38-6ee9-d320-8032-6ee4039ede7d.png)
雙峰祭オンラインシステムのトップページ

### 開発について
開発は2人で行いました。バックエンドが私でフロントエンドがもう一人、という分担で始めましたが、フロントエンドを完全にRailsから分離することができなかったため、フロントエンドにも私が関わりました。

### 機能
雙峰祭オンラインシステムは一言で言えばユーザ管理のできるアンケートシステムです。
学園祭でどのようの企画を出店するか、使用する機材や教室などの申請をオンラインで行えます。
それ以外に特筆すべき機能はありませんが、強いて言うならGraphQLのAPI機能があります(後述)。

#### ユーザー登録後にできること
![Screenshot](https://qiita-image-store.s3.amazonaws.com/0/245363/def03d92-c1cc-321f-6481-9d2b022aa640.png)

初回ログインの直後には以下のことができるようになります。

- 企画登録
- 副責任者登録コードの発行
- アカウント設定

企画登録を行うと以下のことができるようになります。

- 各種申請
- 過去の申請内容の確認
- 副責任者の登録

これらの機能について順を追って説明します。

##### 企画登録
![Screenshot](https://qiita-image-store.s3.amazonaws.com/0/245363/decff67c-80f9-d9dd-351a-a65db0a4e708.png)
雙峰祭に出店するための企画登録することができます。

##### 副責任者登録コードの発行・副責任者の登録
![Screenshot](https://qiita-image-store.s3.amazonaws.com/0/245363/99c6b488-e06f-dbaa-8cd5-91fc80e42b21.png)

雙峰祭には企画の責任者とは別に、副責任者を一人立てるルールがあります。
その登録のためのランダムな4桁の数字を発行します。
この数字を副責任者になる人は企画登録後の責任者に伝え、責任者のアカウントからそれを雙峰祭オンラインシステムに登録することで副責任者登録が完了します。

副責任者登録後は責任者と同様に副責任者も企画のページを表示をすることができ、各種申請を行うことができます。

##### 各種申請
受付期間中の申請が一覧表示され、回答することができます。また、受付期間中であれば回答の修正もすることができます。

##### 過去の申請内容の確認
受付期間が終了した申請の内容を確認することができます。


## 利用技術

### インフラ

- Heroku
- Heroku Postgres Hobby Basic

インフラは当初はオンプレミスのサーバーの予定でしたが、紆余曲折ありHerokuになりました。サーバー管理の心配が減り、アプリケーションの開発に集中することができました。独自ドメインを使用するためにHobby dynoを、Heroku Postgressはレコード数が多くなることが予想されたのでHobby Basicを使用しました。

### バックエンド

- Rails
    - banken
    - devise
    - gon
    - graphql
        - graphql-cache
    - rails_admin
    - webpacker


あまり使用していないgemもありますが、Gemfileはこんな感じです。

```ruby:Gemfile
# frozen_string_literal: true

source 'https://rubygems.org'

git_source(:github) do |repo_name|
  repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?('/')
  "https://github.com/#{repo_name}.git"
end

ruby '2.5.1'

gem 'rails'

gem 'acts-as-taggable-on'
gem 'banken'
gem 'bootsnap', require: false
gem 'cancancan'
gem 'cocoon'
gem 'coffee-rails'
gem 'devise'
gem 'devise-i18n'
gem 'dotenv-rails'
gem 'enumerize'
gem 'gon'
gem 'graphql'
gem 'graphql-cache'
gem 'jquery-rails'
gem 'kaminari'
gem 'nested_form_fields'
gem 'pg'
gem 'puma'
gem 'rack-cors'
gem 'rails-i18n'
gem 'rails_admin', '1.4.0'
gem 'rails_admin-i18n'
gem 'rollbar'
gem 'sass-rails'
gem 'seed-fu', '~> 2.3'
gem 'slim-rails'
gem 'uglifier'
gem 'webpacker'

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: %i[mri mingw x64_mingw]
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara'
  gem 'database_cleaner'
  gem 'factory_bot_rails'
  gem 'faker'
  gem 'gimei'
  gem 'nyan-cat-formatter'
  gem 'rspec-rails'
  gem 'rspec_junit_formatter'
  gem 'rubocop-junit-formatter'
  gem 'rubocop-rspec'
  gem 'selenium-webdriver'
  gem 'timecop'

  gem 'codecov', require: false
  gem 'simplecov', require: false
end

group :development do
  # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
  gem 'better_errors'
  gem 'binding_of_caller'
  gem 'brakeman', require: false
  gem 'bullet'
  gem 'bundler-audit', require: false
  gem 'graphiql-rails'
  gem 'letter_opener_web'
  gem 'listen', '>= 3.0.5', '< 3.2'
  gem 'overcommit'
  gem 'rails_best_practices', require: false
  gem 'rubocop', require: false
  gem 'scss_lint', require: false
  gem 'slim_lint', require: false
  gem 'spring'
  gem 'spring-commands-rspec'
  gem 'spring-watcher-listen', '~> 2.0.0'
  gem 'web-console', '>= 3.3.0'

  gem 'pry-byebug'
  gem 'pry-coolline'
  gem 'pry-rails'
end
```

バックエンドは経験があったRailsにしました。ユーザ管理はDevise、管理画面はrails_adminを使用することで他の機能の開発に集中することができました。

### フロントエンド

- [Buefy](https://buefy.github.io/)
- [Vue.js](https://jp.vuejs.org/index.html) (一部)

Bulma(Buefy)を使用し、独自のスタイルはほとんどありません。Vue.jsを一部で使用しましたが、Railsとの連携や、開発の分業の面で私たちのアプリケーションには導入すべきではありませんでした。

### その他

- Git/GitHub
- Slack
- Kibela
- CircleCI
- Overcommit
    - EsLint
    - RuboCop
    - SlimLint
    - RailsBestPractices
    - RSpec

#### Git/GitHub

コードの管理にはGitやGitHubを利用しました。ブランチの管理はgit-flowのような運用をしました。Herokuと連携して `master` ブランチを自動でデプロイしたり、Slackに通知を送ったりするなど、それなりに活用できたと思います。

#### Kibela

情報共有にはKibelaを利用しました。

[技術コミュニティ・教育機関での利用を無償化します - Kibela Blog](https://blog.kibe.la/entry/2017/11/21/183214)

この取り組みによって無償で利用することができました。
ただ、あまり活用はできなかったと感じています……

#### CircleCI

CIにはCircleCIを利用しました。
Pull Requestごとにテストやスタイルチェックをして、テストが完了した `master` ブランチをHerokuにデプロイするフローだったのですが、徐々に形骸化してしまいました。

#### Overcommit

コミットやプッシュ前に様々なテストツールを実行するために `Overcommit` を利用しました。
Rspecも当初はテストを書いていましたが、徐々に書かなくなってしまいました。

## 頑張ったこと

### GraphQL APIの実装
[企画検索システム](https://www.sohosai.tsukuba.ac.jp/search/) のためのAPIをGraphQLで実装しました[^1]。

[^1]: 2018年12月18日時点では企画検索のページは動作しますが、今後の公式サイトのリニューアルや雙峰祭オンラインシステムの終了によって停止している可能性があります。

雙峰祭オンラインシステムと企画検索システムを分割し、企画検索システムの開発者が自由に開発できるよう、GraphQLでAPIを実装することを決めました。しかし、実際の企画検索システムでは開発日数などの制約によって、GraphQLの良さを活かすことはできませんでした。具体的には、企画検索システムのページの初回アクセス時に全てのデータを読み込み、それ以降APIを使用せず、クライアント側で検索をしています。

初回アクセス時に全てのデータの取得がされるため、レスポンスを返すまでに時間がかかっていました。そこで、[graphql-cache](https://github.com/stackshareio/graphql-cache) gemを利用してキャッシュを行いレスポンスを改善しました。

### Buefyへのちょっとした貢献
雙峰祭オンラインシステムで利用しているOSSの[Buefy](https://github.com/buefy/buefy) へ小さなプルリクエストを3つ送り、全てマージされました。

https://github.com/buefy/buefy/pull/683
https://github.com/buefy/buefy/pull/642
https://github.com/buefy/buefy/pull/624

どれも1行変更するだけですが、これまでOSSへの貢献をしていなかった私にとっては大きな頑張りの一つでした。
これからも自分のアプリケーションを開発している中でライブラリのバグを見つけたときにはPull requestやIssueを送りたいと思います。

## 苦労したこと

### ユーザー登録の間違いへの対応

雙峰祭オンラインシステムでは筑波大学の関係者のみにユーザー登録を制限するため、メールアドレスのドメインが `*.tsukuba.ac.jp` のアドレスを持つユーザーのみが登録できるようにしていました。

この制限のために以下のようにdeviseを設定しました。

```ruby:app/config/initializers/devise.rb
# frozen_string_literal: true

Devise.setup do |config|
  config.email_regexp = /\A[^@\s]+@[^@\s]*tsukuba\.ac\.jp\z/
end
```

これにより、メールアドレスによる登録の制限を実現しました。しかし、`User` モデルに以下のようなバリデーションを設定してしまったことにより、間違ったメールアドレスを登録すると再登録ができなくなってしまいました。

```ruby:app/models/user.rb
# frozen_string_literal: true

class User < ApplicationRecord
  devise :confirmable, :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  validates :student_number, presence: true, length: { in: 8..9 }, uniqueness: true
end
```

`uniqueness: true` としたことにより、間違ったメールアドレスで正しい学籍番号を登録すると、メールアドレスの間違いに気づき、正しいメールアドレスで登録しようとしても、学籍番号のユニーク制約によってエラーになり登録できません。

この問題のため、個別のメールによる対応をすることになってしまいました。直接の原因は学籍番号のユニーク制約ですが、これを付けないと重複した学籍番号を認めることになってしまいます。

このような場合、どのようにするのが良いか教えていただきたいです。

### フォームの作成・削除
<img width="783" alt="Screenshot" src="https://qiita-image-store.s3.amazonaws.com/0/245363/c2bb46ae-1793-bc36-a34f-3446fbaf0d19.png">

雙峰祭オンラインシステムのメイン機能はアンケートです。そのため、項目の異なる似たようなフォームを何個も作成することになります。
このフォームの作成を素直に実装すると以下のような作業になります。

1. rails generate model
- 企画モデルに `has+one`、作成したモデルに`belongs_to`を追記
- `config/locales/ja.yml`に項目名を追加
- rails generate scaffold_controller
- controlerやviewを修正
- `config/routes.rb`に追記
- bankenのloyaltyを作成
- フォームへのリンクを追加

開発当初はこのフォームの追加をあまり重要に考えていませんでした。そのため、このような作業でのフォームを追加することに問題を感じていませんでした。実際に運用してみると、多くのフォームを迅速に追加しなければなりませんでした。それを私一人でするのは大きな負担であり、退屈な作業でした。また、同じようなコードが多数存在することになり、RailsのDRYの理念に大きく反するコードになりました。

フォームの削除も同様にこの作業の逆の手順で行っていました。感想は追加するときと同じです。

これもどのように実現するのが良いのかよくわかっていないので教えていただきたいです。

## 約400企画を管理する苦労
企画の多さ自体は開発に問題だと感じたことは正直ありません。なぜなら、それぞれの企画を管理し、責任者や副責任者と直接やりとりしているのは他の局の学園祭実行委員だからです。雙峰祭オンラインシステムは前述の通り、情報のやりとりする仕組みを作ることが仕事で、個々のやりとりには関与していなかったためです。

## まとめ

**今年なにしていたの?** という質問に答えるならば、「雙峰祭オンラインシステムに関わっていました」と言い切れるぐらいに時間を使っていたのが雙峰祭オンラインシステムです。

大きな組織[^2]の情報システム局でそれなりに影響のあるプログラミングができたのは、良い経験でした。自分のプログラミングスキルを活かすのはもちろんですが、自分に足りないことを知ったり、経験不足を思い知らされたりした1年でした。
[^2]: 筑波大学学園祭実行委員会は総勢約300人です。

### これから
Railsや設計について困っても聞ける相手が身近にいない状況でした。そのため、実際にRailsを運用されている企業でのインターンやアルバイトに興味があります。