2019年5月18日土曜日

Sinatra + Redis で簡単な like ボタンを作ってみた

概要

Sinatra + Redis を使って簡単な like ボタンを実装してみました
Redis にデータを格納しているため like 数を永続化しています
今回はコーディングの方法を 1 から紹介したいと思います

環境

  • macOS 10.14.4
  • Ruby 2.6.2p47
  • Redis 4.0.9
  • Sinatra 2.0.5

環境構築

まず開発するための環境を準備します
必要な gem のインストールや redis の起動を行います

  • bundle init
  • vim Gemfile
gem "sinatra"
gem "redis"
  • bundle install --path vendor

でライブラリのインストールは OK です
あとはアプリに必要なファイルを作成します

  • touch app.rb
  • touch config.ru

この 2 つはサーバ側のコードを書くファイルになります
この 2 つは Ruby で実装します

  • mkdir views
  • mkdir public
  • touch views/index.erb
  • touch public/custom.css
  • touch public/custom.js

WebUI を構成するファイルになります
HTML や CSS, JavaScript を書きます
これで今回必要なファイルの準備が整いました
あとは redis を localhost で起動しておきましょう

  • redis-server

とりあえず like ボタンを設置する

まずはとりあえず like ボタンを設置してみましょう
今回はボタンのデザインに FontAwesome を使います

  • vim views/index.erb
<html>
<head>
  <link rel="stylesheet" href="/custom.css">
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css" integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
</head>
<body>
<button id="like">
  <i class="fas fa-heart fa-2x"></i>
</button>
<span id="like_count"><%= @like %></span>
</body>
</html>

FontAwesome を使うのに必要な CSS ファイルの読み込みとボタンおよびカウントの設置をしています
@like は Ruby 側から受け取った変数を展開するための erb の機能です

  • vim public/custom.css
#like_count {
    margin-left: 10px;
}

#like {
    border: none;
}

適当にスタイルシートを当てています
この辺りは好きなようにカスタムしてもらって大丈夫です

  • vim app.rb
require 'sinatra'
require 'redis'

class LikeApp < Sinatra::Base
  redis = Redis.new

  get '/' do
    @like = redis.get('like') || 0
    erb :index
  end
end

サーバ側のコードです
先程作成した index.erb を返すだけのコードになります
Redis にアクセスするコードも含めています
like というキーの値を取得しています
もしまだ値が設定されていない場合は 0 を設定します

  • vim config.ru
require './app.rb'
run LikeApp

あとはアプリケーションを Rack で起動するための起動スクリプトを書けば OK です
作成した app.rb を読み込んで Rack のコマンドの run を記載しているだけです

  • bundle exec rackup config.ru

これで localhost:9292 にアクセスすると以下のように like ボタンが表示されると思います

当然ですがクリックしてもまだ何も起きません

ボタンをクリックしたときのクライアント側の処理

次にボタンを押したときにサーバ側にリクエストするクライアント側のコードを作成しましょう
具体的には JavaScript を書きます

  • vim public/custom.js
$('#like').click(function() {
  $.ajax({
    url: '/like',
    type: 'POST'
  })
  .done((data) => {
    $('#like_count').text(data)
  })
});

DOM の操作には jQuery を使っています
button タグには id="like" という ID を振りました
それが .click() された場合にこの関数が呼ばれます
クリックされた場合にはサーバ側に Ajax を使ってリクエストします
具体的には POST /like をリクエストします
この POST /like のサーバ側の処理はこの後実装します
あとはレスポンスを受け取ったあとの処理を .done() 内に記載します
サーバからは like された後の like 数が返ってくる想定なので、それを DOM に表示してあげる処理を実装しています
Ajax では他にもエラー時の処理を .fail(), 常に行う処理を .always() として実装することができます
今回はそれらは特に使っていませんが例えば .fail() ではエラーの原因などの文章を DOM に表示してあげたりすると良いでしょう

  • vim views/index.erb
<html>
<head>
  <link rel="stylesheet" href="/custom.css">
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css" integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
  <script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
</head>
<body>
<button id="like">
  <i class="fas fa-heart fa-2x"></i>
</button>
<span id="like_count"><%= @like %></span>
<script type="text/javascript" src="/custom.js"></script>
</body>
</html>

作成した JavaScript を HTML 内で読み込む処理を追記しましょう
具体的には <script> タグを 2 箇所追記しています
ポイントは自分で作成した custom.js は body タグの直前に記載している点です
HTML が描画される前に jQuery を使った JavaScript がロードされると対象の DOM がまだない状態でロードされてしまうためうまく動作しません
なので DOM がすべて描画されたあとで JavaScript をロードしてあげましょう
もしくは JavaScript の実装を $(function() { ... }) で囲ってあげれば ready 後にロードされるのでそれでも OK です

これで再度アプリを起動しましょう
特に見た目は変わっていません
またサーバ側を実装していないので動きはしませんがコンソールを見ると 404 が発生しサーバ側にリクエストしようとしていることがわかると思います

ボタンをクリックしたときのサーバ側の処理

さてではメインのサーバ側の処理を実装します
やることは以下の通り

  • リクエストを受け取ったら現在の like 数に +1 して Redis に格納する
  • レスポンスには +1 後の like 数を返却してあげる

これを踏まえたサーバ側のコードは以下のようになります

  • vim app.rb
require 'sinatra'
require 'redis'

class LikeApp < Sinatra::Base
  redis = Redis.new

  get '/' do
    @like = redis.get('like') || 0
    erb :index
  end

  post '/like' do
    like = redis.get('like')
    if like.nil?
      like = 1
    else
      like = like.to_i + 1
    end
    redis.set('like', like)
    like.to_s
  end
end

post /like do ... end の部分を追記しています
redis.get した値は必ず String になっているので計算する際には .to_i メソッドを呼び出して Integer にしてあげましょう
また Sinatra はメソッドの最後に評価した値をレスポンスボディ (またはレスポンスコード) として返却します
文字列などの場合はレスポンスボディとして返却し数字の場合にはレスポンスボディが空で指定されたレスポンスコードで返却します
今回はレスポンスコードが 200 でレスポンスボディが +1 後の like 数なので like 数を文字列に変換することで、それをレスポンスボディとして返却しています

これで JavaScript 側で実装した .done()datalike.to_s の値が返ってくるようになります

  • bundle exec rackup config.ru

これで正常に動作します
like ボタンを押すとカウントアップするのが確認できると思います

アニメーションを入れる

これだけだと少しさびしいのクリックした際にアニメーションを入れてみましょう
クリックしたことがわかれば何でもいいので好きなアニメーションを入れれば OK です
今回は Animate.css を使ってみます

  • vim views/index.erb
<html>
<head>
  <link rel="stylesheet" href="/custom.css">
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css" integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css">
  <script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
</head>
<body>
<div id="section">
  <button id="like">
    <i class="fas fa-heart fa-2x"></i>
  </button>
</div>
<span id="like_count"><%= @like %></span>
<script type="text/javascript" src="/custom.js"></script>
</body>
</html>

animate.min.csshead タグ内で読み込んでいるのとアニメーションさせるためにボタンを <div id="section"> で囲んでいます
animate.css の仕様ですがブロック要素でないとアニメーションしないため div で囲っています
なお div などのブロック要素で囲っても display: inline などにするとインライン要素になりアニメーションしないので注意してください

  • vim public/custom.css
#like_count {
    float: left;
    margin-left: 10px;
}

#like {
    border: none;
}

#section {
    float: left;
}

ボタンとカウンタが横並びになるようにしています

  • vim public/custom.js
$('#like').click(function() {
  $('#section').addClass('animated bounce')
  $('#section').on('animationend', function() {
    $('#section').removeClass('animated bounce')
  })
  $.ajax({
    url: '/like',
    type: 'POST'
  })
  .done((data) => {
    $('#like_count').text(data)
  })
});

あとはボタンが押されたさいにアニメーションするための animate.css のクラスを追加してあげるだけです
jQuery を使ってクラスの追加や削除を行うのは addClass/removeClass を使います
また animationend がアニメーション終了時に発火するのでそれを拾ってちゃんとクラスを削除するようにしましょう

動作確認

あとはアプリを起動して動作確認してみましょう
今回の記事のアプリを完成されると以下のように動作すると思います

最後に

Sinatra + Redis で永続可能な like ボタン的なのを作ってみました
今回の仕様だと 1 人で何回も like できてしまいます
一番良いのはアカウント登録させて 1 アカウント 1 回だけ like させるような処理をサーバ側で実装するのが良いと思います
ただ、アカウント登録させるとなるとそれだけでグッとハードルが上がってしまいます
なので session などを使って制限するのが簡単かなと思います
例えば前回の like から 24 時間経過していないと次の like ができないようにするなどです
ただ session の場合はクライアント側で cookies を削除してしまえばどうにでも対応できるので完璧な対応ではないのでご注意ください

0 件のコメント:

コメントを投稿