つよく、やさしく、泥臭く生きていくブログ

日常とポエムと、ときどき技術

LINE bot

lambda

googleカレンダー

というのを作った。

とりあえず動くだけなので、

定型でLINEに投稿するとカレンダーに登録されて、

「予定」と投稿すると予定一覧を返してくれる。

使い勝手はまだない。

 

LINE botの使い方、googleカレンダーAPIの使い方、lambdaのAPIゲートウェイとかの使い方

あたりを、整理してまとめて書きたい。

という決意

EC2とS3の連携

lambdaにs3からデプロイするために、ソースをec2上からs3へ上げたい。

github -> EC2上にcloneしてくる -> 変更したりする -> s3に上げる -> lambdaで、S3からデプロイする


そのためにまず、EC2からS3にアクセスできるようにしたい。

・S3へのアクセスが可能なロールを作る

・そのロールを、EC2につける

Amazon EC2 インスタンスから Amazon S3 バケットに接続する

 

$ s3 aws ls s3://バケット

で確認 

 

Unityでゲーム作ったのともろもろ

ちょっと暇があったので、Unityを触ってみてゲーム作った。

まず普通の2Dのブロック崩しチュートリアル的なものをやった後、それを改造して3Dなものにした。 2Dから3Dにすることで、2Dではほとんど気にしなかったことを追加で学習できてよい

2Dのチュートリアルはこのページを見ながらやった。
【Unity】ブロック崩し(3D)の制作手順を解説!反射角度・速度調整・クリア判定まで – XR-Hub

3Dは適宜ググりながらいろいろ見てやった。

あと、Unityのすごいところは、簡単に、ブラウザで動くようにビルドできること。しかもそれをgithubページでそのまま公開できちゃうってこと。
ブラウザで遊べるってのは強くて、ツイッターで公開してすぐに遊んでもらえる。感動。

ビルドとgithubページでの公開はこれを参考にそのまんまできた。
【Unity】作成したゲームをWebで公開する方法 - Qiita

2Dで学べる事

  • 物体の作り方
  • 剛体が勝手に動くようにすること
  • キー入力で物体を動かせること
  • 当たり判定、当たった時の処理
  • プレハブ
  • etc.

3Dで学べる事

  • 光と影
  • 物体の回転
  • カメラの回転
  • カメラに合わせた物体の移動
  • Ray ( 邪魔になる壁を透過するとかに使う )
  • etc.

困ったことと解決した方法を書いておく

箱の面の、光源と逆側が真っ暗

逆向きの光源を一つ追加して解決。多分ほかにやりようがある気がする。例えば箱の内側の面がそれぞれ発光するとか。
また、光源にはこういう種類がある。

  • めちゃ遠い位置から均一に降り注ぐ太陽みたいな光源 (デフォルトの光源がこれ。向きはあるが位置には意味ない。)
  • ランプのようにある位置から全方向に広がる光源
  • スポットライトのようにある位置からある方向へ円錐型に広がる光源
  • 物体そのものが発光する光源

影が出るけど離れると出ない

編集中は台の影が床に出てるのに、ゲーム画面になると影がでない。編集中でもカメラを遠くすると出ないということがあった。 これは負荷軽減のために遠い影を描画しないからだそうだ。
Edit > Project Settings で、Quality の Shadow Distance の値を大きくすることで解決 f:id:yukinea:20200906153129p:plain

あと、物体はそれぞれに

  • 光をどう遮るか: Inspectorタブ> Mesh Renderer > Lighting > のCast Shadows
  • 影を受けるか: Inspectorタブ> Mesh Renderer > Lighting > のReceive Shadows

という設定がある。そもそも影が出ないとかってときはこの辺をちゃんと設定する。

プレハブで作成したブロックが、ボールはぶつかるのに消えない

プレハブ化したブロックを、ゲームオブジェクトに設定したスクリプトで出現させるようにしたところ、ぶつかっても消えない最強のブロックができてしまった。 当たればボールは跳ね返るので当たり判定はあるっぽい。なぜだといろいろ悩んだ結果、 原因は、プレハブ側にスクリプトを設定していなかったこと。2つのスクリプトが必要。

  • 「プレハブから作るという動き」はあるゲームオブジェクトにスクリプトを設定する
  • 「当たったら消えるという動き」はそのプレハブにスクリプトを設定する

プレハブで作るボールが無限増殖する

プレハブの方に、「プレハブから作るという動き」のスクリプトを設定してしまっていたため。 これのせいで、プレハブから作られたオブジェクトがプレハブから作りそのオブジェクトがプレハブからオブジェクトを作り...という無限増殖になってしまっていた。

バーの移動とカメラ移動

これを主に参考にした
Unityで3D空間内をTPSっぽくWASD移動するキャラをつくる - ささみ雑記帳

メインカメラが取得できない

メインカメラオブジェクトは、 Camera.main.transform みたいにして簡単に取得できるはずなのに、The name 'Camera' does not exist in the current context みたいなエラーがでて困ってた。 原因は、Camera.csっていうスクリプトを作ってて Camera っていうクラスを自分で作ってしまっていてそっちを参照されていたからっぽい。 名前を変えるか、 UnityEngine.Camera.main みたいにするとよいらしい。

手前の壁を透過したい

舞台が壁に囲まれた箱の中なので、カメラがその外に居るので中が見えない。 なので普通の考えとして、手前側になる壁は消えてほしい。 カメラを横移動して視点を変えることもできるようにしているので、その時その時で手前の壁が消えてほしいし奥の壁は表示されてほしい。 Unity カメラ 壁 透過 とかでいろいろググった結果こうなった。

    void Update()
    {
        // 一旦、壁を全部表示する
        GameObject[] transparentWalls = GameObject.FindGameObjectsWithTag("transparentWall"); // 透過したい側面の横壁にはタグをつけてある
        foreach( var wall in transparentWalls)
        {
            wall.GetComponent<Collider>().gameObject.GetComponent<Renderer>().enabled = true;
        }

        // 床面のオブジェクトを取得
        GameObject underWall = GameObject.Find("UnderWall");
        // 床面の4隅の座標を取得
        Vector3 underWall_pos = underWall.transform.position;
        Vector3 underWall_size = underWall.GetComponent<Renderer>().bounds.size;
        //Debug.Log("Ray Hit");

        // 中心点とサイズから四隅を計算
        ArrayList vectors = new ArrayList();
        vectors.Add( new Vector3(
            underWall_pos.x - (underWall_size / 2).x,
            underWall_pos.y,
            underWall_pos.z + (underWall_size / 2).z
        ));
        vectors.Add( new Vector3(
            underWall_pos.x + (underWall_size / 2).x,
            underWall_pos.y,
            underWall_pos.z + (underWall_size / 2).z
        ));
        vectors.Add( new Vector3(
            underWall_pos.x - (underWall_size / 2).x,
            underWall_pos.y,
            underWall_pos.z - (underWall_size / 2).z
        ));
        vectors.Add( new Vector3(
            underWall_pos.x + (underWall_size / 2).x,
            underWall_pos.y,
            underWall_pos.z - (underWall_size / 2).z
        ));

        // 四隅それぞれの座標とカメラを結ぶRayを作成
        RaycastHit hit;
        foreach (Vector3 vector in vectors)
        {
            Vector3 difference = (vector - this.transform.position);
            Vector3 direction = difference.normalized;
            Ray ray = new Ray(this.transform.position, direction);

            // 横壁とぶつかった場合表示を消す
            if (Physics.Raycast(ray, out hit))
            {
                GameObject hitObject = hit.collider.gameObject;
                Renderer renderer = hitObject.GetComponent<Renderer>();
                if (hitObject.tag == "transparentWall" && _renderer != null)
                {
                    renderer.enabled = false;
                }

            }
        }
    }

参考にした: Unity でカメラと被写体の間の遮蔽物を非表示にするスクリプトを作ってみた - Qiita

Rayというのは、ある座標からある座標に仮想的なレーザービームを打って、途中で遮られたかどうかと遮った物体が何かを取得できる機能。 通常はカメラからある座標へRayを飛ばすらしいが、座標から座標へ飛ばせる模様。
これを使って、床面の四隅とカメラを結ぶ線の間に壁があればそれを消すようにした。
四隅が見えているかどうか、とすることで思ってる通りの見せ方ができるようになった。
もっといいやり方ないのかな。

BGMのループ

ちなみにBGMは自作です。
で、ループ仕様なのだけどイントロ+ループ部分なBGMなのでどうしたらいいのかなってところは
https://tsumikiseisaku.com/blog/how-to-make-loop-sound/
をそのまま参考にしてできた。 waveファイルにはsmplチャンクという、ループ部分を指定するものがある。

効果音がちっさい

ボールが壁などにぶつかったらボールから音が出るのだけど、カメラから遠くて音が小さい。 通常、メインカメラにマイクがある想定となっており、これによりカメラと物体の距離に応じて遠くから聞こえたり左右から聞こえたりができる。
でも今回は距離感いらないので、ボールの座標から音を出すのではなく、カメラの座標から音を出すようにした。
これは簡単で、鳴らすコードの第2引数が鳴る座標なのでこれをカメラの座標を指定する。 AudioSource.PlayClipAtPoint(sound, m_camera.transform.position);

WebGLにしたら日本語が表示されない

デフォルトが日本語を含まないフォントで、かつ、表示できないときに別のフォントを使おうとしないため。
参考にした: 【Unity】WebGLで日本語テキストが表示されない問題について | ちりつもぶろぐ

Acmeモジュールを書いた

Acmeデビューしました

metacpan.org

Acme::KemonoFriends::Color というモジュールです。
printkという関数をエクスポートします。printkを使うと、けものフレンズっぽい色でprintします。

SYNOPSISに書いてあるとおりこんな感じに書くと、

use Acme::KemonoFriends::Color;
use utf8;

printk('hogehoge');

$ perl script.pl

f:id:yukinea:20190305014904j:plain
たーのしー!

ターミナルが256色表示に対応している必要があります。
Tera Termでは 設定->ウィンドウ から、「256色モード」のチェックボックスがあると思います。

どんな色が使えるのかは、
https://misc.flogisoft.com/bash/tip_colors_and_formatting
このページが見やすかったです。
このページと、けもフレ公式ページのロゴとを見比べて一番近いっぽい色を選びました。

本当はprint関数をオーバーライドするようにして、useするだけでprintが色付くみたいにしたかったんですけど、print関数ってオーバーライドできない(?)んですね。

雑に確認したところ同じようなものは無かったけど、なんかすごく既にありそうで怖い。

参考にしたページ http://gihyo.jp/dev/serial/01/perl-hackers-hub/005001

Mojolicious::LiteとDBIx::Classを使って簡単なwebアプリケーションを作る

  • 社内の勉強会で、「PerlでDBを扱う」というのを教える必要があったので、その時の資料の抜粋を書き留めておく。

  • 内容としては、

    • DBIx::Classの基本的な使い方をさらった後、
    • Mojolicious::Liteで簡単な書籍管理アプリを作り、DB操作を盛り込むというハンズオン。
  • 後半にある「Webアプリ上に実現してみよう」を順にこなすとwebアプリケーションができあがります。
  • 詳しく動作を理解するとか実践的な内容というよりも、Perlでこういうことがサクッとできますよ、という紹介です。
  • 弊社のPerl 人口は風前の灯である。

  • 想定対象者としては、Perl初心者、プログラミング初心者で、Perlの基本文法は(一応)分かり、(調べながらでも)fizzbuzz問題は書けるくらいの人

  • また、Mojolicious::Liteによるwebアプリを作るので、Perl入学式の第5回資料をさらった人。(弊社勉強会ではこれの前段階でPerl入学式の第5回資料を実施しました。)

github.com

注意

  • 勉強会のための方便的な記述や雑な記述があります
  • 足りないところは喋りで解説

準備

$ cpanm -l ~/extlib local::lib
$ perl -I ~/extlib/lib/perl5 -Mlocal::lib=~/extlib | tee -a ~/.bash_profile
$ exec $SHELL -l

$ cpanm Mojolicious
$ cpanm DBIx::Class
$ cpanm DBD::SQLite
$ cpanm DBIx::Class::Schema::Loader

DB (SQLite)の初期準備

  • DBにはいくつかの種類がありますが、今回は手軽さのためSQLiteを使います。
  • SQLiteはデータ保存先として単一のファイルを使います。
$ sqlite3 sqlite.db

# booksという名前のテーブル作成
sqlite> CREATE TABLE books(id INTEGER PRIMARY KEY AUTOINCREMENT, author text, title text, price integer);

# 適当にレコードを入れる
sqlite> INSERT INTO books (author, title, price) VALUES ('Dazai Osamu', 'Hashire merosu', '1111');
sqlite> INSERT INTO books (author, title, price) VALUES ('Dazai Osamu', 'Ningen shikkaku', '2222');
sqlite> INSERT INTO books (author, title, price) VALUES ('Miyazawa Kenji', 'Ginga tetsudo no yoru', '1234');

# SELCT結果を見やすく整形する
sqlite> .header ON
sqlite> .mode column

# SELECTしてみる
sqlite> select * from books;
id          author       title           price
----------  -----------  --------------  ----------
1           Dazai Osamu  Hashire merosu  1111
2           Dazai Osamu  Ningen shikkak  2222
3           Miyazawa Ke  Ginga tetsudo   1234

DB操作

スキーマ情報を取得する

  • 参考:

  • DB操作といえばSQL文が一般的かと思います。

  • Perlでも、SQL文をコードに書いてDB操作を行うことができます。
  • ただし今回使うDBIx::Classは、ORM(O/Rマッパー)と呼ばれるもので、SQL文を書かずに使えます。
  • DB操作をオブジェクトのように扱えます。
  • DBIx::Classは、事前に「データベース」に対応したクラス(スキーマクラス)や、「テーブル」に対応したクラスを用意しておき、プログラムではそいつらを通して DB にアクセスします。
  • このスキーマクラスは、既存のDBから自動で作成することができます。
スキーマ作成するためのスクリプト
  • これはDBに変更がなければ初回に一回やるだけでよいものです
以下を make_schema.pl として保存する
#!/usr/bin/perl
use strict;
use warnings;
use FindBin;
use File::Spec;
use DBIx::Class::Schema::Loader qw/make_schema_at/;

make_schema_at(
    'MyDB::Schema',
    {
        # 出力先のディレクトリ
        dump_directory => File::Spec->catfile( $FindBin::Bin ),
        really_erase_my_files => 1,
        debug => 1,
    },
    [
        'dbi:SQLite:dbname=sqlite.db',
        {
            on_connect_do => ['SET NAMES utf8'],
        }
    ]
);
$ perl make_schema.pl
これで自分のディレクトリにMyDBというディレクトリができてその中にいろいろできていればOK

DBに接続する

  • DBに接続してDBの情報を取得してみましょう
  • 以下を db.pl として保存してください
#!/usr/bin/perl
use strict;
use warnings;

use MyDB::Schema;

# スキーマクラスのインスタンスを作成
my $schema = MyDB::Schema->connect(
    "DBI:SQLite:dbname=sqlite.db"
);

# ResultSet を作成し全レコード取得
my $rs = $schema->resultset('Book')->search;


# イテレートして出力
while (my $row = $rs->next) {
    printf "%s : %s : %s" , $row->id , $row->author, $row->title;
    print "\n";
}

検索 (search)

  • search で、authorを検索
  • 先ほどsearchしていた箇所をこのように変えてみましょう
# author がDazai Osamu のものを検索
my $rs = $schema->resultset('Book');

my $result=$rs->search(
    {
        author => 'Dazai Osamu',
    }
);
  • ライク検索
# author がDazai何とか のものを検索
my $rs = $schema->resultset('Book');

my $result=$rs->search(
    {
        author => { like => "%Dazai%" },
    }
);
  • OR検索
# author がMitazawa何とか or タイトルがNingen shikkakuのものを検索
my $rs = $schema->resultset('Book');

my $result=$rs->search(
    {
        -or => {
            author => { like => "Miyazawa%" },
            title  => "Ningen shikkaku",
        },
    }
);
  • AND検索
# author がDazai何とかで、タイトルがNingen何とかのものを検索
my $rs = $schema->resultset('Book');

my $result=$rs->search(
    {
        -and => {
            author => { like => "Dazai%" },
            title  => { like => "Ningen%"},
        },
    }
);
  • ANDとORの組み合わせ
# ちょっと複雑な検索条件
# (authorがDazai何とか かつ titleがNingenなんとか ) または authorがMiyazawa Kenji  を検索
my $rs = $schema->resultset('Book');

my $result=$rs->search(
    {
        -or => {
            -and => {
                author => { like => "Dazai%" },
                title  => { like => "Ningen%"},
            },
            author => 'Miyazawa Kenji',
        },
    },
);
  • ORDER BY (並び替え)
# 全件検索して、値段順に並び変える
my $rs = $schema->resultset('Book');

my $result=$rs->search(
    {
        # 検索条件は空 なので 全件取得
    },
    {
        # オプションを付けられる。order_byは順序
        order_by => ['price'],
    }
);

# 出力のところで値段も出すようにしましょう
while (my $row = $result->next) {
    printf "%s : %s : %s : %s" , $row->id , $row->author, $row->title, $row->price;
    print "\n";
}

行追加、変更

  • CREATE (追加)
my $rs = $schema->resultset('Book');

$rs->create(
    {
        author => 'Matayoshi',
        title  => 'Hibana',
        price  => 999,
    }
);

my $result = $rs->search();

# イテレートして出力
while (my $row = $result->next) {
    printf "%s : %s : %s : %s" , $row->id , $row->author, $row->title, $row->price;
    print "\n";
}
  • UPDATE
my $rs = $schema->resultset('Book');

# 変更対象のレコードを取得して
my $id4 = $rs->search(
    { id => 4 }
);

# そいつをアップデート
$id4->update(
    {
        title => 'Kinikuman',
        author => 'Yudetamago'
    }
);

my $result = $rs->search();
# イテレートして出力
while (my $row = $result->next) {
    printf "%s : %s : %s : %s" , $row->id , $row->author, $row->title, $row->price;
    print "\n";
}
  • メソッドチェーンのようにつなげられる
my $rs = $schema->resultset('Book');

$rs->search(
    { id => 4 }
)->update(
    {
        title => 'Kinikuman',
        author => 'Yudetamago'
    }
);

my $result = $rs->search();
  • 複数レコードをまとめて処理することも可能
# Dazai Osamu のレコード全てpriceを変更
$rs->search(
    {
        author => 'Dazai Osamu',    # Dazaiのレコードは2つある
    }
)->update(
    {
        price  => 99999
    }
);
  • イテレートする中でupdateしたりもできる
my $rs = $schema->resultset('Book');

my $result = $rs->search();
# イテレートして、それぞれupdateして、出力
while (my $row = $result->next) {

    my $price = $row->price;
    $price = $price * 3;    # 現在の値段の3倍をとって

    # その行を更新する
    $row->update(
        {
            price => $price,
        }
    );

    printf "%s : %s : %s : %s" , $row->id , $row->author, $row->title, $row->price;
    print "\n";
}

行削除

  • DELETE
my $rs = $schema->resultset('Book');

$rs->search(
    { id => 5 }
)->delete();

Webアプリ上に実現してみよう

  • DB操作を盛り込んだWebアプリを作ってみましょう。
  • 簡単な書籍管理アプリです。
    • 一覧の表示
    • 検索した結果の表示
    • 書籍情報の追加
    • 変更、削除
  • Mojolicious::Liteを使って作ります。

Mojolicious::Liteの準備

  • ひな形作成
$ mojo generate lite_app books_list.pl

一覧表示

  • /にアクセスされたときに、booksテーブルの中身を全部表示します。
  • use MyDB::Schema;を追加
  • get / の中身を変更
  • テンプレートの変更
    • index.html.epの変更
    • default.html.epで、bootstrapを使うように追記

GET /

#!/usr/bin/env perl
use Mojolicious::Lite;
use MyDB::Schema;

get '/' => sub {
  my $c = shift;

  my $schema = MyDB::Schema->connect(
      "DBI:SQLite:dbname=sqlite.db",
  );

  my $rs = $schema->resultset('Book');
  my $result = $rs->search();

  $c->stash(result => $result);
  $c->render(template => 'index');
};

app->start;
  • まずは、コントローラの中身を変更します。
  • テーブルの全レコードを取得して、stashを使い、テンプレートへ渡します。
@@ index.html.ep
% layout 'default';
% title 'Books List';

<div class="container">
  <div class="row border-bottom border-dark bg-light">
    <div class="col-sm"> id     </div>
    <div class="col-sm"> author </div>
    <div class="col-sm"> title  </div>
    <div class="col-sm"> price  </div>
  </div>

% while (my $row = $result->next) {
  <div class="row border-bottom">
    <div class="col-sm"> <%= $row->id     %> </div>
    <div class="col-sm"> <%= $row->author %> </div>
    <div class="col-sm"> <%= $row->title  %> </div>
    <div class="col-sm"> <%= $row->price  %> </div>
  </div>
% }

</div>

@@ layouts/default.html.ep
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  </head>
  <body><%= content %></body>
</html>
  • index.html.epと,default.html.epをそれぞれ変更します。
  • indexでは、コントローラから渡された$resultを使いながらhtmlを記述しています。
  • defaultでは、CSSフレームワークのbootstrapを読み込むようにしています。
    • これで見た目をサクッときれいにしちゃいます。
  • ここまで出来たら、morboを起動して表示を確認しましょう。
$ morbo books_list.pl

検索

  • フォームを作り、入力された値を使って検索した結果をテンプレートに渡します

post /search

post '/' => sub {
  my $c = shift;

  # POSTされた値を取る
  my $author = $c->param('author');
  my $title  = $c->param('title');

  # POSTされた値をもとに、検索条件を作る
  my $query = {};
  if ( $author ) {
      $query->{author} = { like => "%$author%"};
  }
  if ( $title ) {
      $query->{title} = { like => "%$title%"};
  }

  my $schema = MyDB::Schema->connect(
      "DBI:SQLite:dbname=sqlite.db",
  );

  # 検索する
  my $rs = $schema->resultset('Book');
  my $result = $rs->search(
      {
          -and => $query,
      }
  );

  $c->stash(result => $result);
  $c->render(template => 'index');

};
  • post '/' を新たに追記します。
  • schemaをとるところがgetで書いたものと冗長ですね。後で直します。
%= form_for '/' => method => 'POST' => begin
  <div class="col-md-3">
    著者名 <%= text_field 'author' , class => 'form-control' %>
  </div>
  <div class="col-md-3">
    作品名 <%= text_field 'title' , class => 'form-control' %>
  </div>
  %= submit_button '検索' , class => 'btn btn-primary mt-3'
% end
  • 先ほどのindex.html.epに、フォーム部分を追記します。
  • form_forといった機能を使っていますが、普通に
    タグなどで書いてもかまいません。
  • ここまで書けたら、morboを起動し、検索を実行してみましょう。

サブルーチンへの切り出し

#!/usr/bin/env perl
use Mojolicious::Lite;
use MyDB::Schema;

sub get_schema {
  return MyDB::Schema->connect(
      "DBI:SQLite:dbname=sqlite.db",
  );
}

sub get_book_rs {
    return get_schema()->resultset('Book');
}

get '/' => sub {
  my $c = shift;

  my $rs = get_book_rs();
  my $result = $rs->search();
  $c->stash(result => $result);

  $c->render(template => 'index');
};

post '/' => sub {
  my $c = shift;

  my $author = $c->param('author');
  my $title  = $c->param('title');

  my $query = {};
  if ( $author ) {
      $query->{author} = { like => "%$author%"};
  }
  if ( $title ) {
      $query->{title} = { like => "%$title%"};
  }

  my $rs = get_book_rs();
  my $result = $rs->search(
      {
          -and => $query,
      }
  );
  $c->stash(result => $result);
  $c->render(template => 'index');
};

app->start;
  • get と postでどちらも schemaを取るところは同じなので、サブルーチンにまとめます。

書籍追加機能

  • 追加するためのページを一枚用意します

GET /add

get '/add' => sub {
  my $c = shift;
  $c->render(template => 'add');
};
中略
  %= submit_button '検索' , class => 'btn btn-primary mt-3'
% end
<a class="btn btn-warning" href="<%= url_for('/add') %>">新規追加</a>

@@ add.html.ep
% layout 'default';
% title 'Books List add';

%= form_for '/add' => method => 'POST' => begin
  <div class="col-md-3">
    著者名 <%= text_field 'author' , class => 'form-control' %>
  </div>
  <div class="col-md-3">
    作品名 <%= text_field 'title' , class => 'form-control' %>
  </div>
  <div class="col-md-3">
    価格 <%= text_field 'price' , class => 'form-control' %>
  </div>
  %= submit_button '追加する' , class => 'btn btn-primary mt-3'
% end
  • 検索フォームのすぐ下に、新規追加画面へのリンクを置きます。
  • add.html.epを追記します。
    • フォームで追加ボタンを押すと、/add へPOSTします

POST /add

get '/add' => sub {
  my $c = shift;
  $c->render(template => 'add');
};

post '/add' => sub {
  my $c = shift;

  my $author = $c->param('author');
  my $title  = $c->param('title');
  my $price  = $c->param('price');

  # 本来ならこの辺で入力値のチェックをするべき

  my $query = {
      author => $author,
      title => $title,
      price => $price,
  };

  my $rs = book_rs();
  $rs->create( $query );

  $c->redirect_to('/');
};
  • 新規追加でPOSTされてきたときに、DBへ追加します。
  • 追加処理だけしたあとは、index(/)へリダイレクトします、
  • 本来は、入力された値は妥当かどうかをチェックし、妥当でなければその旨を表示させるのが一般的です。

日本語対応

  • SQLiteに日本語が入っていて文字化けする場合、このようにする
use Encode qw( decode );

# テンプレート内で使えるhelperという関数を定義する
helper decode_utf8 => sub {
    my ($self,$string) = @_;
    my $string_utf8 = decode ('utf-8', $string);
    return $string_utf8;
};

中略
テンプレート内で、DBの内容で日本語がありうるところにdecode_utf8を入れる
    <div class="col-sm"> <%= decode_utf8( $row->author ) %> </div>
    <div class="col-sm"> <%= decode_utf8( $row->title )  %> </div>

変更

GET /edit/:id

get '/edit/:id' => sub {
  my $c = shift;
  my $id = $c->param('id');

  my $rs = get_book_rs();
  my $result = $rs->search(
      { id => $id }
  )->first;

  $c->stash(row => $result);
  $c->render(template => 'edit');
};
  • get '/edit/:id' を追加します。
  • /edit/:id の「:id」はプレースホルダと呼ばれるもので、URLの一部を取得することができます。
  • 取得した部分は、 my $id = $c->param('id'); で取り出します。
<div class="container">
  <div class="row border-bottom border-dark bg-light">
    <div class="col-sm">        </div>
    <div class="col-sm"> id     </div>
    <div class="col-sm"> author </div>
    <div class="col-sm"> title  </div>
    <div class="col-sm"> price  </div>
  </div>

% while ( my $row = $result->next ) {
  <div class="row border-bottom">
    <a class="btn btn-light" href="<%= url_for('/edit/' . $row->id) %>">変更</a>
    <div class="col-sm"> <%= $row->id    %> </div>
    <div class="col-sm"> <%= decode_utf8( $row->author ) %> </div>
    <div class="col-sm"> <%= decode_utf8( $row->title )  %> </div>
    <div class="col-sm"> <%= $row->price  %> </div>
  </div>
% }

</div>
@@ edit.html.ep
% layout 'default';
% title 'Books List edit';

%= form_for '/edit/'. $row->id => method => 'POST' => begin
  <div class="col-md-3">
    著者名
    <%= text_field 'author' => decode_utf8($row->author) , class => 'form-control' %>
  </div>
  <div class="col-md-3">
    作品名
    <%= text_field 'title'  => decode_utf8($row->title) , class => 'form-control' %>
  </div>
  <div class="col-md-3">
    価格
    <%= text_field 'price' => $row->price , class => 'form-control' %>
  </div>
  %= submit_button '変更する' , class => 'btn btn-primary mt-3'
% end
  • index.html.ep に変更ボタンを付けます。
  • 表示されるボタンのURLに注目してください。
  • また、edit.html.epテンプレートを作成します。

POST /edit/:id

post '/edit/:id' => sub {
  my $c = shift;
  my $id = $c->param('id');

  my $author = $c->param('author');
  my $title  = $c->param('title');
  my $price  = $c->param('price');

  # 本来ならこの辺で入力値のチェックをするべき

  my $query = {
      author => $author,
      title => $title,
      price => $price,
  };

  # 指定したid のレコードを更新
  my $rs = get_book_rs();
  my $result = $rs->search(
      { id => $id }
  )->update( $query );

  $c->redirect_to('/');
};
  • post '/edit/:id' を追加します。
  • 受け取った値で、レコードを更新します
  • 更新処理した後は、indexページへリダイレクトします。

削除

POST /delete/:id

post '/delete/:id' => sub {
  my $c = shift;
  my $id = $c->param('id');
  my $rs = get_book_rs();
  my $result = $rs->search(
      { id => $id }
  )->delete;

  $c->redirect_to('/');
};
  • post '/delete/:id' を追加する
  • delete処理をした後はindexページへリダイレクトします
@@ edit.html.ep
% layout 'default';
% title 'Books List edit';

%= form_for '/edit/'. $row->id => method => 'POST' => begin
  <div class="col-md-3">
    著者名
    <%= text_field 'author' => decode_utf8($row->author) , class => 'form-control' %>
  </div>
  <div class="col-md-3">
    作品名
    <%= text_field 'title'  => decode_utf8($row->title) , class => 'form-control' %>
  </div>
  <div class="col-md-3">
    価格
    <%= text_field 'price' => $row->price , class => 'form-control' %>
  </div>
  %= submit_button '変更する' , class => 'btn btn-primary mt-3'
% end

%= form_for '/delete/'. $row->id => method => 'POST' => begin
  %= submit_button '削除する' , class => 'btn btn-danger mt-3'
% end
  • editテンプレート内に、削除ボタンを追記します。

参考

  • 今回作成の最終的なアプリ
    books_list.pl
#!/usr/bin/env perl
use Mojolicious::Lite;
use MyDB::Schema;
use Encode qw ( encode decode ); 

sub get_schema {
  return MyDB::Schema->connect(
      "DBI:SQLite:dbname=sqlite.db",
  );
}

sub get_book_rs {
    return get_schema()->resultset('Book');
}

# テンプレート内で簡単に使える関数を定義できる
helper decode_utf8 => sub {
    my ($self,$string) = @_;
    my $string_utf8 = decode ('utf-8', $string);
    return $string_utf8;
};

# /にGETアクセスされたときの処理
get '/' => sub {
  my $c = shift;

  my $rs = get_book_rs();
  my $result = $rs->search();
  $c->stash(result => $result);

  $c->render(template => 'index');
};

# /にPOSTアクセスされたときの処理
post '/' => sub {
  my $c = shift;

  my $author = $c->param('author');
  my $title  = $c->param('title');

  my $query = {};
  if ( $author ) {
      $query->{author} = { like => "%$author%"};
  }
  if ( $title ) {
      $query->{title} = { like => "%$title%"};
  }

  my $rs = get_book_rs();
  my $result = $rs->search(
      {
          -and => $query,
      }
  );
 # 上記は変数展開されるとこうなる
#    {
#        -and => {
#            author => { like => "%$author%"},    # ここと
#            title     => { like => "%$title%"}   # ここが $queryの中身
#        }
#    }

  $c->stash(result => $result);
  $c->render(template => 'index');
};

get '/add' => sub {
  my $c = shift;
  $c->render(template => 'add');
};

post '/add' => sub {
  my $c = shift;

  my $author = $c->param('author');
  my $title  = $c->param('title');
  my $price  = $c->param('price');

  # 本来ならこの辺で入力値のチェックをするべき

  my $query = {
      author => $author,
      title => $title,
      price => $price,
  };

  my $rs = get_book_rs();
  $rs->create( $query );

  # DBに入れる処理だけして
  # TOPページに飛ばす
  $c->redirect_to('/');
};


# /edit/:hoge/ という「:hoge」のような書き方をすると、URLの一部を取得できる
get '/edit/:id' => sub {
  my $c = shift;
  my $id = $c->param('id');    # http://x.x.x.x:3000/edit/12345  だと、12345が取れる

  my $rs = get_book_rs();
  my $result = $rs->search(
      { id => $id }
  )->first;

  $c->stash(row => $result);
  $c->render(template => 'edit');
};

post '/edit/:id' => sub {
  my $c = shift;
  my $id = $c->param('id');

  my $author = $c->param('author');
  my $title  = $c->param('title');
  my $price  = $c->param('price');

  # 本来ならこの辺で入力値のチェックをするべき

  my $query = {
      author => $author,
      title => $title,
      price => $price,
  };

  # 指定したid のレコードを更新
  my $rs = get_book_rs();
  my $result = $rs->search(
      { id => $id }
  )->update( $query );

  $c->redirect_to('/');
};

post '/delete/:id' => sub {
  my $c = shift;
  my $id = $c->param('id');
  my $rs = get_book_rs();
  my $result = $rs->search(
      { id => $id }
  )->delete;

  $c->redirect_to('/');
};

app->start;

__DATA__

@@ index.html.ep
% layout 'default';
% title 'Books List';

%= form_for '/' => method => 'POST' => begin
  <div class="col-md-3">
    著者名 <%= text_field 'author' , class => 'form-control' %>
  </div>
  <div class="col-md-3">
    作品名 <%= text_field 'title' , class => 'form-control' %>
  </div>
  %= submit_button '検索' , class => 'btn btn-primary mt-3'
% end
<a class="btn btn-warning" href="<%= url_for('/add') %>">新規追加</a>

<div class="container">
  <div class="row border-bottom border-dark bg-light">
    <div class="col-sm">        </div>
    <div class="col-sm"> id     </div>
    <div class="col-sm"> author </div>
    <div class="col-sm"> title  </div>
    <div class="col-sm"> price  </div>
  </div>

% while ( my $row = $result->next ) {
  <div class="row border-bottom">
    <a class="btn btn-light" href="<%= url_for('/edit/' . $row->id) %>">変更</a>
    <div class="col-sm"> <%= $row->id    %> </div>
    <div class="col-sm"> <%= decode_utf8( $row->author ) %> </div>
    <div class="col-sm"> <%= decode_utf8( $row->title )  %> </div>
    <div class="col-sm"> <%= $row->price  %> </div>
  </div>
% }

</div>

@@ add.html.ep
% layout 'default';
% title 'Books List add';

%= form_for '/add' => method => 'POST' => begin
  <div class="col-md-3">
    著者名 <%= text_field 'author' , class => 'form-control' %>
  </div>
  <div class="col-md-3">
    作品名 <%= text_field 'title' , class => 'form-control' %>
  </div>
  <div class="col-md-3">
    価格 <%= text_field 'price' , class => 'form-control' %>
  </div>
  %= submit_button '追加する' , class => 'btn btn-primary mt-3'
% end


@@ edit.html.ep
% layout 'default';
% title 'Books List edit';

%= form_for '/edit/'. $row->id => method => 'POST' => begin
  <div class="col-md-3">
    著者名
    <%= text_field 'author' => decode_utf8($row->author) , class => 'form-control' %>
  </div>
  <div class="col-md-3">
    作品名
    <%= text_field 'title'  => decode_utf8($row->title) , class => 'form-control' %>
  </div>
  <div class="col-md-3">
    価格
    <%= text_field 'price' => $row->price , class => 'form-control' %>
  </div>
  %= submit_button '変更する' , class => 'btn btn-primary mt-3'
% end

%= form_for '/delete/'. $row->id => method => 'POST' => begin
  %= submit_button '削除する' , class => 'btn btn-danger mt-3'
% end

@@ layouts/default.html.ep
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title><%= title %></title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  </head>
  <body><%= content %></body>
</html>

参考: http://nekokak.jf.land.to/wiki/wiki.cgi/sub?page=Perl%2FDBIC

たくさんのWebページのSSL証明書の有効期限を確認する

ちょっと大量のドメインSSL証明書を確認する必要があった。

openssl コマンドで確認する

$ openssl s_client -connect example.com:443 -showcerts < /dev/null 2>/dev/null |  openssl x509 -dates -noout

各部分の説明

$ openssl s_client -connect ${domain}:443 -showcerts

このコマンドで、証明書情報とれる。
ただし、このコマンドだけでは接続中の状態になるので、 Ctrl + cとかで終わらせないと、次のパイプにつなげたときに上手くいかない。
ので、

< /dev/null 2>/dev/null 

を付けて、終わらす&エラー出力は不要なので捨てる。 これ分かるまでなんかうまいことできなくて詰まった。

 |  openssl x509 -dates -noout

さっきのをパイプで繋げて証明書の中身を見る。
-datesを付けると有効期限とかが出てくる。notAfter=hogehoge って感じで書かれてるのが、有効期限である。

perl で回す

さっきので1ドメイン分が取れるので、普通にシェルの for で回してもいいんだけど、出力を適当に加工したりするかもしれないので融通の利くperlで回す。

for my $domain ( @domain_list ) {
    my $result = check( $domain ) // 'error';
    print "$result : $domain \n";
}

sub check {
    my $domain = shift;
     my $command = "openssl s_client -connect ${domain}:443 -showcerts < /dev/null 2>/dev/null |  openssl x509 -dates -noout";
     my $result = `$command`;
     chomp $result;
 
     if ( $result =~ /notAfter=(.*)/  ) {
         return $1;
     }
}

参考

Perl で SSL 証明書の有効期限を取得する : あかぎメモ

OpenSSLをSSL/TLSクライアントとして使ってみる | Siguniang's Blog