Railsアプリに隠しコマンド・隠しページを実装してみる

## 概要

Railsアプリを作っている際、特定のコマンドを知っている人だけが辿り着けるページみたいなものがあれば面白いなと思い調べてみたところ、JavaScriptを上手く使えば実装できる事がわかったので試してみました。

実運用できるレベルかどうかは別として完全に遊び心みたいな感じです。（URLを直打ちすれば普通に飛べちゃうみたいな野暮なツッコミは無しでお願いします（笑））

## 完成イメージ

![マイ-ムービー(7).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/3444aa18-c61b-b48c-0a1e-54adb58f1077.gif)

今回はかの有名な [コナミコマンド](https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%8A%E3%83%9F%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89) （上上下下右左右左BA）を入力した場合、隠しページにつながる導線をアラートメッセージで表示するような仕組みにしてみました。

## 仕様

- Ruby3
- Rails6
- MySQL8
- Docker

再現性を考慮しDockerで環境構築を行っていきます。

## 実装

前置きはそこそこに実装に入ります。

### ディレクトリ&各種ファイルを作成

```
$ mkdir rails-secret-command && cd rails-secret-command
$ touch Dockerfile
$ touch docker-compose.yml
$ touch entrypoint.sh
$ touch Gemfile
$ touch Gemfile.lock
```

```dockerfile:./Dockerfile
FROM ruby:3.0

RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list

RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs yarn

ENV APP_PATH /myapp

RUN mkdir $APP_PATH
WORKDIR $APP_PATH

COPY Gemfile $APP_PATH/Gemfile
COPY Gemfile.lock $APP_PATH/Gemfile.lock
RUN bundle install

COPY . $APP_PATH

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]
```

```yml:./docker-compose.yml
version: "3"
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql-data:/var/lib/mysql
      - /tmp/dockerdir:/etc/mysql/conf.d/
    ports:
      - 3306:3306
  web:
    build:
      context: .
      dockerfile: Dockerfile
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
      - ./vendor/bundle:/myapp/vendor/bundle
    environment:
      TZ: Asia/Tokyo
      RAILS_ENV: development
    ports:
      - 3000:3000
    depends_on:
      - db
volumes:
  mysql-data:
```

```sh:./entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
```

```rb:./Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "rails", "~> 6"
```

```./Gemfile.lock
# 空欄でOK
```

### rails new

おなじみのコマンドでプロジェクトを作成します。

```
$ docker-compose run web rails new . --force --no-deps -d mysql
```

Gemfileが更新されたので再ビルド。

```
$ docker-compose build
```

### 「.config/database.yml」を編集

```yml:./config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password # デフォルトだと空欄になっているはずなので変更
  host: db # デフォルトだとlocalhostになっているはずなので変更

development:
  <<: *default
  database: myapp_development

test:
  <<: *default
  database: myapp_test

production:
  <<: *default
  database: <%= ENV["DATABASE_NAME"] %>
  username: <%= ENV["DATABASE_USERNAME"] %>
  password: <%= ENV["DATABASE_PASSWORD"] %>
  host: <%= ENV["DATABASE_HOST"] %>
```

### データベースを作成

```
$ docker-compose run web rails db:create
```

### 動作確認

```
$ docker-compose up -d
```

![スクリーンショット 2021-05-04 4.44.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/25a27e98-797b-c3e3-85e4-1642daea2453.png)

[localhost:3000](http://localhost:3000) にアクセスしていつもの画面が表示されればOK。

### トップページを作成

```
$ docker-compose run web rails g controller home index
```

#### コントローラー

```rb:./app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
  end
end
```

#### ビュー

```erb:./app/views/home/index.html.erb
<%= javascript_pack_tag "home/index" %>

<h1>次のコマンドを入力してください</h1>
<p>上上下下右左右左BA</p>
```

#### ルーティング

```rb:./config/routes.rb
Rails.application.routes.draw do
  root "home#index" # 追記
end
```

### 隠しページを作成

```
$ docker-compose run web rails g controller secret_pages index
```

#### コントローラー

```rb:./app/controllers/secret_pages_controller.rb
class SecretPagesController < ApplicationController
  def index
  end
end
```

#### ビュー

```erb:./app/views/secret_pages/index.html.erb
<h1>ここは隠しページです</h1>
```

#### ルーティング

```rb:./config/routes.rb
Rails.application.routes.draw do
  root "home#index"
  resources :secret_pages, only: %i[index] # 追記
end
```




### 隠しコマンドを実装

Rails6でJavaScriptを使用する場合、「app/javascript/packs/」以下にjsファイルを配置する必要があるのでちゃちゃっと作成します。

```
$ mkdir app/javascript/packs/home
$ touch app/javascript/packs/home/index.js
```

```js:./app/javascript/packs/home/index.js
var secretCommands = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65] // 隠しコマンド
var inputtedCommands = [] // 入力されたコマンド

document.onkeydown = function(e) {
  inputtedCommands.push(e.keyCode)
  
  // 入力されたコマンドの順番と値が隠しコマンドと全一致した場合はアラートメッセージを表示
  if (inputtedCommands[inputtedCommands.length - 1] === secretCommands[inputtedCommands.length - 1]) {
    if (inputtedCommands.length === secretCommands.length) {
      if (window.confirm("隠しページへ飛びますか？")) {
        location.href = "http://localhost:3000/secret_pages" // 隠しページへ飛ぶ
      } else {
        window.alert("キャンセルしました") // キャンセル時の処理
      }
      inputtedCommands.length = 0
    }
  } else {
    inputtedCommands.length = 0 // 少しでも違った場合はリセット
  }

  console.log(inputtedCommands)
}
```

JavaScriptには

- keydown（キーが押された瞬間）
- keypress（キーが押されて文字が入力された瞬間）
- keyup（キーを放した瞬間）

といったキーボードの操作を検知して発火するイベントがありますが、今回はキーが押された瞬間を検知したいので「keydown」を採用しました。

また、キーボードにはそれぞれのキーに「キーコード」という数字が割り当てられています。

参照: [キーコード一覧](http://faq.creasus.net/04/0131/CharCode.html)

そのルールに従うと「上上下下右左右左BA」は「38, 38, 40, 40, 37, 39, 37, 39, 66, 65」に変換できるため、コンピューターに対してはコイツが隠しコマンドであると認識させます。

## 動作確認

![マイ-ムービー(7).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/3444aa18-c61b-b48c-0a1e-54adb58f1077.gif)

## あとがき

以上、Railsアプリに隠しコマンド・隠しページを実装してみました。

実用性という点ではちょっと不明ですが、こういう面白い仕掛けを組み込む事もできるという内容です。

今回作成したコードの完成品を記載しておくので、もし記事通りに進めて動かない部分などはそちらと照らし合わせてみてください。

https://github.com/kazama1209/rails-secret-command