投稿者「sasaki」のアーカイブ

[Laravel] JWTAuthの導入で詰まったポイント整理

概要

Laravel(とLumen)にJSONによる認証の仕組みを簡易的に導入するためのライブラリである、JWTAuth(tymondesigns/jwt-auth)を導入した際に以下のエラーに詰まったのでその解決方法の備忘録

$ php artisan jwt:generate

In BoundMethod.php line 135:

  Method Tymon\JWTAuth\Commands\JWTGenerateCommand::handle() does not exist

前提

以下環境で動作確認

debian 8.6
PHP 7.0
Laravel 5.5
composer 1.2.0

問題点

Laravel 5.4 と Vue.js 2.2 と JWTAuth で、ログインできる SPA アプリケーションのチュートリアル その4 – Qiita に沿って、Laravel + Vue + JWTAuthで、SPAによるログイン機能を実装しようとしていた所、jwt:generateを実行する所で以下のエラーが発生した。

$ php artisan jwt:generate

In BoundMethod.php line 135:

  Method Tymon\JWTAuth\Commands\JWTGenerateCommand::handle() does not exist

比較的新しい情報の通りに進めて躓くと辛いなと思いながら、頑張って原因を調べて解決した。

解決方法

バージョン互換性問題かはハッキリわからなかったが、このエラーが出る問題は、新しい開発版のtymon/jwt-authパッケージで解決されている。よって、以下のコマンドで開発版をインストールする。

composer require tymon/jwt-auth:dev-develop --prefer-source

バージョンアップに伴い、サービスプロバイダの設定も書き換える必要がある。config/app.phpの該当箇所を以下のように書き換える。

変更前

'providers' => [
    ....
    Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class
],

変更後

'providers' => [
    Tymon\JWTAuth\Providers\LaravelServiceProvider::class
],

最後に、認証用の秘密鍵も更新する。

php artisan jwt:secret

これで後はLaravel 5.4 と Vue.js 2.2 と JWTAuth で、ログインできる SPA アプリケーションのチュートリアル その4 – Qiitaの通りに実装を進めることができた。

備考

ライブラリの安定版でなく開発版を使うのはいささか気が引ける。安定版のままでも動かせる解決策があったら乗り換えたい。

参考

[PHP] Fakerでランダムなフェイクデータを作成する

概要

PHPでランダムなフェイクデータ(テストデータ/ダミーデータ)を作成するためのライブラリであるFakerのTips

全体

以下環境で動作確認済み

debian 8.6
php 7.0
composer 1.5.2
faker 1.7.2

Fakerについて

FakerはPHPで利用できる、フェイクデータ(テストデータ/ダミーデータ/フェイクデータ)を生成するためのライブラリで、以下のようにそれっぽいデータを作成することができる。

<?php
// Faker autoloadeをrequireすることで使える
require_once 'vendor/fzaninotto/faker/src/autoload.php';

// フェイクデータを生成するジェネレータを作成
$faker = Faker\Factory::create('ja_JP');

// ジェネレータのプロパティにアクセスする形式でフェイクデータを取得できる
echo $faker->name;
  // '若松 直樹';
echo $faker->address;
  // '8891527  石川県田中市東区斉藤町佐藤4-4-2 ハイツ桐山107号';
echo $faker->text;
  // Dolores sit sint laboriosam dolorem culpa et autem. Beatae nam sunt fugit
  // et sit et mollitia sed.
  // Fuga deserunt tempora facere magni omnis. Omnis quia temporibus laudantium
  // sit minima sint.

用途としては、開発中のシステムのテストデータが考えられる。リリース前のサービスで、本番と同様の条件で動作を確認したい時、テストデータがuser1,user2,user3などの形式的なデータだとリアリティがない。そんな場合にFakerを使えば、実在しそうなユーザなどのデータを容易に準備することが出来る。

さらに、Fakerはフェイクデータ取得の都度、異なるデータを生成するので、毎回まったく違うデータで動作確認を行うことができるため、特定のデータの場合にのみ発生するバグなどに気づくこともできる。

Fakerのインストール

FakerはComposerで簡単にインストールできる。動作確認用の環境にcomposerが入っていなかったので、まずcomposerをインストールする。

$ sudo apt-get update
$ sudo apt-get -y composer
$ composer --version
Composer 1.5.2 2017-09-11 16:59:25

Composerが入ったので、fakerをインストールする。

$ composer require fzaninotto/faker

利用準備

まずは簡単な例でfakerの使い方を確認する。composer installを実行したディレクトリにPHPファイルを作成し、以下のコードを記述する。

<?php

// インストールしたfakerのautoload.phpをrequireする
require_once 'vendor/fzaninotto/faker/src/autoload.php';

// フェイクデータを生成するジェネレータを作成
$faker = Faker\Factory::create('ja_JP');

// 日本人の氏名を10人分出力
for ($i = 0; $i < 10; $i++) {
  echo $faker->name . "\n";
}

実行すると、以下のように日本人の氏名が10人分出力される。

青山 治
渡辺 京助
三宅 加奈
渡辺 裕樹
小泉 治
佐藤 さゆり
笹田 幹
村山 さゆり
原田 零
木村 京助

下記のジェネレータ生成メソッドでは、’ja_JP’を指定しているが、これは生成するフェイクデータのローケル情報を表す。

$faker = Faker\Factory::create('ja_JP');

これを省略して下記のように定義し、上記コードを実行すると

$faker = Faker\Factory::create();
Lynn Bins
Mozelle Glover
Mikel Borer PhD
Dr. Joel Marvin
Aurelie Beahan
Pete Herzog
Janae Borer I
Toy Heidenreich
Prof. Tiara Shanahan DDS
Ivah Blanda V

のように、英語圏の人名が出力される。
日本語に対応しているフェイクデータは、氏名、住所などの一部のデータに限るが、以降ではja_JPを指定した状態で進める。

名前関連のフェイクデータ

氏名とフリガナは日本語でそれぞれ取得できる。本当に居そう。

// 氏名
echo $faker->name . "\n";

// 名字
echo $faker->lastName . "\n";

// 名前
echo $faker->firstName . "\n";

// 氏名(カナ)
echo $faker->kanaName . "\n";

// 名字(カナ)
echo $faker->lastKanaName . "\n";

// 名前(カナ)
echo $faker->firstKanaName . "\n";
斉藤 くみ子
山本
美加子
カノウ アスカ
カノウ
ナオト

個人情報関連のフェイクデータ

// メールアドレス
echo $faker->email . "\n";

// 電話番号
echo $faker->phoneNumber . "\n";

// 住所
echo $faker->address . "\n";

// 郵便番号
echo $faker->postcode . "\n";

// 都道府県
echo $faker->prefecture . "\n";

// 市町村
echo $faker->city . "\n";

// 番地など
echo $faker->streetAddress . "\n";

// 誕生日(20歳から80歳になるように)
echo $faker->dateTimeBetween('-80 years', '-20years')->format('Y-m-d') . "\n";
0410-021-281
7443156 奈良県村山市東区加納町木村10-1-5
1384890
岐阜県
山口市
井高町青山10-5-8
1942-07-21

Webサービスっぽいフェイクデータ

// UUID
echo $faker->uuid . "\n";

// ユーザ名
echo $faker->userName . "\n";

// パスワード(記号あり)
echo $faker->password . "\n";

// URL
echo $faker->url . "\n";

// IPアドレス(IPv4)
echo $faker->ipv4 . "\n";

// マックアドレス
echo $faker->macAddress . "\n";

// ユーザーエージェント
echo $faker->userAgent . "\n";
vagrant$ php qs.php
2e829943-718c-3cd8-a2fb-6614ab1139b1
kudo.kyosuke
DKVA_6K03S-=@|
http://tanabe.com/qui-asperiores-sequi-sit-voluptatem-sint-dolore-vero-assumenda.html
144.48.103.1
D8:0E:E5:B9:D4:22
Opera/9.42 (X11; Linux i686; sl-SI) Presto/2.11.249 Version/11.00

乱数系

この辺はfakerでなくても出来るけど、どうせならfaker1本で済ませたい。

// 一桁の乱数
echo $faker->randomDigit() . "\n";

// 100~200の整数
echo $faker->numberBetween(100, 200) . "\n";

// 小数2桁の0~100の実数
echo $faker->randomFloat(2, 0, 100) . "\n";

// a,b,c,dからいずれか一つ
echo $faker->randomElement(['a', 'b', 'c', 'd']) . "\n";

// a,b,c,dからいずれか二つ
var_dump($faker->randomElements(['a', 'b', 'c', 'd'], 2));

// 文字列をシャッフル
echo $faker->shuffle('abcdefghijklmnopqrstuvwxyz') . "\n";
4
178
43.38
b
array(2) {
[0]=>
string(1) "d"
[1]=>
string(1) "a"
}
nziajpludqogcyfkrmweshtbxv

文字列系

最初難しい英単語ばかりだなと思ったけど全部ラテン語だった。

// ラテン語1つ
echo $faker->word() . "\n";

// ラテン語3つ
var_dump($faker->words(3));

// 10単語で構成される文
echo $faker->sentence(10) . "\n";

// 文章
echo $faker->paragraph() . "\n";
voluptatem
array(3) {
[0]=>
string(10) "blanditiis"
[1]=>
string(8) "expedita"
[2]=>
string(10) "reiciendis"
}
Qui voluptatum doloribus ad mollitia laudantium nobis quia.
Debitis est soluta quibusdam harum facilis ut deleniti aperiam. Molestias repellendus occaecati ad cupiditate omnis voluptatum et beatae. Enim et fugit quae et explicabo iusto maxime. Molestiae enim maiores et sit ut occaecati.

備考

ここで紹介したのはFakerの機能の極一部。もっと知りたい方はfzaninotto/Faker: Faker is a PHP library that generates fake data for youを見よう

3年目プログラマが参考にしたQiita記事まとめ⑨

概要

特にカテゴリもレベルも偏らずにざっくばらんに、参考になったQiita記事を一言添えて列挙するシリーズ9本目

Qiitaまとめシリーズ

脱Word、脱Markdown、asciidocでドキュメント作成する際のアレコレ

手軽でサクッと書けるMarkdownは大好きだけど、表の結合ができないとか、テキストのスタイルを細かく指定できないとか、手軽ゆえの弱点が多いのも否めない。asciidocを選ぶかはともかく、もう少し複雑な記述をするための方法を一つぐらい持っておいたほうが良さそう

Python クラスについて

Pythonをなぁなぁで使ってるので一通り確認。Pythonをよく理解してないからだろうけど、どうにもPythonのオブジェクト指向は不完全さを感じる。メソッド毎に引数にselfを定義しないと行けないのも煩わしいし、全体的にRubyに劣ると思う。全てがオブジェクトのRubyと比較するのも違うとだろうが。

2017年のJavaScript開発で知っておきたい用語集/リンク集

今年は積極的にフロントエンドの技術を取り入れるように心がけたので、全体的に知っている、理解してる、使っているものだったが、フロントエンドのテスト手法に関しては全然できなかった。まだサーバサイドのテストもコレという手法を確率的で居ないが、フレームワークによるフロント開発が増えてきたのでボチボチ手を出したい。

JavaScriptのfloatで正確な比較を行う

Babelの勉強にはなったけど、インチキだった(笑) 誤差許容値を指定して比較してる時点で「正確」とは言わないんだよなぁ‥‥。

Ruby におけるハッシュ (Hash) と構造体 (Struct) の使い分け

恥ずかしながら、Rubyの構造体を全然知らなくて、何でもかんでもハッシュにしていたのでここらでお勉強。しっくり来ないけど、要素の動的追加がない場合は積極的に構造体使っていけそう。何よりドット記法で属性にアクセスできるのは自然で強力。

Rubyのメソッドの引数受け渡しまとめ

続けて恥ずかしながら、この記事を読んで初めてキーワード引数を使えることを知った。これまで、def func(params = {})みたいに、デフォルトが空のハッシュを引数に取ることでオプション的な使い方をしてたけど、def func(option_a: false, option_b: false) のほうが良さそう。

「進捗どうですか?」より2015倍捗る「困ってますか?」

「進捗どうですか?」にかぎらず、コミュニケーションの第一声はクローズドクエスチョン(YES/NO)のほうが良さそう。

データベース列名の名前付け(英単語での)採用例を集めてみた

やっぱり日本人的に身近でわかりやすい英単語で、かつ列の内容を確実に理解できるワードを採用したい。後者については、設計時点で列に複数の意味を持たせないような設計が望まれそう。

vimで検索してから行削除する(globalコマンドを覚える)

vimのグローバルコマンド全然使ってなかったけどこれは便利。特定の検索ワードに合致した行全体に対して特定の操作ができるのは行指向で考えるとかなり強力。

エンジニアが転職する時に考えることを採用面接官をしてる立場から書いてみた

面接官視点の話はあんまり気にしたことなかったので参考になった。転職活動開始に合わせてその準備をするのは遅いってことはわかった。現状スキルシートのOSS化もしてるし準備万端といえば万端だけど他にできることはなんだろ。

DockerでLaravelの開発環境を速攻で作る

概要

Dockerにて、Laravel開発用ツールが1イメージに詰まったlaraedit-dockerイメージをpullして、速攻でLaravelの開発環境を作る。docker-composerは使わず、最短手順で構築する。

前提

ホストOSは以下の通り

CentOS 6.9
docker 1.7.1

構築するイメージ(Laravel開発環境)は以下の通り

php 7.0.1
Laravel 5.5.23
mySQL 14.14
nginx 1.10.1
node 5.12.0
npm 3.8.6

また、DockerがインストールされたCentOSを用意できていること

laraedit-dockerとは

laraedit-dockerはLaravelのHomeSteadと同じようなことをDockerで出来るようにしたDockerイメージ

HomeSteadとは

HomeSteadは、Laravelの開発環境(PHP/MySQL/Nginx/Node…)をオールインワンパッケージとしたVagrantのbox。DockerじゃなくてVagrantでLaravelの開発環境を構築した場合は普通にこちらを使う。

何故Dockerを使うのか

VagrantのHomeSteadはLaravel本家から出ているので安心感もあるし、関連情報も多い。
が、本記事では以下の理由からDockerを採用する

  • 起動/停止が高速
  • 複数の環境を容易に立ち上げ、使い捨てられる
  • 容量をあまり食わない

Dockerイメージを取得

早速Dockerイメージを取得しよう。以下のコマンドだけでイメージは取得できる。

$ docker pull laraedit/laraedit

Dockerコンテナの作成

Laravel開発環境となるコンテナを立ち会が得る

$ docker run -p 8000:80 --name myapp -v /path/to/your/app:/var/www/html/app laraedit/laraedit

何となく80番は本番以外で使いたくないので8000番を80番にポートフォワーディング。
ホストの/path/to/your/appを/var/www/html/appに共有ディレクトリ設定することで、GUIで作業する際に捗る。vimmerの方は不要。
コンテナを識別しやすいようにmyappと名前をつける。

Laravelのインストール

まずは作成したコンテナにbashでアクセスする。

$ docker exec -it myapp bash
root@b780875cb411:/$

デフォルトだと、/var/www/html/appがアプリケーションディレクトリになるので、本記事ではデフォルトのまま、ここを使用する。変更する場合はnginxの設定も弄る必要があるので注意。

root@b780875cb411:~$ cd /var/www/html/
root@b780875cb411:/var/www/html/$

composerも既にインストール済みなので、laravelのプロジェクトを作る。appディレクトリを上書きしてほしいのでプロジェクト名をappにする。何かオプション上手く使えばもっとスマートに出来る気がする。

$ root@b780875cb411:/var/www/html/$ composer create-project --prefer-dist laravel/laravel app
Installing laravel/laravel (v5.5.22)
  - Installing laravel/laravel (v5.5.22)
    Downloading: 100%
(以下略)

appディレクトリにLaravelの構成ファイルが配置される。
忘れがちだけどenvファイルを生成してAppキーもセットする。

root@b780875cb411:/var/www/html$ cd app
root@b780875cb411:/var/www/html/app$ cp .env.example .env
root@b780875cb411:/var/www/html/app$ php artisan key:generate
Application key [base64:cPt6tNOTSaqakthBXTtra0sZTIXPFZ9DRoMdKIX0934=] set successfully.

動作確認

Dockerのホストがlocalhostの場合、localhost:8000にアクセスすると

やったぜ

Redux Form で、クライアントバリデーションを実施する

概要

Redux用のフォームライブラリである、Redux Formを用いて、クライアントバリデーション(validation)を実施した備忘録。本記事では、Reactを用いたRedux及びReduxFormの使用を前提とする。比較的我流が多くて間違った使い方をしてる可能性もある。

gifは圧縮してるのでクリックで鮮明なバージョンを確認できます。

前提

以下環境で動作確認済み

debian 8.6
react 15.6
redux 3.7.2
react-redux 4.4.8
redux-form 6.8.0

validation用メソッドを用意する

以下のような各種validateメソッドを用意する。validateメソッドは、入力値と、失敗時のエラーメッセージを指定し、入力値に応じてエラーメッセージかundefinedを戻すようにする。これらのメソッドは、各フォームから必要に応じて利用できるように、exportしておく。

/**
 * ReduxFormバリデーション用のバリデーションメソッド集
 * 各メソッドは、先頭の引数で入力データを受け取り、undefinedかエラーメッセージを戻す
 */

// 入力必須
export const required = (value, msg) => !value ? msg : undefined;

// 数値
export const number = (value, msg) => value && isNaN(value) ? msg : undefined;

// 最大文字数
export const maxLength = (value, max, msg) => value && value.length > max ? msg : undefined;

// 最小文字数
export const minLength = (value, min, msg) => value && value.length < min ? msg : undefined;

// 最大値
export const maxNum = (value, max, msg) => value && value > max ? msg : undefined;

// 最小値
export const minNum = (value, min, msg) => value && value < min ? msg : undefined;

// フォーマット
export const format = (value, reg, msg) => value && !value.match(reg) ? msg : undefined;

// メールアドレス
export const email = (value, msg) => format(value, /^[\w+\-.]+@[a-z\d\-.]+\.[a-z]+$/i, msg);

validation機能付きのFieldコンポーネントを実装する

下記のRenderFieldコンポーネントは、ReduxForm用のFiledコンポーネントと、エラーメッセージを表示する仕組みをラッピングしたコンポーネント。input要素及び、select,textareaに対応し、必要に応じてclassNameやdisabled属性を引き継げるようにしている。

/**
 * ReduxFormのFieldと、エラーメッセージを出力する機能を組み合わせたテンプレート
 */
export class RenderField extends React.Component {
  render() {
    const { className, type, input, disabled, meta: {touched, error} } = this.props;
    return (
      <div className="inline">
        { type === 'textarea' ?
        <textarea className={className} {...input} type={type} disabled={disabled}/>
        : type === 'select' ?
        <select className={className} {...input} type={type} disabled={disabled}>{this.props.children}</select>
        :
        <input className={className} {...input} type={type} disabled={disabled}/>
        }
        {touched && error && <span className="form-error">{error}</span>}
      </div>
    );
  }
};

RenderFieldコンポーネントを、Validationを指定して描画

従来通りのReduxFormを使いつつ、実装したvalidateメソッドをRenderFieldコンポーネントに適用する。

RenderFieldコンポーネント及び使用するvalidateメソッドをimportしておく。本記事ではそれぞれvalidation.jsxにまとめて記述している。

import { RenderField, required, maxLength, datetime } from '../../validation';

ニックネームの入力の場合、以下のように、Fieldコンポーネントのcomponent属性にRenderFieldを指定し、validate属性にvalidateメソッドの呼び出しを配列で記述する。

<FormGroup>
  <ControlLabel>ニックネーム</ControlLabel>
  <Field
    className="nickname"
    name="nickname"
    type="text"
    component={RenderField}
    disabled={this.props.isViewMode}
    validate={[
      (v) => required(v, 'ニックネームを入力してください'),
      (v) => maxLength(v, 16, 'ニックネームは16文字以下で入力してください')
    ]}
  />
</FormGroup>

バリデーションエラー時はsubmitできなくする

Buttonコンポーネント(react-bootstrap)のdisabled属性に、ReduxFormのinvalid属性をバインドする。本記事ではreact-bootstrap使ってるけど、多分標準のbuttonでも問題ない。

<Button type="submit" block disabled={this.props.invalid}>
  保存
</Button>

エラーメッセージを装飾する

エラーメッセージにはform-errorクラスが付与されるようにしているので、scssで装飾する。

.form-error {
  color: $theme-red;
  font-size: 80%;
  &::before {
    display: inline-block;
    font: normal normal normal 14px/1 FontAwesome;
    font-size: inherit;
    text-rendering: auto;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    content: '\F06A';
    margin-right: 5px;
  }
}

デモ(再掲)

gifは圧縮してるのでクリックで鮮明なバージョンを確認できます。

3年目プログラマが参考にしたQiita記事まとめ⑧

概要

特にカテゴリもレベルも偏らずにざっくばらんに、参考になったQiita記事を一言添えて列挙するシリーズ8本目

Qiitaまとめシリーズ

(翻訳)【GitHub公式】Gitコマンドチートシート

Github公式のチートシートを翻訳した記事。gitを使い始めた程度の人にはすぐに開けるようにしておくと便利かも。
個人的には今更な内容かなと思ったけど、恥ずかしながらgit mvを初めて知った。というか今までgit rmとgit addをわざわざやってた。こうやって初心に帰ると新しい発見があるんだなと実感。

プログラミングに関するあの名前の由来 #1 言語名その1

思ってたよりも商標の都合だったり、仮の名前をそのまま採用したりと適当な理由で名前付けした言語ばかり。本当に便利なものには名前なんてどうでもいいのかもしれない。

プログラミングに関するあの名前の由来 #2 言語名その2

Rubyに至っては、作者自身どころか同僚の誕生石から取ったなんて適当さ。本当に名前にコダワリはなかったのかな。Perlに合わせてって聞いたことあるけど。

「小説家になろう」の作品タイトルは、本当に長くなってるのか? Pandasで確認しよう!

まず「なろう」がAPIを提供してることに驚いた。ということはそれを用いたサービスがあるのかなと調べてみると、なろう小説を読むためのリーダーアプリとか結構あるみたい。なるほどなぁ、どこにでもそういうアイディアはあるものだ。

シンプルだけどシッカリ働く最新のECMAScript開発環境

確かに最小限だけど、モダン開発未経験の人がモダン開発に一歩踏み出すためにはこれでもまだ敷居が高く感じる。もっと手軽に始められるはなんだろう。

めんどくさいおじさんをブロックするプログラムを書いた

ツイプロAPIがこんなに使い勝手良いとは思わなかった。TwitterAPIだけだと、ユーザの検索がうまく出来なかったりするけど、これと組み合わせればかなり柔軟にできそう。

Google Home に話しかけるだけで、日記を書く仕組みを作ってみた

GoogleHomeってこんなにカスタマイズできるんだ。スマートスピーカーに興味はあるんだけど一歩踏み出せない。というか具体的にやりたいことが思いつかない。どこまで出来るのかが未知数なところもあるけど。

快眠ハーブティーの効果は本当にあるのか、ベイズ統計で調べてみた

ハーブティも気になるけど、記事中で登場した睡眠時間事細かに計測してくれるウェアラブル端末が気になる。。他のデバイスと連携できるようだし、色々楽しめそう。多分そういうサービスは沢山有るんだろうけど。

シェルスクリプトを自己文章化する

最初何言ってるのか全然わかんなかったけど、スクリプト内のコメントをスクリプト自身で参照してusage化してるのね。これは頭良いかも。特にサブコマンドが増えてきた時に、usageのメンテを効率的にできそうだ。

オブジェクト指向とは結局何なのか あるいはプログラミングをする上で気をつけるべきこと

これはわかりやすい!前に見た他の記事同様、オブジェクト指向の本質はカプセル化と捉えれてたけど、具体例がわかりやすくて理解しやすい記事だと思う。特にポリモーフィズムの例は犬猫みたいな残念すぎる例えじゃなく、より現実的で実用的な例で良かった。

LaravelでStdClassが使えない

概要

LaravelでstdClassを使うとして

PHP Error: Class ‘App\stdClass’ not found
と出たのでその解決策の備忘録

問題点

以下のようにstdClass使おうと思ったら

$hoge = new stdClass();

PHP Error: Class ‘App\stdClass’ not found
とエラーが出た

解決策

以下のようにする

$hoge = new \stdClass();

原因

当該コードがApp名前空間の中に居たので、App内でstdClassを探してしまったため

[Ruby] チャットワークからBacklogの課題を作成する(webhook/API)

概要

チャットワーク(chatwork)とBacklogのマッシュアップ。チャットワークにて、特定フォーマットに従ったテキストを発信することで、チャットワークのwebhookを経由してサーバで発言内容を受信し、Backlog APIを用いて課題を作成する。課題作成後は、チャットワークAPIを用いて、元のチャットルームに作成した課題URLを通知する。

テキストで説明するより以下のデモgifを見ると早い。

前提

以下環境で動作確認済み

debian 8.6
ruby 2.2.2
backlog_kit 0.16.0

以下の点にご注意ください

  • Webhookの仕組みに関する説明は割愛
  • サーバーサイドにはRubyの軽量フレームワークであるSinatraを用いているが、Sinatraに関する説明は割愛
  • BacklogAPIを用いて課題の作成を行うが、BacklogAPIに関する説明は以下記事に委譲する
    [Ruby] BacklogをCUIで操作してみる
  • チャットワークAPIを用いて通知を行うが、チャットワークAPIに関する説明は以下記事に委譲する
    チャットワークAPIをRubyで利用する

課題作成フォーマット

本記事では、以下のようなフォーマットのテキストを、webhookで設定したルーム内で発言した際に、連動してBacklogの課題が生成されるようにする。

@@課題登録 (課題タイトル)
(課題本文1行目)
(課題本文2行目)
(課題本文3行目)

一行目の@@のあとに、実行するコマンドを指定するイメージ。本記事では「課題登録」のみ実装。その後ろに課題のタイトルを、2行目以降に課題の本文を記述する。本課題の範囲では、タイトル/本文以外の各設定値を指定する手段は提供しない。

Webhookの登録

チャットワークのwebhookは、「アカウントイベント」と「ルームイベント」の何れかを適用できる。本記事では、ルームイベントとして、自身のマイチャットのルームIDを指定して登録する。

webhookの登録は、チャットワークにログイン後のAPI設定の画面で以下のように行える。

チャットワークのメッセージを受信する(Sinatra)

マイチャット内で発言すると、Webhook登録時に指定したWebhook URLに対して、メッセージ内容とそのメタデータがPOSTされる。今回はPOSTの受け口として、Rubyの軽量フレームワークであるSinatraを用いて実装する。Sinatraについては本記事の本筋ではないので割愛するが、雰囲気でコードの内容は把握できるようになっている。

Sinatraのコントローラにて、以下のようにPOST内容を受け取る。WebhookによるPOSTデータはJSONで届くので、parseして標準出力してみる。

post '/' do
  pp JSON.parse(request.body.read)
end

そしてチャットワークに以下のような投稿をしてみると

概ねリアルタイムに、以下のような標準出力が得られる

{"webhook_setting_id"=>"127",
 "webhook_event_type"=>"message_created",
 "webhook_event_time"=>1512109688,
 "webhook_event"=>
  {"message_id"=>"988329000694410240",
   "room_id"=>59200706,
   "account_id"=>2011143,
   "body"=>"こんにちは!",
   "send_time"=>1512109688,
   "update_time"=>0}}

本記事ではこのデータを用いて課題の作成を行う。

フォーマットに合致しない場合拒否する

POSTされたデータから、チャットワークのルームID(通知時に使う)と、メッセージ本文を取り出す。メッセージ本文に、フォーマットの必須要件である「@@課題登録」が含まれていない場合returnする。

# メッセージ内容を確認
event = JSON.parse(request.body.read)['webhook_event']
room_id = event['room_id'].to_s
msg     = event['body']
return unless msg.index('@@課題登録') == 0

正確にはこのタイミングで、ヘッダーをチェックして、確かにwebhookからのPOSTであることを確認しないと、不正に課題を量産されてしまうので注意

メッセージから課題タイトルと課題本文を抜き出す

メッセージは1行目と2行目以降で分かれているので、最初に改行毎に分割する。1行目は、スペース刻みでコマンド内容と課題タイトルに分かれているので、後半の課題タイトルの部分を抜き出す。2行目以降は課題本文なので、またjoinする。

# メッセージ内容から課題の情報を抜き出す
lines = msg.split("\n")
header = lines[0]
title  = header.split[1]
body   = lines[1..-1].join("\n")

Backlogに課題を作成する

Backlogに課題を作成するために、BacklogAPIを用いる。本ブログではお馴染みのgemである、backlog_kitを用いるが、それを含めたBacklogAPIの使い方については以下記事に委譲する。

[Ruby] BacklogをCUIで操作してみる

APIキーと、対象のBacklogスペースIDは事前に取得して環境変数に入れておく。

backlog = BacklogKit::Client.new(
  space_id: ENV['PRIVATE_BACKLOG_SPACE_ID'],
  api_key:  ENV['PRIVATE_BACKLOG_API_KEY']
)

create_issueメソッドを用いて課題を作成する。課題タイトルと課題本文はwebhookより受け取ったメッセージに含まれてる内容を使用。
「優先度」と、「分類」は必須なので、とりあえずここでは優先度:中と、分類:タスク に該当する数値を入れておく。

result = backlog.create_issue(title, {
  :projectId   => 38382,
  :priorityId  => 1,
  :issueTypeId => 172585,
  :description => body,
})

課題作成成功後のレスポンスに、生成した課題のURLが含まれているので、それを抑えておく。

issue_url = result.headers.location

登録した課題をチャットワークに通知する

最後に、前項で作成した課題のURLをチャットワークに通知する。チャットワークにメッセージを投稿するためにチャットワークAPIを用いるが、ここでは手前味噌だが以下のクラスを利用する。

chatwork-ruby/chatwork.rb at master · Sa2Knight/chatwork-ruby

上記クラスについての詳細は下記記事を参照。とりあえずsendMessageメソッドにルームIDとメッセージ本文を指定すればメッセージを送信できることがわかればよい。

チャットワークAPIをRubyで利用する

チャットワークのinfoタグ、titleタグを組み合わせて、いい感じのメッセージを投げる。

# 結果をチャットワークに投稿する
chatwork = Chatwork.new
chatwork.sendMessage(room_id, "[info][title]課題を作成しました[/title]#{issue_url}[/info]")

ソースコード

ここまでのコントローラ部の全ソースコードは以下の通り

post '/' do

  # メッセージ内容を確認
  event = JSON.parse(request.body.read)['webhook_event']
  room_id = event['room_id'].to_s
  msg     = event['body']
  return unless msg.index('@@課題登録') == 0

  # メッセージ内容から課題の情報を抜き出す
  lines = msg.split("\n")
  header = lines[0]
  title  = header.split[1]
  body   = lines[1..-1].join("\n")

  # Backlogに課題を作成する
  backlog = BacklogKit::Client.new(
    space_id: ENV['PRIVATE_BACKLOG_SPACE_ID'],
    api_key:  ENV['PRIVATE_BACKLOG_API_KEY']
  )
  result = backlog.create_issue(title, {
    :projectId   => 38382,
    :priorityId  => 1,
    :issueTypeId => 172585,
    :description => body,
  })
  issue_url = result.headers.location

  # 結果をチャットワークに投稿する
  chatwork = Chatwork.new
  chatwork.sendMessage(room_id, "[info][title]課題を作成しました[/title]#{issue_url}[/info]")

end

デモ(再掲)

所感

本記事では非常にシンプルな構成にしたが、以下のように拡張していけば使いみちがあるかも?

  • チャットワークのユーザとBacklogのユーザを関連付けて、チャットワークの発言者をBacklog課題の作成者にする
  • 複数のチャットルームでWebhookを登録し、チャットルームとBacklogプロジェクトを関連付けて対応したプロジェクトに課題を作成する
  • @@課題コメント/@@課題完了 などの課題を操作する各種コマンドを導入する

[jQuery] セレクトボックスの切り替えに応じて、次のセレクトボックスの選択肢を切り替える

概要

例えば、なんらかの大区分をセレクトボックスで選択すると、連動して大区分に対応した中区分のみが中区分用セレクトボックスで選択できるようになり、同様に中区分を選択すると対応する小区分が選択できるようになるアレを実装する。

上記説明がわかりづらすぎるのでgifにすると以下の感じ

パッと思いついた方法なのでもっと良い方法ありそう。というかjQueryじゃなくてReactやVueを使うべきだけど。

前提

以下環境で動作確認

OSX 10.12.6
GoogleChrome 62.0
jQuery 3.2.1

HTML

とりあえず大区分、中区分、小区分のselectを表示する。内容は動的に追加するので空で。
jQueryを使うのでCDNで読んでおく。

<html>
  <head>
  <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
  </head>
  <body>
    大区分 <select id="select_a"></select>
    中区分 <select id="select_b"></select>
    小区分 <select id="select_c"></select>
  </body>
</html>

JavaScript

ポイントは中区分、小区分は上位区分の選択に応じた選択肢のみをoptionタグで描画すること。

//
// 選択肢を全て事前に定義する
// 中区分は2次元配列
// 小区分は3次元配列
//
var a_options = ['区分1', '区分2'];
var b_options = [
  ['区分1-1', '区分1-2'],
  ['区分2-1', '区分2-2'],
];
var c_options = [
  [
    ['区分1-1-1', '区分1-1-2'],
    ['区分1-2-1', '区分1-2-2'],
  ],
  [
    ['区分2-1-1', '区分2-1-2'],
    ['区分2-2-1', '区分2-2-2'],
  ],
]
$(function() {

  //
  // select要素を予め取得しておく
  //
  var $select_a = $('#select_a');
  var $select_b = $('#select_b');
  var $select_c = $('#select_c');

  //
  // 大区分決定時に中区分に選択肢を設定
  //
  $select_a.change(function(e) {
    var selected_a = $select_a.val();
    $('#select_b option').remove();
    b_options[selected_a].forEach(function(option_b, idx) {
      var $option_tag = $('<option>').val(idx).text(option_b);
      $select_b.append($option_tag);
    });
    $select_b.change();
  });

  //
  // 中区分決定時に小区分の選択肢を設定
  //
  $select_b.change(function(e) {
    var selected_a = $select_a.val();
    var selected_b = $select_b.val();
    $('#select_c option').remove();
    c_options[selected_a][selected_b].forEach(function(option_c, idx) {
      var $option_tag = $('<option>').val(idx).text(option_c);
      $select_c.append($option_tag);
    });
  });

  //
  // 大区分は固定なので最初に選択肢に追加する
  //
  a_options.forEach(function(option_a, idx) {
    var $option_tag = $('<option>').val(idx).text(option_a);
    $select_a.append($option_tag);
    $select_a.change();
  });

});

チャットワークにソースコードを掲載する

概要

チャットワーク(Chatwork)に、ソースコードを綺麗に表示する方法の備忘録。基本的にcodeタグを使うが、裏技っぽい記法で長文のコードを折りたたんで掲載する方法についても触れる。

前提

2017/11/28時点のチャットワークの機能を利用。そのうち改善されるかも。

codeタグでソースコードを表示

対象のソースコードをcodeタグで囲んで投稿する。

インデントが保持されたまま投稿される。本来は絵文字に展開されるような記号もcodeで囲んだ場合はちゃんと表示されるらしい。

titleタグとcodeタグでそれっぽくする

申し訳程度だがコードにタイトルをつける

申し訳程度にタイトルが表示される

長いコードを折りたたんで表示

基本的に現状のチャットワークでは長いコードを折りたたんで表示する方法は無い

が、強引にやるとすれば以下のような裏技がある。
Taskコマンドを使うことで、擬似的にチャットワークのタスクに関するフォーマットを使用することができるので、ここに長いソースコードを掲載すると

このように折りたたまれて表示される。といってもcodeタグは使用していないので、絵文字記号はそのまま絵文字に展開され、実用的なものではない。

フォーマットは以下の通り。

[task aid=000000 st=done lt=0000000000]
ソースコード
[/task]

備考

  • コードの折りたたみもできなければ、シンタックスハイライトも無いので、チャットワークではソースコードを共有するのに向いてないと思われる
  • プログラマ的には、その辺の機能が充実してるSlackのほうが遥かに適してる

関連

[Chrome] チャットワークをChatwork Extension他で使いやすくする のように、ブラウザの拡張機能でシンタックスハイライトをつけたりはできる。が、チャットワーク側の仕様変更の影響を受けるので、公式に機能追加されて欲しい。

参考