1日1問解いてSQLをマスター！SQLの学習サービス Qdash を作った話

# 目次

- はじめに
- なぜ作ろうと思ったか
- 使ってる技術
- 同じようなサービスを作りたい人のために実装を紹介
    - 1日に1回、指定の時間に問題を切り替えて表示する
    - 質問箱風なOGP画像を生成
- これから
- 最後に

# はじめに

Qdash という1日1問形式の SQL 学習サービスを作りました。

https://q-dash.jp

### 問題画面

<img width="734" alt="スクリーンショット 2019-05-21 12.18.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38580/3e563b30-1a14-bf1f-b987-8f1a5a898d5b.png">

### 回答画面

<img width="740" alt="スクリーンショット 2019-05-21 12.18.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38580/b43544fa-9da5-c51b-1e1b-3410a35da041.png">


# なぜ作ろうと思ったか

とあるデータを見たいけど、いつも SQL を書ける人にお願いしている。
勉強はしたいが、中々勉強する時間が取れない。

と思ってるディレクター、営業、マーケターの方！！
でも1日の中でも隙間時間はありますよね？

その隙間時間に問題を解き、SQL は任せて！と自信を持って言えるようになりませんか？
というのがサービスのコンセプトです。

# 使っている技術

・ Ruby
・ Rails
・ ServiceWorker / Webpush
・ Heroku

Rails / Heroku で手早く作りました。
push 通知をするために ServiceWorker と Webpush にも挑戦しました。 (この記事では触れません)

# 実装紹介

似たようなサービスを作りたい人のために実装をいくつか紹介します！

### 1日に1回、指定の時間に問題を切り替えて表示する

SQL を学びたい人にとって負担にならないように1日1問という仕様にしました。
あなたならどのように実装しますか？

今回は Cookie と Heroku scheduler を使って実装してみました。


```ruby

class HomeController < ApplicationController
  def index
    @question = today_question
    delete_cookie_if_question_changed

    @answer = Answer.find_by(id: answer_id)
  end

  private

    # flesh set by scheduler.rake
    def today_question
      today_question = TodayQuestion.last || TodayQuestion.create_randomly
      today_question.question
    end

    def delete_cookie_if_question_changed
      answer = @question.answers.find_by(id: answer_id)
      cookies.delete('answer_id') if answer.nil?
    end

    def answer_id
      cookies['answer_id']
    end
end
```

回答をした際に cookie をセットします。

```ruby

class AnswersController < ApplicationController
  def create
    answer = Answers::Factory.build(answer_choice_params)
    if answer.save
      cookies['answer_id'] = answer.id
      redirect_to root_path
    else
      redirect_to root_path, notice: '回答に失敗しました。'
    end
  end

  private

    def answer_choice_params
      params.permit(:answer_choice_id)
    end
end
```

Heroku scheduler で1日1回、今日の問題をDBに登録しています。
時間と実行するコマンドはアドオンを入れた後に Heroku の管理画面から設定することができます。

```ruby
# sheduler.rake

task set_question_randomly: :environment do
  TodayQuestion.create_randomly
end
```

昨日と同じ問題が出たらテンションが下がるのでそれは避けるようにしています。

```ruby
class TodayQuestion < ApplicationRecord
  belongs_to :question

  def self.create_randomly
    question = nil

    loop do
      last_question = TodayQuestion.last.question
      question = Question.pick_randomly

      break if last_question.id != question.id
    end

    create(question: question)
  end
end
```

### 質問箱風なOGP画像を生成
続いて、質問箱風な OGP の生成処理です。
問題の作成時に問題のタイトルを画像に書き込み、画像を S3 にアップロードします。 (active_storage を利用)

そして、シェアした時に作成した問題文が書かれたOGP画像が表示されるようにします。

```ruby
# Gemfile
gem 'mini_magic'
```

```ruby

class OgpImageGenerator
  def initialize(question: question)
    @question = question
  end

  def generate
    image = load_base_image
    image.combine_options do |c|
      c.gravity 'West'
      c.pointsize 30
      c.draw "text 55,0 '#{title}'"
      c.size "70x"
      c.fill '#000'
      c.font Rails.root.join('public', 'hiraginokakugoW6.ttc').to_s
      c.interline_spacing 10
      c.kerning 4
    end
    image
  end

  private

    def title
      ApplicationController.helpers
                           .sanitize(@question.title)
                           .chars.each_slice(15)
                           .map(&:join)
                           .join("\n")
    end

    def load_base_image
      MiniMagick::Image.read(Rails.root.join('public', 'images', 'ogp_base.png'))
    end
end
```

```ruby

class Question < ApplicationRecord
  ..
  before_save :generate_ogp_image
  before_update :generate_ogp_image

  has_one_attached :ogp_image

  def generate_ogp_image
    return unless self.will_save_change_to_title?

    ogp_image_path = OgpImageGenerator.new(question: self).generate.path
    file = File.open(ogp_image_path)

    self.ogp_image.attach(
      io: file,
      filename: "question_ogp_image_#{id}_#{SecureRandom.urlsafe_base64}.png", content_type: "image/png"
    )
    file.close
  end
end
```

![スクリーンショット 2019-05-21 12.17.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38580/ce3ce208-dc2c-b634-5776-4eefbfc3dc58.png)

問題を作成すると、このような OGP 画像が自動で生成されます。


## これから

ログイン機能とコース機能を作って問題を解けるようにしたいと思っています。
ログインしていなくても問題を解くことができる一方で、熱量の高い人はログインしてまとまった時間で勉強できたら良いですね。

実は一番大変なのは機能の実装より SQL の問題を作ることだったりしますw


## 最後に

詳細な使い方については note に書いているので見ていただけたら幸いです。
https://note.mu/usabdelah/n/n0b7f8fd30ed5

Qdash で学んだことを活かして実務で SQL を書き、
データの可視化をやっていく人が増えたらいいなと思っています。

ぜひ、Qdash で SQL の問題を解いてみてください。

https://q-dash.jp