2018年4月15日日曜日

Sinatra + bulma で簡単ダッシュボード作成

概要

bulma はモダンな WebUI がデザインできる CSS フレームワークです
今回は Sinatra と組み合わせて動的なダッシュボード UI を作成してみます

環境

  • macOS 10.13.2
  • Ruby 2.4.1p111
  • sinatra 2.0.1
  • bulma 0.6.2

テンプレートファイルの作成

bulma には様々なテンプレートが準備されておりその中にダッシュボード用のテンプレートがあります
これを Sinatra で動くように移植していきます

  • mkdir views
  • cd views
  • wget 'https://raw.githubusercontent.com/dansup/bulma-templates/master/templates/admin.html'
  • mv admin.html admin.erb

テンプレート用のディレクトリを作成しここに用意されているテンプレートファイルを配置します
更に Ruby から参照できるように拡張子を .erb に変更します

変数埋め込み

とりあえず今回はタイトルの部分を変数にしてみたいと思います

  • vim admin.erb
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Admin - Free Bulma template</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=" crossorigin="anonymous" />
  <link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,700" rel="stylesheet">
  <!-- Bulma Version 0.6.0 -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.0/css/bulma.min.css" integrity="sha256-HEtF7HLJZSC3Le1HcsWbz1hDYFPZCqDhZa9QsCgVUdw=" crossorigin="anonymous" />
  <link rel="stylesheet" type="text/css" href="../css/admin.css">
</head>
<body>

  <!-- START NAV -->
  <nav class="navbar is-white">
    <div class="container">
      <div class="navbar-brand">
        <a class="navbar-item brand-text" href="../">
          <%= @title %> 
        </a>

長いので先頭から対象部分まで表示しています
下の方でタイトル部分を <%= @title %> に変更しています
この変数を Sinatra アプリでいろいろと変更してみたいと思います

bulma に必要なファイルの設定

css と js が必要なものがあるので配置します

  • mkdir -p public/css
  • mkdir -p public/js
  • cd public/css
  • wget 'https://github.com/dansup/bulma-templates/raw/master/css/admin.css'
  • cd public/js
  • wget 'https://github.com/dansup/bulma-templates/raw/master/js/bulma.js'

これらもすでに用意されているテンプレートから取得します

コントローラ側の実装

先ほど作成した .erb のテンプレートファイルに埋め込んだ変数の値をコントローラ側で設定します

  • vim app.rb
require 'sinatra'

get '/' do
  @title = ['bulma admin', 'ぶるまあどみん', '管理画面'].sample
  erb :admin
end

ランダムでタイトル用の値を設定しているだけです
あとは erb メソッドを使って先ほどの admin.html をシンボルで参照します

動作確認

これで localhost:4567 にアクセスすると以下のように表示されると思います
sinatra_bulma1.png

アクセスするごとに左上のタイトル部分が変化するのがわかると思います

今回はタイトル部分しか弄りませんでしたが例えば数字の部分などは MySQL から取得したものを表示するなどの処理を app.rb に書き足してあげれば更に動的な値を表示してあげることもできます

ツリー

今回の成果物のツリー構造です

$ tree -I "vendor" .
.
├── Gemfile
├── Gemfile.lock
├── app.rb
├── public
│   ├── css
│   │   └── admin.css
│   └── js
│       └── bulma.js
└── views
    └── admin.erb

4 directories, 6 files

最後に

Sinatra + bulma で超簡単にダッシュボードを実装してみました

nodejs のように npm でインストールしてやる感じではなく静的ファイルをダウンロードしてやるので直感的に開発はできるかなと思います
あとは CSS や JS をカスタマイズすれば更に動的なページが作れると思います

自分はこのやり方であとは jQuery を使って開発しています
Ruby でというか Sinatra で WebUI を作る場合はどんな感じで進めるのが良いのかベストプラクティスを知りたいです、、、

参考サイト

2018年4月14日土曜日

sidekiq/api を使ってキューやジョブの操作をしてみる

概要

Sidekiq はワーカーがエラーをキャッチすると自動でリトライ処理をしてくれます
故にエラーが解消されない限りはワーカーが永遠にジョブを処理し続けてしまいます
そんな場合にはキューからジョブを削除しなければなりません
直接 redis を操作しても問題ないですが Sidekiq にはキューやジョブを操作する API が備わっているので使ってみました

環境

  • macOS 10.13.2
  • Ruby 2.4.1p111
  • Redis 3.2.1
  • sidekiq 5.1.3

Redis に接続する

今回の作業はすべて irb (bundle console) で行います

デフォルトだと localhost:6379 のデータベースインデックス 0 に接続します
今回はインデックスが 1 のデータベースに接続するため切り替える必要があります

irb(main):001:0> require 'sidekiq/api'
=> true
irb(main):002:0> Sidekiq.redis { |con| con.select 1 }
=> "OK"

これでインデックス 1 のデータベースに切り替えできました

キューの一覧を取得する

特にしていしなければ default のみあると思います

irb(main):003:0> Sidekiq::Queue.all
=> [#<Sidekiq::Queue:0x007fc13b3a0238 @name="default", @rname="queue:default">]
irb(main):004:0> q = Sidekiq::Queue.new
=> #<Sidekiq::Queue:0x007fc13b371d20 @name="default", @rname="queue:default">

Sidekiq::Queue.new でキューオブジェクトを作成します
引数に文字列でキュー名を指定することができます
指定しない場合は default になります
今回はこの default キュー内のジョブを削除してみます

キュー内に溜まったジョブの一覧を取得する

each で回すだけです

irb(main):008:0> q.each { |job|
irb(main):009:1* p job
irb(main):010:1> }
=> nil

特に何も入っていないので何も表示されませんでした

リトライ対象のジョブの一覧を取得する

リトライ対象のジョブは別のクラスから取得することができます
Sidekiq::RetrySet を使います

irb(main):014:0> rs = Sidekiq::RetrySet.new
=> #<Sidekiq::RetrySet:0x007fc13b83fc08 @name="retry", @_size=1>
irb(main):015:0> rs.each { |job|
irb(main):016:1* p job['jid']
irb(main):017:1> p job['retry_count']
irb(main):018:1> }
"85d82e45decb7474d5fbfbaf"
6
=> nil

長いので jid と retry_count だけ表示しています
ここにジョブが入っていると default キューを見るワーカーがこのジョブを処理し続けます

リトライジョブを削除する

ジョブに対して delete メソッドをコールするだけです

irb(main):022:0> rs.each { |job|
irb(main):023:1* job.delete
irb(main):024:1> }
=> nil
irb(main):025:0> rs.each { |job|
irb(main):026:1* p job.jid
irb(main):027:1> }
=> nil
irb(main):028:0> rs.size
=> 0

もし特定のジョブだけを削除したい場合は一旦 jid のリストを取得して、指定の jid とマッチしたら delete するという感じの処理を追加すれば OK です
おそらくこの操作が一番運用で使うんじゃないかなと思います

最後に

sidekiq/api を使ってキューやジョブの操作をしてみました
前回紹介したダッシュボードの機能を使えば UI でもリトライジョブを削除することができると思います
1 つや 2 つであれば UI でもいいですが、何百何千という単位で処理したい場合はこれを使ってプログラムを組んだほうが良いかなと思います

参考サイト

2018年4月13日金曜日

Sidekiq でキューを指定してワーカーを起動する方法

概要

Sidekiq はデフォルトで default という名前のキューを使用します
用途の異なる複数のワーカーを作成する場合にキューを指定しないとすべてのワーカーが default を見てしまい本来掴みたくないジョブを掴んでしまうことがあります
その場合はワーカーごとにキューをわけましょう

環境

  • macOS 10.13.2
  • Ruby 2.4.1p111
  • Redis 3.2.1
  • sidekiq 5.1.3
  • sinatra 2.0.1

ワーカークラスでキューを指定する

これはエンキューするクライアントが参照します
当初ここに記載しておけばワーカー側も自動で参照してくれると思ったのですが、これはクライアント用の設定みたいです

require 'sidekiq'

Sidekiq.configure_server do |config|
  config.redis = { 'db' => 1 }
end

class MyWorker
  include Sidekiq::Worker
  sidekiq_options queue: 'test_queue'

  def perform(complexity)
    case complexity
    when 'super_hard'
      sleep 20
      puts 'super_hard'
    when 'hard'
      sleep 10
      puts 'hard'
    else
     sleep 1
     puts 'easy'
    end
  end
end

sidekiq_options queue: 'test_queue' の設定を 1 行追加しているだけです
これで MyWorker.perform_async したときに test_queue にエンキューされます

ワーカーを起動する時にキューを指定する

ワーカーは起動するときのオプションで参照するキューを指定します

  • bundle exec sidekiq -q test_queue -r ./worker.rb

これで default ではなく test_queue を参照するようになります
試しに redis-cli を使って確認したところちゃんと指定のキューが作成されていることがわかると思います

$ redis-cli -n 1
127.0.0.1:6379[1]> SMEMBERS queues
1) "test_queue"

最後に

Sidekiq でキューを指定してワーカーを動作させる方法を紹介しました
ワーカーを起動する時にオプションが必要になるのがポイントでしょうか

もう一つ方法として namespace を使う方法もあるらしいですがこれはまた別の方法かなと思います
新たにキューを作成するわけではなく更に上位の概念で分割しようという機能だと思います
また、namespace を使う場合には redis-namespace という gem も必要になります

参考サイト

2018年4月12日木曜日

Sinatra + Sidekiq で簡単非同期 Web アプリ

概要

前回 Sidekiq に入門してみました
今回は Sinatra と組み合わせてみました

環境

  • macOS 10.13.2
  • Ruby 2.4.1p111
  • Redis 3.2.1
  • sidekiq 5.1.3
  • sinatra 2.0.1

ワーカー

基本的には前回と同じです

  • vim worker.rb
require 'sidekiq'

Sidekiq.configure_server do |config|
  config.redis = { 'db' => 1 }
end

class MyWorker
  include Sidekiq::Worker

  def perform(complexity)
    case complexity
    when 'super_hard'
      sleep 20
      puts 'super_hard'
    when 'hard'
      sleep 10
      puts 'hard'
    else
     sleep 1
     puts 'easy'
    end
  end
end
  • bundle exec sidekiq -r ./worker.rb -P ./tmp/sidekiq.pid

とりあえず起動しておきましょう

アプリ

普通の Sinatra アプリに enqueue する処理をいれるだけです

require 'sinatra/base'
require './worker.rb'

Sidekiq.configure_client do |config|
        config.redis = { 'db' => 1 }
end

class MyApp < Sinatra::Base
        get '/enqueue/:value' do
                MyWorker.perform_async(params['value'])
                "enqueue #{params['value']}"
        end
end
  • bundle exec rackup config.ru

動作確認

  • curl 'localhost:9292/enqueue/super_hard'
  • curl 'localhost:9292/enqueue/hard'
  • curl 'localhost:9292/enqueue/easy'

と実行してみましょう
すぐにレスポンスが返ってくると思います

そしてワーカーのログを確認するとちゃんと非同期処理が実行されていることが確認できると思います

easy
2018-04-04T09:20:43.050Z 5683 TID-ovxtvzpkn MyWorker JID-170aa25c66fcdafb78e7c049 INFO: done: 1.001 sec
hard
2018-04-04T09:20:49.878Z 5683 TID-ovxtw03kf MyWorker JID-22663fbd98445bde4119a4d1 INFO: done: 10.006 sec
super_hard
2018-04-04T09:20:57.103Z 5683 TID-ovxtvzpbn MyWorker JID-895bd0adfb849652c0860eae INFO: done: 20.005 sec

最後に

Sinatra + Sidekiq で超かんたんな非同期 Web アプリを作成してみました
処理も切り離せて管理もしやすいと思います

サービスにする場合はちゃんとキューの状態などを追える仕組みを考えないとダメだと思います

2018年4月11日水曜日

Sidekiq で管理画面を表示する方法

概要

Sidekiq には管理画面がデフォルトでついており現在実行中のジョブなどを視覚的に確認することができます
デフォルトだと使えないようなので有効にする方法を紹介します

環境

  • macOS 10.13.2
  • Ruby 2.4.1p111
  • Redis 3.2.1
  • sidekiq 5.1.3

ダッシュボード用の Rack アプリの作成

すでにモジュールが用意されているのでそれを実行するだけです

  • vim config.ru
require 'sidekiq'

Sidekiq.configure_client do |config|
  config.redis = { :size => 1 }
end

require 'sidekiq/web'
run Sidekiq::Web

動作確認

実行してみましょう
Rack で動作します

  • bundle exec rackup config.ru

Rack なのでデフォルトは 9292 ポートで動作します
ブラウザでアクセスすると以下のような UI が表示されると思います

sidekiq_dashboard1.png

ちゃんとローカライズもされていました
もちろんワーカーを動作させるとグラフが描画されるのを確認することができます

最後に

Sidekiq のダッシュボードアプリを作成してみました
簡単に作れるのでとりあえず立てておくと運用も楽になるかなと思います

Heroku にデプロイできるボタンもあったので Redis が外部から参照できるのであれば Heroku にデプロイしちゃってもいいかなと思います

参考サイト

2018年4月10日火曜日

Sidekiq 超入門

概要

Sidekiq はキュー&ワーカー方式で非同期処理を実現するためのツールです
バックエンドに Redis を使っています
今回は動作させるところまでやってみました

環境

  • macOS 10.13.2
  • Ruby 2.4.1p111
  • Redis 3.2.1
  • sidekiq 5.1.3

インストール

  • bundle init
  • vim Gemfile
gem "sidekiq"
  • bundle install --path vendor

Redis も必要になるのでインストールしていない場合は brew などを使ってインストールしてください

ワーカー作成

vim worker.rb

require 'sidekiq'

Sidekiq.configure_server do |config|
  config.redis = { 'db' => 1 }
end

class MyWorker
  include Sidekiq::Worker

  def perform(complexity)
    case complexity
    when 'super_hard'
      sleep 20
      puts 'super_hard'
    when 'hard'
      sleep 10
      puts 'hard'
    else
     sleep 1
     puts 'easy'
    end
  end
end

include Sidekiq::Worker し perform メソッドを実装することでワーカーを作成します
クライアント側でキューイングする際に perform_async を呼び出すことでこの perform メソッドが自動的に呼ばれます

今回は非同期で実行されているか確認できるように sleep を入れています

クライアント作成

  • vim test_client.rb
require './worker.rb'

Sidekiq.configure_client do |config|
  config.redis = { 'db' => 1 }
end

class MyClient
  def enqueue(complexity)
    MyWorker.perform_async(complexity)
  end
end

cli = MyClient.new
cli.enqueue('super_hard')
cli.enqueue('hard')
cli.enqueue('easy')

クライアント側は worker クラスが必要になります
MyWorker クラスでは include Sidekiq::Worker することでエンキュー用の perform_async というメソッドが使えるようになっているのでこれをコールします

今回はワーカー側もクライアント側も localhost:6379 のデフォルトの Redis を参照しているため特にホストやポートの指定はしていませんが、ホストやポートが異なる場合は url というオプションがあるのでそれを使って指定してください (参考)

動作確認

まずはワーカーを起動します
ワーカーは Redis を必要するので Redis も起動しておいてください

  • redis-server
  • mkdir ./tmp
  • bundle exec sidekiq -r ./worker.rb -P ./tmp/sidekiq.pid

sidekiq というコマンドがあるのでこれを使ってワーカースクリプトを指定します
sidekiq コマンドの詳しいオプションは bundle exec sidekiq --help で確認できます
-P オプションで PID ファイルを作成することができます
ワーカーを停止する際に必要になるので指定しておきます
ずらずらーとログが流れてくると思います
キックしている AA とエラーログが流れなければ OK です

次にクライアントを起動します

  • bundle exec ruby test_client.rb

すると以下のようにログが流れると思います

2018-04-04T06:17:54.025Z 4252 TID-oxfu7p3gg MyWorker JID-4473b56c73ed2dd8cf1f89a7 INFO: start
easy
2018-04-04T06:17:55.029Z 4252 TID-oxfu7p3gg MyWorker JID-4473b56c73ed2dd8cf1f89a7 INFO: done: 1.004 sec
hard
2018-04-04T06:18:04.027Z 4252 TID-oxfu7zlbk MyWorker JID-b6be42cf2f3e5f34c7f47468 INFO: done: 10.002 sec
super_hard
2018-04-04T06:18:14.030Z 4252 TID-oxfu7zlh4 MyWorker JID-4ea3461949baefd67e58172c INFO: done: 20.005 sec

enqueue した順番は super_hard -> hard -> easy ですが出力されるのは easy -> hard -> super_hard で表示されており非同期でそれぞれ処理されているのがわかります

ワーカーを停止する場合は sidekiqctl というコマンドが用意されているのでこれを使うと良いです

  • bundle exec sidekiqctl stop ./tmp/sidekiq.pid

PID ファイルを指定すれば停止できます
全部のワーカーが完了するかタイムアウトの 10 秒後の終了します
タイムアウトの時間は設定で変更できます

最後に

SIdekiq の基本的な使い方を紹介しました
Redis を導入する必要はありますが比較的に簡単に導入できるので非同期処理を実現するにはオススメです

Web アプリではよく Rails に組み込む方法が紹介されていますが Sinatra などの軽量フレームワークでも使うことはできます
そのあたりも検証できたら紹介したいと思います

参考サイト

2018年4月9日月曜日

Mac の vim で Ruby のカラーシンタックスを適用する

概要

MacOS High Sierra にインストールされているデフォルトの vim だと Ruby のカラーシンタックスがされていません
今回は dein.vim をインストールしてカラーシンタックス用のプラグインをインストールするところまでやってみました

環境

  • macOS 10.13.2
  • vim 8.0

dein.vim のインストール

  • mkdir ~/.vim/dein/repos/github.com/Shougo/dein.vim/
  • curl https://raw.githubusercontent.com/Shougo/dein.vim/master/bin/installer.sh > installer.sh
  • sh ./installer.sh ~/.vim/dein/repos/github.com/Shougo/dein.vim/

で表示された内容を .vimrc に記載する

Ruby 用のカラーシンタックスプラグインをインストールする

  • vim ~/.vimrc

で 20 行目あたりに以下を追加します

call dein#add('ftdetect/ruby.vim')
call dein#add('indent/ruby.vim')

またその下あたりにある以下の行を削除しましょう
削除しないとエラーが出ます

call dein#add('Shougo/deol.nvim', { 'rev': 'a1b5108fd' })

で vim を開いてコマンドモードで以下を実行します

  • :call dein#install()

これでインストールが完了すれば OK です

おまけで今回はインデントを設定できるプラグインも入れています
デフォルトだとインデントが Tab になっているのでスペース 2 文字に設定したいので .vimrc の最後に以下を追加します

setlocal expandtab tabstop=2 shiftwidth=2 softtabstop=2

動作確認

.rb ファイルを開いてちゃんとカラーシンタックスが適用されているか確認してみましょう
vim_ruby_color_syntax1.png

最後に

とりあえずカラーシンタックスだけでもということでやってみました
自分は普段は emacs なので vim の使い方は正直よくわかっていません
もっと良い方法があったらすいません、、、

vim-ruby という始めから Ruby に特化した vim もあるようなので面倒であれば始めからそれを使うのもありだと思います

参考サイト