作りたいものがありすぎる

40歳を過ぎてプログラミングを始めた人の顛末とこれからなど

Homestead環境で複数環境がある場合、外部から接続するアプリを選択する際の小技

先に結論

homestead(Laravel Homestead', '7.12.0)で複数のアプリを設定して外部端末から公開ipアドレスでアクセスする場合。
sites: の -map に指定するドメインのアルファベットの最昇順のサイトが表示される。

sites:
    - map: whois.test
      to: /home/vagrant/code/Laravel/public
      schedule: true

    - map: zero.test
      to: /home/vagrant/zero/Laravel/public

# アルファベット順に最初のこれが外部アクセスからのデフォルト表示となる。
    - map: aaa.test 
      to: /home/vagrant/hoge/Laravel/public

# 省略

networks:
    - type: "public_network"
      ip: "192.168.11.99"
      bridge: "en1: Wi-Fi (AirPort)"

こんな方法で設定するのは不本意なのですが、有志の日本語ドキュメントの以下の方法では上手くいきませんでした。

そもそもの話

windows10でvagrantのHomestead環境を使ってLaravelの開発をしていますが、複数のアプリケーションのプロジェクトを動かしているため、Homestead.yamlとhostに複数のアプリの環境を設定しています。

詳しくは以前の記事を参照の事 windows vagrant Homestead環境でLaravelアプリを追加する際の覚書 - 作りたいものがありすぎる

以下の例の様に複数のアプリの環境を設定できる。
Homested.yaml

folders:
    - map: C:/Vagrant/Whois
      to: /home/vagrant/code

    - map: C:/Vagrant/zero
      to: /home/vagrant/zero

sites:
    - map: whois.test
      to: /home/vagrant/code/Laravel/public
      schedule: true

    - map: zero.test
      to: /home/vagrant/zero/Laravel/public

また、詳細は省きますがwindowshostsファイルにもローカルドメインとipを設定します。

この特定のアプリをローカルネットワーク(wi-fi環境)で他のマシンからも開発環境が見れるようにする必要がありましたので、これまではHomested.yamlファイルに以下の様な記述をすることで、他のPCから閲覧できるようにしていました。

Homested.yaml

networks:
    - type: "public_network"
      ip: "192.168.11.99"
      bridge: "en1: Wi-Fi (AirPort)"

上記の設定の192.168.11.99に別のPCからアクセスすれば、これまで問題なく閲覧出来ていたのですが、先日の記事を書いて以降、意図しない別の開発環境が表示されてしまい、困った事になりました。
しかしこれまで、意図したアプリの画面が出ていたことがむしろ偶然の幸運で、複数アプリを設定してる場合、外部からアクセス可能なipでどのアプリが出るかを設定項目はyamlには書いて無い訳です。従来はたまたま偶然に望みのアプリが出ていた。という訳です。

そこで、どうすれば意図したアプリを外部から接続するか調べましたが、上手く行かず結論として上記の様なかなりその場しのぎの方法となりました。

readouble.com

環境の共有
共同作業者やクライアントと、現在作業中の内容を共有したい場合もあるでしょう。Vagrantには、vagrant shareにより、これをサポートする方法が組み込み済みです。しかし、この方法はHomestead.yamlファイルに複数サイトを設定している場合には動作しません。

この問題を解決するため、Homesteadは独自のshareコマンドを持っています。使用を開始するには、vagrant sshによりHomesteadマシンとSSH接続し、share homestead.testを実行してください。これにより、Homestead.yaml設定ファイルのhomestead.testサイトが共有されます。もちろん、homestead.testの代わりに他の設定済みサイトを指定できます。

share homestead.test

とあるので、上記の通りshare whois.testとか打った所以下の様な画面が表示されました。

vagrant@homestead:~$ share whois.test

ngrok by @inconshreveable                                                                                                                                                                                        (Ctrl+C to quit)
Session Status                connecting
Session Status                online
Sesion  Expires               7 hours, 58 minutes
Versionerface                 2.2.8             10:4040
R gio                         United States (us)
Web Interface                 http://192.168.10.10:4040t5     p50     p90     
Forwarding                    http://a3e2a379.ngrok.io -> localhost:80
Forwarding                    https://a3e2a379.ngrok.io -> localhost:80

Connections                   ttl     opn     rt1     rt5     p50     p90     
                              0       1       0.00    0.00    0.00    0.00    

HTTP Requests
-------------

GET /favicon.ico               200 OK

しかし、望みのサイトはip 192.168.11.99 や 192.168.10.10:4040t5 のいずれにアクセスしても他のアプリがでてきたり、アクセスできなかったりでした。

また、コンソールに表示されたhttps://a3e2a379.ngrok.ioにもアクセスしましたが、cssが適用されない画面であるうえ、レスポンスが大変遅く意図したもにはなりえません。

という事で困ったなー。でも外部から見た際に複数アプリがある場合、そもそもどういう法則で、今のアプリが表示されるんだろう?と悩ませている際にたまたま閃いて試したらドンピシャだったという訳です。
でもいずれ複数のアプリで外部PCからアクセスしたくなった場合は…。
これ以上良い方法がない場合はいちいちyamlフィアル書き換えるって事で対処します。

Laravelのブラウザtest duskとDBtestを混在させる場合に use RefreshDatabase;を使ってハマった話

Laravelでブラウザテストをしていますが、testの際のシナリオとして、以下の様な検証をするケースがありました。

  • DBに値を入れない状態でtest開始
  • いくつかのtestを行う。
  • あるtableにレコードを入れた状態で同様のtestを行い表示の確認

tableが0件の状態とレコードがある状態で表示が変わるので、その検証。という事です。

ところが、テストの各項目の都度、 setUp() メソッドに書かれた refresh:migrate, db:seed とかいちいちやっていると時間がかかるので、以前の記事にある様に、初回だけ やって、後は現状のDBを使ったまま破綻しないDB操作の手順を考えつつtestを書く。という事をしてました。

<?php
    protected static $db_inited = false;
    use RefreshDatabase;

    protected static function initDB()
    {
        Artisan::call('migrate:refresh');
       // 個別でシーディング
        Artisan::call('db:seed', ['--class' => 'CommunitiesTableSeeder']);
        Artisan::call('db:seed', ['--class' => 'CommunitiesUsersStatusesTableSeeder']);
        Artisan::call('db:seed', ['--class' => 'CommunityUserTableSeeder']);
        Artisan::call('db:seed', ['--class' => 'MacAddressesTableSeeder']);
        Artisan::call('db:seed', ['--class' => 'RolesTableSeeder']);
        Artisan::call('db:seed', ['--class' => 'RoutersTableSeeder']);
        Artisan::call('db:seed', ['--class' => 'UsersTableSeeder']);
        // Tumolink Tableは後で検証するので今は使わない
        // Artisan::call('db:seed', ['--class' => 'TumolinkTableSeeder']);
    } 

    public function setUp()
    {
        parent::setUp();
        // 以前の記事にもある通り、testの初回だけシーディングを実施
        if (!static::$db_inited) {
            static::$db_inited = true;
            static::initDB();
        }
    }
    // 以下省略
}

さて、上記の様な検証をしようと思っていざ以下の様なtestを書いた所、DBに値が入らないままブラウザtestが実施されて散々悩みました。

<?php
    /**
     * @test
     */
    public function 未ログインで一覧画面表示のテスト()
    {
        // 検証用のデータを入れる
        factory(Tumolink::class)->create([
            'community_user_id' => 4,
        ]);
        factory(Tumolink::class)->create([
            'community_user_id' => 5,
        ]);
        factory(Tumolink::class)->create([
            'community_user_id' => 30,
        ]);
        // 入った検証データが表示される筈なので検証、しかしエラーとなる 
        $this->browse(function (Browser $browser) {
            $browser->visit('/')
            ->assertSee('Tumolinkレコードが入った事で表示される文言');
        });
        // クエリも書かれずなぜかこのassertは通る
        $this->assertDatabaseHas('tumolink', ['community_user_id' => 30]);
    }

で、logを追うと、

2019-02-14T05:37:21.443929Z        90 Query     START TRANSACTION
2019-02-14T05:37:21.574578Z        90 Prepare   insert into `tumolink` (`community_user_id`, `maybe_arraival`, `maybe_departure`, `google_home_push`, `created_at`, `updated_at`) values (?, ?, ?, ?, ?, ?)
2019-02-14T05:37:21.575225Z        90 Execute   insert into `tumolink` (`community_user_id`, `maybe_arraival`, `maybe_departure`, `google_home_push`, `created_at`, `updated_at`) values (4, '2019-02-14 15:37:21', '2019-02-14 15:37:21', 1, '2019-02-09 14:37:21', '2019-02-14 14:37:21')
2019-02-14T05:37:21.575591Z        90 Close stmt
2019-02-14T05:37:21.576674Z        90 Prepare   insert into `tumolink` (`community_user_id`, `maybe_arraival`, `maybe_departure`, `google_home_push`, `created_at`, `updated_at`) values (?, ?, ?, ?, ?, ?)
2019-02-14T05:37:21.577048Z        90 Execute   insert into `tumolink` (`community_user_id`, `maybe_arraival`, `maybe_departure`, `google_home_push`, `created_at`, `updated_at`) values (5, '2019-02-14 15:37:21', '2019-02-14 15:37:21', 1, '2019-02-09 14:37:21', '2019-02-14 14:37:21')
2019-02-14T05:37:21.579742Z        90 Close stmt
2019-02-14T05:37:21.581257Z        90 Prepare   insert into `tumolink` (`community_user_id`, `maybe_arraival`, `maybe_departure`, `google_home_push`, `created_at`, `updated_at`) values (?, ?, ?, ?, ?, ?)
2019-02-14T05:37:21.582885Z        90 Execute   insert into `tumolink` (`community_user_id`, `maybe_arraival`, `maybe_departure`, `google_home_push`, `created_at`, `updated_at`) values (30, '2019-02-14 15:37:21', '2019-02-14 15:37:21', 1, '2019-02-09 14:37:21', '2019-02-14 14:37:21')
2019-02-14T05:37:21.583499Z        90 Close stmt
2019-02-14T05:37:23.829384Z        91 Connect   homestead@localhost on whois_test using TCP/IP
2019-02-14T05:37:23.832003Z        91 Query     use `whois_test`
2019-02-14T05:37:23.833594Z        91 Prepare   set names 'utf8mb4' collate 'utf8mb4_unicode_ci'
2019-02-14T05:37:23.833821Z        91 Execute   set names 'utf8mb4' collate 'utf8mb4_unicode_ci'
2019-02-14T05:37:23.834181Z        91 Close stmt
2019-02-14T05:37:23.834538Z        91 Prepare   set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
2019-02-14T05:37:23.834770Z        91 Execute   set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
2019-02-14T05:37:23.834991Z        91 Close stmt
2019-02-14T05:37:23.835343Z        91 Prepare   select `url_path` from `communities` where `url_path` = ? limit 1
2019-02-14T05:37:23.836422Z        91 Execute   select `url_path` from `communities` where `url_path` = 'hoge' limit 1
2019-02-14T05:37:23.836692Z        91 Close stmt
2019-02-14T05:37:24.005278Z        91 Prepare   select * from `communities` where `url_path` = ? limit 1
2019-02-14T05:37:24.005837Z        91 Execute   select * from `communities` where `url_path` = 'hoge' limit 1
2019-02-14T05:37:24.006340Z        91 Close stmt
2019-02-14T05:37:24.046781Z        91 Prepare   select `user_id` from `communities` where `id` = ?
2019-02-14T05:37:24.047358Z        91 Execute   select `user_id` from `communities` where `id` = 1
2019-02-14T05:37:24.047703Z        91 Close stmt
2019-02-14T05:37:24.081444Z        91 Prepare   select `user_id`, `unique_name`, `name`, `min_arraival_at`, `last_access` from `community_user` left join `users` on `users`.`id` = `community_user`.`user_id` inner join `communities_users_statuses` on `communities_users_statuses`.`id` = `community_user`.`id` inner join (select community_user_id, min(arraival_at) as min_arraival_at from `mac_addresses` where (`hide` = ? and `current_stay` = ?) group by `community_user_id` order by `min_arraival_at` desc) as `mac_addresses` on `community_user`.`id` = `mac_addresses`.`community_user_id` where (`user_id` <> ? and `community_id` = ? and `provisional` = ?)
2019-02-14T05:37:24.081937Z        91 Execute   select `user_id`, `unique_name`, `name`, `min_arraival_at`, `last_access` from `community_user` left join `users` on `users`.`id` = `community_user`.`user_id` inner join `communities_users_statuses` on `communities_users_statuses`.`id` = `community_user`.`id` inner join (select community_user_id, min(arraival_at) as min_arraival_at from `mac_addresses` where (`hide` = 0 and `current_stay` = 1) group by `community_user_id` order by `min_arraival_at` desc) as `mac_addresses` on `community_user`.`id` = `mac_addresses`.`community_user_id` where (`user_id` <> 1 and `community_id` = 1 and `provisional` = 1)
2019-02-14T05:37:24.082748Z        91 Close stmt
2019-02-14T05:37:24.116417Z        91 Prepare   select `user_id`, `unique_name`, `name`, `min_arraival_at`, `last_access` from `community_user` left join `users` on `users`.`id` = `community_user`.`user_id` inner join `communities_users_statuses` on `communities_users_statuses`.`id` = `community_user`.`id` inner join (select community_user_id, min(arraival_at) as min_arraival_at from `mac_addresses` where (`hide` = ? and `current_stay` = ?) group by `community_user_id` order by `min_arraival_at` desc) as `mac_addresses` on `community_user`.`id` = `mac_addresses`.`community_user_id` where (`user_id` <> ? and `community_id` = ? and `provisional` = ?)
2019-02-14T05:37:24.116823Z        91 Execute   select `user_id`, `unique_name`, `name`, `min_arraival_at`, `last_access` from `community_user` left join `users` on `users`.`id` = `community_user`.`user_id` inner join `communities_users_statuses` on `communities_users_statuses`.`id` = `community_user`.`id` inner join (select community_user_id, min(arraival_at) as min_arraival_at from `mac_addresses` where (`hide` = 0 and `current_stay` = 1) group by `community_user_id` order by `min_arraival_at` desc) as `mac_addresses` on `community_user`.`id` = `mac_addresses`.`community_user_id` where (`user_id` <> 1 and `community_id` = 1 and `provisional` = 0)
2019-02-14T05:37:24.117543Z        91 Close stmt
2019-02-14T05:37:24.153092Z        91 Prepare   select `user_id` from `community_user` inner join `communities_users_statuses` on `community_user`.`id` = `communities_users_statuses`.`id` where (`user_id` <> ? and `community_id` = ? and `hide` = ?)
2019-02-14T05:37:24.153243Z        91 Execute   select `user_id` from `community_user` inner join `communities_users_statuses` on `community_user`.`id` = `communities_users_statuses`.`id` where (`user_id` <> 1 and `community_id` = 1 and `hide` = 0)
2019-02-14T05:37:24.153519Z        91 Close stmt
2019-02-14T05:37:24.178974Z        91 Prepare   select `user_id`, `name`, `last_access` from `community_user` left join `users` on `users`.`id` = `community_user`.`user_id` inner join `communities_users_statuses` on `communities_users_statuses`.`id` = `community_user`.`id` where (`community_id` = ?) and `community_user`.`user_id` in (?, ?, ?, ?, ?) order by `last_access` desc
2019-02-14T05:37:24.179504Z        91 Execute   select `user_id`, `name`, `last_access` from `community_user` left join `users` on `users`.`id` = `community_user`.`user_id` inner join `communities_users_statuses` on `communities_users_statuses`.`id` = `community_user`.`id` where (`community_id` = 1) and `community_user`.`user_id` in (4, 9, 10, 11, 12) order by `last_access` desc
2019-02-14T05:37:24.180164Z        91 Close stmt
2019-02-14T05:37:24.204641Z        91 Prepare   select `tumolink`.*, `users`.`name`, `users`.`name_reading`, `users`.`provisional`, `communities_users_statuses`.`hide` from `tumolink` inner join `community_user` on `community_user`.`id` = `tumolink`.`community_user_id` inner join `communities_users_statuses` on `community_user`.`id` = `communities_users_statuses`.`id` inner join `users` on `users`.`id` = `community_user`.`user_id` where `community_user`.`community_id` = ?
2019-02-14T05:37:24.205081Z        91 Execute   select `tumolink`.*, `users`.`name`, `users`.`name_reading`, `users`.`provisional`, `communities_users_statuses`.`hide` from `tumolink` inner join `community_user` on `community_user`.`id` = `tumolink`.`community_user_id` inner join `communities_users_statuses` on `community_user`.`id` = `communities_users_statuses`.`id` inner join `users` on `users`.`id` = `community_user`.`user_id` where `community_user`.`community_id` = 1
2019-02-14T05:37:24.205533Z        91 Close stmt
2019-02-14T05:37:24.925167Z        91 Quit
2019-02-14T05:37:25.928286Z        90 Query     ROLLBACK
2019-02-14T05:37:25.933217Z        90 Quit

DBに検証用の値を入れるのは確認できます。その後、該当ページを表示して検証する際に呼ばれるクエリを読んだ直後にROLLBACKが走っています。
そしてそのあとに本来であれば、DBの値を検証する

$this->assertDatabaseHas('tumolink', ['community_user_id' => 30]);

に相当するクエリが走るべきなのですが、これをlogで確認できないまま、次のtestのクエリが走っていました。

で、色々なやんだ結果testクラスの上に書くこいつを消したところ上手くいきました。

// use RefreshDatabase;

SQL LOG

2019-02-14T05:45:25.585096Z       124 Connect   homestead@localhost on whois_test using TCP/IP
2019-02-14T05:45:25.585600Z       124 Query     use `whois_test`
2019-02-14T05:45:25.585867Z       124 Prepare   set names 'utf8mb4' collate 'utf8mb4_unicode_ci'
2019-02-14T05:45:25.586105Z       124 Execute   set names 'utf8mb4' collate 'utf8mb4_unicode_ci'
2019-02-14T05:45:25.586397Z       124 Close stmt
2019-02-14T05:45:25.586579Z       124 Prepare   set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
2019-02-14T05:45:25.586777Z       124 Execute   set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
2019-02-14T05:45:25.586958Z       124 Close stmt
2019-02-14T05:45:25.587218Z       124 Prepare   insert into `tumolink` (`community_user_id`, `maybe_arraival`, `maybe_departure`, `google_home_push`, `created_at`, `updated_at`) values (?, ?, ?, ?, ?, ?)
2019-02-14T05:45:25.587477Z       124 Execute   insert into `tumolink` (`community_user_id`, `maybe_arraival`, `maybe_departure`, `google_home_push`, `created_at`, `updated_at`) values (4, '2019-02-14 15:45:25', '2019-02-14 15:45:25', 1, '2019-02-09 14:45:25', '2019-02-14 14:45:25')
2019-02-14T05:45:25.591928Z       124 Close stmt
2019-02-14T05:45:25.592896Z       124 Prepare   insert into `tumolink` (`community_user_id`, `maybe_arraival`, `maybe_departure`, `google_home_push`, `created_at`, `updated_at`) values (?, ?, ?, ?, ?, ?)
2019-02-14T05:45:25.593254Z       124 Execute   insert into `tumolink` (`community_user_id`, `maybe_arraival`, `maybe_departure`, `google_home_push`, `created_at`, `updated_at`) values (5, '2019-02-14 15:45:25', '2019-02-14 15:45:25', 1, '2019-02-09 14:45:25', '2019-02-14 14:45:25')
2019-02-14T05:45:25.594983Z       124 Close stmt
2019-02-14T05:45:25.595811Z       124 Prepare   insert into `tumolink` (`community_user_id`, `maybe_arraival`, `maybe_departure`, `google_home_push`, `created_at`, `updated_at`) values (?, ?, ?, ?, ?, ?)
2019-02-14T05:45:25.596246Z       124 Execute   insert into `tumolink` (`community_user_id`, `maybe_arraival`, `maybe_departure`, `google_home_push`, `created_at`, `updated_at`) values (30, '2019-02-14 15:45:25', '2019-02-14 15:45:25', 1, '2019-02-09 14:45:25', '2019-02-14 14:45:25')
2019-02-14T05:45:25.597019Z       124 Close stmt
2019-02-14T05:45:27.846052Z       125 Connect   homestead@localhost on whois_test using TCP/IP
2019-02-14T05:45:27.848972Z       125 Query     use `whois_test`
2019-02-14T05:45:27.850521Z       125 Prepare   set names 'utf8mb4' collate 'utf8mb4_unicode_ci'
2019-02-14T05:45:27.850730Z       125 Execute   set names 'utf8mb4' collate 'utf8mb4_unicode_ci'
2019-02-14T05:45:27.850912Z       125 Close stmt
2019-02-14T05:45:27.851136Z       125 Prepare   set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
2019-02-14T05:45:27.851316Z       125 Execute   set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
2019-02-14T05:45:27.851492Z       125 Close stmt
2019-02-14T05:45:27.851773Z       125 Prepare   select `url_path` from `communities` where `url_path` = ? limit 1
2019-02-14T05:45:27.852839Z       125 Execute   select `url_path` from `communities` where `url_path` = 'hoge' limit 1
2019-02-14T05:45:27.853121Z       125 Close stmt
2019-02-14T05:45:28.046130Z       125 Prepare   select * from `communities` where `url_path` = ? limit 1
2019-02-14T05:45:28.046618Z       125 Execute   select * from `communities` where `url_path` = 'hoge' limit 1
2019-02-14T05:45:28.047275Z       125 Close stmt
2019-02-14T05:45:28.091173Z       125 Prepare   select `user_id` from `communities` where `id` = ?
2019-02-14T05:45:28.091653Z       125 Execute   select `user_id` from `communities` where `id` = 1
2019-02-14T05:45:28.092026Z       125 Close stmt
2019-02-14T05:45:28.127509Z       125 Prepare   select `user_id`, `unique_name`, `name`, `min_arraival_at`, `last_access` from `community_user` left join `users` on `users`.`id` = `community_user`.`user_id` inner join `communities_users_statuses` on `communities_users_statuses`.`id` = `community_user`.`id` inner join (select community_user_id, min(arraival_at) as min_arraival_at from `mac_addresses` where (`hide` = ? and `current_stay` = ?) group by `community_user_id` order by `min_arraival_at` desc) as `mac_addresses` on `community_user`.`id` = `mac_addresses`.`community_user_id` where (`user_id` <> ? and `community_id` = ? and `provisional` = ?)
2019-02-14T05:45:28.128013Z       125 Execute   select `user_id`, `unique_name`, `name`, `min_arraival_at`, `last_access` from `community_user` left join `users` on `users`.`id` = `community_user`.`user_id` inner join `communities_users_statuses` on `communities_users_statuses`.`id` = `community_user`.`id` inner join (select community_user_id, min(arraival_at) as min_arraival_at from `mac_addresses` where (`hide` = 0 and `current_stay` = 1) group by `community_user_id` order by `min_arraival_at` desc) as `mac_addresses` on `community_user`.`id` = `mac_addresses`.`community_user_id` where (`user_id` <> 1 and `community_id` = 1 and `provisional` = 1)
2019-02-14T05:45:28.128830Z       125 Close stmt
2019-02-14T05:45:28.161408Z       125 Prepare   select `user_id`, `unique_name`, `name`, `min_arraival_at`, `last_access` from `community_user` left join `users` on `users`.`id` = `community_user`.`user_id` inner join `communities_users_statuses` on `communities_users_statuses`.`id` = `community_user`.`id` inner join (select community_user_id, min(arraival_at) as min_arraival_at from `mac_addresses` where (`hide` = ? and `current_stay` = ?) group by `community_user_id` order by `min_arraival_at` desc) as `mac_addresses` on `community_user`.`id` = `mac_addresses`.`community_user_id` where (`user_id` <> ? and `community_id` = ? and `provisional` = ?)
2019-02-14T05:45:28.162015Z       125 Execute   select `user_id`, `unique_name`, `name`, `min_arraival_at`, `last_access` from `community_user` left join `users` on `users`.`id` = `community_user`.`user_id` inner join `communities_users_statuses` on `communities_users_statuses`.`id` = `community_user`.`id` inner join (select community_user_id, min(arraival_at) as min_arraival_at from `mac_addresses` where (`hide` = 0 and `current_stay` = 1) group by `community_user_id` order by `min_arraival_at` desc) as `mac_addresses` on `community_user`.`id` = `mac_addresses`.`community_user_id` where (`user_id` <> 1 and `community_id` = 1 and `provisional` = 0)
2019-02-14T05:45:28.162965Z       125 Close stmt
2019-02-14T05:45:28.188154Z       125 Prepare   select `user_id` from `community_user` inner join `communities_users_statuses` on `community_user`.`id` = `communities_users_statuses`.`id` where (`user_id` <> ? and `community_id` = ? and `hide` = ?)
2019-02-14T05:45:28.188561Z       125 Execute   select `user_id` from `community_user` inner join `communities_users_statuses` on `community_user`.`id` = `communities_users_statuses`.`id` where (`user_id` <> 1 and `community_id` = 1 and `hide` = 0)
2019-02-14T05:45:28.189100Z       125 Close stmt
2019-02-14T05:45:28.231102Z       125 Prepare   select `user_id`, `name`, `last_access` from `community_user` left join `users` on `users`.`id` = `community_user`.`user_id` inner join `communities_users_statuses` on `communities_users_statuses`.`id` = `community_user`.`id` where (`community_id` = ?) and `community_user`.`user_id` in (?, ?, ?, ?, ?) order by `last_access` desc
2019-02-14T05:45:28.231659Z       125 Execute   select `user_id`, `name`, `last_access` from `community_user` left join `users` on `users`.`id` = `community_user`.`user_id` inner join `communities_users_statuses` on `communities_users_statuses`.`id` = `community_user`.`id` where (`community_id` = 1) and `community_user`.`user_id` in (4, 9, 10, 11, 12) order by `last_access` desc
2019-02-14T05:45:28.232347Z       125 Close stmt
2019-02-14T05:45:28.258731Z       125 Prepare   select `tumolink`.*, `users`.`name`, `users`.`name_reading`, `users`.`provisional`, `communities_users_statuses`.`hide` from `tumolink` inner join `community_user` on `community_user`.`id` = `tumolink`.`community_user_id` inner join `communities_users_statuses` on `community_user`.`id` = `communities_users_statuses`.`id` inner join `users` on `users`.`id` = `community_user`.`user_id` where `community_user`.`community_id` = ?
2019-02-14T05:45:28.259224Z       125 Execute   select `tumolink`.*, `users`.`name`, `users`.`name_reading`, `users`.`provisional`, `communities_users_statuses`.`hide` from `tumolink` inner join `community_user` on `community_user`.`id` = `tumolink`.`community_user_id` inner join `communities_users_statuses` on `community_user`.`id` = `communities_users_statuses`.`id` inner join `users` on `users`.`id` = `community_user`.`user_id` where `community_user`.`community_id` = 1
2019-02-14T05:45:28.260029Z       125 Close stmt
2019-02-14T05:45:29.048109Z       125 Quit
2019-02-14T05:45:29.981275Z       124 Prepare   select count(*) as aggregate from `tumolink` where (`community_user_id` = ?)
2019-02-14T05:45:29.981864Z       124 Execute   select count(*) as aggregate from `tumolink` where (`community_user_id` = 30)
2019-02-14T05:45:29.982288Z       124 Close stmt

‘use RefreshDatabase‘が無いため当然ROLLBACKはかかりません。また、DBの値を検討する以下のクエリもlogの最後の方に見られます。

2019-02-14T05:45:29.981864Z       124 Execute   select count(*) as aggregate from `tumolink` where (`community_user_id` = 30)

RefreshDatabase の動きですが、有志作成の日本語リファレンスでは以下の様にあります。

https://readouble.com/laravel/5.5/ja/database-testing.html

各テスト後のデータベースリセット 前のテストがその後のテストデータに影響しないように、各テストの後にデータベースをリセットできると便利です。インメモリデータベースを使っていても、トラディショナルなデータベースを使用していても、RefreshDatabaseトレイトにより、マイグレーションに最適なアプローチが取れます。テストクラスてこのトレイトを使えば、全てが処理されます。

ところが実際に動かしてSQLのLOGを見ると、どうもtestのfunction単位でrollbackが発生するのでは無いのは明らかです。

このような動きの為、ブラウザの表示をして確認が取れる前に rollback が走ってせっかく挿入したレコードが消え、その後ブラウザの表示が完了して検証をする。といった動作の為、testが失敗する様です。

<?php
    public function 未ログインで恵比寿_滞在者一覧画面閲覧_ツモリスト有り()
    {
        factory(Tumolink::class)->create([
            'community_user_id' => 30,
        ]);
        $this->browse(function (Browser $browser) {
            $browser->visit('/')
            ->assertSee('Tumolinkレコードが入った事で表示される文言');
        });
        // RefreshDatabase を使うとここでrollbackが発生した上
        // select count(*) as aggregate from `tumolink` where (`community_user_id` = 30) のクエリも走らず次のtestに行く
        $this->assertDatabaseHas('tumolink', ['community_user_id' => 30]);
    }

ではなぜ?という細かい所までは追ってませんが、ひとまずこんなハマり所があるので気を付けましょう。という話でした。

【輪読会資料】基礎から学ぶVue.js CHAPTER3 イベントとフォーム入力の受け取り 読書メモ

以下の記事は2019/2/14 コワーキングスペース秋葉原Weeybleで行われる輪読会
[秋葉原] 基礎から学ぶVue.js輪読会 ch3 イベントとフォーム入力 (初心者歓迎!)のための読書メモとなります。
以下の書籍の CHAPTER3 イベントとフォーム入力の受け取り のメモです。

基礎から学ぶ Vue.js

基礎から学ぶ Vue.js

CHAPTER3 イベントとフォーム入力の受け取り

ちなみに Weeybleで去年同じ本の輪読会で使ったドキュメント、およびソースコードは以下にありました。

GitHub yasugahira0810/vuejs_chapter3

書籍用のサイトのCHAPTER3記述ページ(サンプルコード有り)

これまでの輪読会資料

CAHPTER 1
【輪読会資料】基礎から学ぶVue.js CHAPTER1 Vue.jsとフレームワークの基礎知識 読書メモ - 作りたいものがありすぎる

CHAPTER 2
【輪読会資料】基礎から学ぶVue.js CHAPTER2 データの登録と更新 - Qiita

SECTION 13 イベントハンドリング

イベントハンドラ
これまでのサンプルのボタンに出てた v-onの事をここでは解説する

イベントに紐づける処理の内容をこの本では「イベントハンドラと呼び
イベントハンドラとイベントを紐づけることを「ハンドル」と呼ぶ
イベントはmousewheelIE9では動かないものもあるので注意

<button v-on:click="doRemove(index)">モンスターを削除</button>

@で記述も可能

<button @click="doRemove(index)">モンスターを削除</button>
new Vue({
    el: '#app',
    data: {
    },
    methods: {
        doRemove: function (index) { // ボタンクリックでこの処理が走る
            this.list.splice(index, 1)
        }
    }
})

click同様ブラウザが対応していれば以下のイベントも使える

  • scroll
  • mousewheel

フォーム入力の取得

v-onディレクティブで入力内容を確認してからデータに代入することができる

<input v-bind:value="message" v-on:change="handleInput">
new Vue({
    el: '#app',
    data: {
        message: 'Hello Vue.js',
    },
    methods: {
        handleInput: function (event) {
            // 代入前に何か処理を行う…
            // バリデード処理とかできるのかな?
            this.message = event.target.value
        }
    }
})

イベント修飾子

click などのDOMのふるまいを変更する

  • .stop event.stopPropagation(); イベント伝播,バブリングを止める
  • .prevment event.preventDefault(); 禁止操作の指定 リンク操作、submitの処理をキャンセル
  • .capture キャプチャーモードDOMイベントをハンドルする
  • .self
  • .native
  • .once
  • .passive { passive: true } でDOMイベントはハンドルする

クリックイベント マウスボタンを指定できる

  • .left
  • .right
  • .middle

作例

<!-- いずれもhandlerメソッドでマウス右クリックでconsole.logにmouse event のlogを出力する -->

<div v-on:click.right="handler">example</div>
<!-- こっちは`.prevment`修飾子で右クリックメニューの表示を禁止している -->
<div v-on:click.right.prevent="handler">example</div>
new Vue({
    el: '#app',
    methods: {
        handler: function (comment) {
            console.log(comment)
        }
    }
})

Extra DOMイベント伝播,バブリングについて

そもそもJavaScriptのバブリングの概念を知っておく必要あり
DOMイベントのキャプチャ/バブリングを整理する 〜 JSおくのほそ道 #017

その上でイベント修飾子を付与することでバブリングを制御できる。

入れ子のDOMイベントの発生順序は JSの addEventListenerには第三引数に省略可能でデフォルトはfalseの値がある。
これをuseCaptureという

<div id="outer">
    <div id="inner" align="center"></div>
</div>
function out(s) {return function() {console.log(s);}}
document.getElementById('outer').addEventListener('click', out('outer'), false); // ←コレ
document.getElementById('inner').addEventListener('click', out('inner'));

#outer,.addEventListenerの第三引数を false または省略した場合、innerのイベントが先に発火する。
逆にtureにすればouterが先に発火する

結果として、これを理解していないでアッチコッチにイベント仕込むと、親要素にイベントが伝播しまくって困った事になるらしい。
そこでVue.jsでは前述のイベント修飾子を使って伝播の制御を行う、という事らしい

では以下のJSを元に書くイベント修飾子の動きを確認してゆく

new Vue({
    el: '#app',
    methods: {
        handler: function (comment) {
            console.log(comment)
        }
    }
})

.stop

event.stopPropagation(); イベント伝播,バブリングを止める
div2クリックでdiv2のみが出力

<div v-on:click="handler('div1')">
  div1
  <a href="#top" v-on:click.stop="handler('div2')">div2</a>
</div>

.prevent

event.preventDefault(); 禁止操作の指定 リンク操作、submitの処理をキャンセル
div2クリックでdiv2,div1と出力(操作の禁止をするのみなので伝播は通常通り起こるという事?)

<div v-on:click="handler('div1')">
  div1
  <a href="#top" v-on:click.prevent="handler('div2')">div2</a>
</div>

.capture

キャプチャーモードでイベントを発生させる バブリングモードのイベントよりも先に発生する
div3クリックでdiv1,div3,div2の順で出力

<div v-on:click.capture="handler('div1')">
  div1
  <div v-on:click="handler('div2')">
    div2
    <div v-on:click="handler('div3')">div3</div>
  </div>
</div>

.self

evant.targetが自分自身の時だけハンドラが呼び出される

<div class="overlay" v-on:click.self="close">div</div>

.native

直接イベントを発火させたい場合に使う 詳細はCAPTER5に

<!-- コンポーネントをクリックするとハンドラが呼び出される -->
<my-component v-on:click.native="handler"></my-component>
<!-- コンポーネントをクリックしてもハンドラは呼び出されない -->
<my-component v-on:click="handler"></my-component>

.passive

event.prevmentDefault()を呼び出さない事を明示的にする
.preventとの併用はNG
モバイル環境でのスクロールカク追記を防ぐ等に使用

キー修飾子

キーボード入力時に呼び出される様になる修飾子,キーコードか、キー指定でもOK

<!-- どちらもEnterキーを表す -->
<input v-on:keydown.13="handler">
<input v-on:keydown.enter="handler">

システム修飾子

キーが押されている場合のみハンドラが呼び出される
以下はshiftキーの例

<button v-on:click.shift="doDelete"></button>

その他詳細はVue.js公式ガイド「イベントハンドリング」「システム修飾子キー」を参照のこと

SECTION14 フォーム入力バインディング

フォームの入力や選択値を、データを同期する 「双方向データバインディング にはv-modelディテクティブを使う

v-modelの使い方

テキストフォームをmessageプロパティとバインディングした例

<div id="app">
    <input v-model="message">
    <p>{{ message }}</p>
</div>
new Vue({
  el: '#app',
  data: {
    message: 'Hello!'
  }
})

Vue.jsの双方向データディバイディング

入力した文字をデータに反映したい場合は、入力イベントをハンドルして取得したデータをリアクティブデータに代入する必要がある。

this.message = event.taget.value // ここでデータが書き換わる

一連の例に出て来るmessageを使用した処理は良く行われる

v-modeディレクティブはDOMのデータバインディングと要素から取得したデータをリアクティブにするための鉄板構文らしい。

v-modelで受け取りデータの型

基本、入力フォームは文字列型、複数選択フォームは配列型となるが、値にデータバインディングを使用した場合、値の型はバインドされているデータによって変わる。

複数行テキスト

文字列となる。

<textarea v-model="message"></textarea>
<pre>{{ message }}</pre>
new Vue({
  el: '#app',
  data: {
    message: 'Hello!'
  }
})

チェックボックス

単数の場合、は単純に bool

<label>
  <input type="checkbox" v-model="val"> {{ val }}
</label>
new Vue({
  el: '#app',
  data: {
    val: true
  }
})

複数要素は配列、各要素にvalue属性を設定

<label><input type="checkbox" v-model="val" value="A"> A</label>
<label><input type="checkbox" v-model="val" value="B"> B</label>
<label><input type="checkbox" v-model="val" value="C"> C</label>
<p>{{ val }}</p>
new Vue({
  el: '#app',
  data: {
    val: []
  }
})

AとCの選択では ["A", "C"]となる

ラジオボタン

デフォルトは文字列

<label><input type="radio" value="a" v-model="val"> A</label>
<label><input type="radio" value="b" v-model="val"> B</label>
<label><input type="radio" value="c" v-model="val"> C</label>
<p>{{ val }}</p>
new Vue({
  el: '#app',
  data: {
    val: ''
  }
})

セレクトボックス

単一選択プルダウン形式
デフォルト文字列

<select v-model="val">
  <option disabled="disabled">選択してください</option>
  <option value="a">A</option>
  <option value="b">B</option>
  <option value="c">C</option>
</select>
<p>{{ val }}</p>
new Vue({
  el: '#app',
  data: {
    val: ''
  }
})

複数選択リスト形式

<select v-model="val" multiple>
  <option value="a">A</option>
  <option value="b">B</option>
  <option value="c">C</option>
</select>
<p>{{ val }}</p>
new Vue({
  el: '#app',
  data: {
    val: []
  }
})

AとCの選択では ["A", "C"]となる

画像ファイル

v-modelは使用できない。リアクティブにするならchangeイベントをハンドルする

<input type="file" v-on:change="handleChange">
<div v-if="preview"><img v-bind:src="preview"></div>
new Vue({
  el: '#app',
  data: {
    preview: ''
  },
  methods: {
    handleChange: function (event) {
      var file = event.target.files[0]
      if (file && file.type.match(/^image\/(png|jpeg)$/)) {
        this.preview = window.URL.createObjectURL(file)
      }
    }
  }
})

画像選択するとプレビューが出る!カッコイイ!

その他入力タイプ

range,colorHTML5の入力タイプも使える

横スライドレンジの数値が出る奴

<input type="range" v-model.number="val">{{ val }}
new Vue({
  el: '#app',
  data: {
    val: 50
  }
})

修飾子

v-modelにくっつく奴

修飾子 作用
.lazy inputの代わりにchangeイベントはハンドルする
.number 値を数値に変換する
.trim 値の余分なスペースを削除する

.numberの使用例
テキストフォームに入った値はtype="number"としても文字列となる。
だが、これで数値として取得することが出来る。

<input type="text" v-model.number="price"> {{ price }}
new Vue({
el: '#app',
    data: {
    price: 50
    }
})

SECTION 15 マウント要素外のイベントと操作

v-onはDOMのwindow,bodyでは使用できない為、それらを扱いたい場合はJS純正のaddEventLisnerメソッドを使う事になる。注意点として、不要になっても自動的に解除されないので、不要になった際はフック(ライフサイクルフックCAPTER1の最後の奴)を使って解除する必要がある。

スクロールイベントの取得

発生頻度の高いイベント等は、タイマーを使用して処理の実行頻度を抑えると良い。
以下はwindowのスクロールイベントを200ms間隔でwindow.scrollYプロパティを更新する例
これを応用して、サイドバーを画面に常に固定したり、スクロールすると表示を変化させるメニュー等に使用可能。

<header v-bind:class="{ compact: scrollY > 200 }">
  200pxより下にスクロールしたら .compact を付与する
</header>
new Vue({
  el: '#app',
  data: {
    scrollY: 0,
    timer: null
  },
  created: function () {
    // ハンドラを登録
    window.addEventListener('scroll', this.handleScroll)
  },
  // CAPTER1最後のライフサイクルダイアグラムの beforeDestroy
  beforeDestroy: function () {
    // ハンドラを解除(コンポーネントやSPAの場合忘れずに!)
    window.removeEventListener('scroll', this.handleScroll)
  },
  methods: {
    // 違和感のない程度に200ms間隔でscrollデータを更新する例
    handleScroll: function () {
      if (this.timer === null) {
        this.timer = setTimeout(function () {
          this.scrollY = window.scrollY
          clearTimeout(this.timer)
          this.timer = null
        }.bind(this), 200)
      }
    }
  }
})

bodyに適当な要素を縦長に書いてから開発コンソールのElementsを開いて確認してみる
スクロール前

<header class="">
  200pxより下にスクロールしたら .compact を付与する
</header>

スクロール後

<header class="compact">
  200pxより下にスクロールしたら .compact を付与する
</header>

当然、JS内の最後の値200の数値を大きくすると反応はニブくなる。

スムーススクロールの実装

よくある『ページTOP』で滑らかに移動する奴はwindowオブジェクトを操作している、ライブラリを使えば簡単に実装できる、ここでは「Smooth Scroll」を使った例を示す。
GitHub Smooth Scroll

<script src="https://cdn.jsdelivr.net/npm/smooth-scroll@12.1.5"></script>
<div id="app">
  <div class="content">...</div>
  <div v-on:click="scrollTop">
    ページ上部へ移動
  </div>
</div>
// ここでSmoothScrollを変数に入れている
var scroll = new SmoothScroll()
new Vue({
    el: '#app',
    methods: {
        scrollTop: function () {
            // scrollTopのバインドでSmoothScrollのメソッド animateScroll() を呼んでいる
            // 引数に画面最上部からの位置を指定できる
            scroll.animateScroll(0)
        }
    }
})

COLUMN Vue.js以外からのイベントの読み取り

プラグインの実装等で、Vue.js以外のDOM操作ライブラリを使わざるを得ない場合、JSのdispatchEventを使ってイベント検知が出来る

以下はjQueryvalメソッドと絡めた例

<div id="app">
  <input id="message" v-on:input="handleInput">
  <button data-update="jQuery!">jQueryからの更新</button>
</div>
<!-- html内でjQueryのCDNを別途読み込むこと -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>  
$(document).on('click', '[data-update]', function () {
    $('#message').val($(this).attr('data-update'))
    // 入力値を更新したらイベントを発生させる
    $('#message')[0].dispatchEvent(new Event('input'))
})
new Vue({
    el: '#app',
    methods: {
        handleInput: function (event) {
            console.log(event.target.value)
        }
    }
})

まとめ

Laravelのブラウザテストでtest用DBを使う際はコマンドに注意

短めですが、ブラウザテストの際の注意点。
以下のサイトにもあるような設定をしてから、テスト用のDBに切り替えて自動ブラウザテストが行われる様に諸々設定をしていたんですが...

Laravel5.6 テスト用データベースを作成してテストを実行するための設定方法

mysqlのlogを調べた所。なぜかArtisanコマンドはtest用のDBでシーディングをしているにも関わらず、いざブラウザテストとなると、ローカルの通常のDBを見てtestをしているようなのです。

原因はtest実施の際のコマンドでした、以下じゃだめです。

 ./vendor/bin/phpunit tests/Browser/IndexTest.php

ちゃんと duskのコマンドでやりましょう。

 php artisan dusk tests/Browser/IndexTest.php

ユニットテストとブラウザテストは別物、と意識した方が良いですね。

基礎から学ぶVue.js CHAPTER2 データの登録と更新 読書メモ

今回は輪読担当ではありませんが、ひとまずメモをまとめたのでアップします。

以下の記事は2019/2/7 コワーキングスペース秋葉原Weeybleで行われる輪読会
[秋葉原] 基礎から学ぶVue.js輪読会 ch2 データの登録と更新(初心者歓迎!)のための読書メモとなります。
以下の書籍の CHAPTER2 Vue.jsとデータの登録と更新 のメモです。

基礎から学ぶ Vue.js

基礎から学ぶ Vue.js


CAPTER2

SECTION 07 基本データのバインディング

Mustache(マスタッシュ記法) hogeプロパティをhtmlにバインドする

<p>{{ hoge }}</p>

バインドは属性に使えない
下はエラー

<input type="text" value="{{ message }}">

これが正しい
属性へのバインドはv-bindディレクティブを使う

<input type="text" v-bind:value="message">
<!-- 省略して書くとこう -->
<input type="text" :value="message">

cssスタイルはキャメルケースで指定

<button v-on:click="isActive=!isActive">isActiveを切り替える</button>
<p v-bind:class="{ child: isChild, 'is-active': isActive }" class="item">
  動的なクラス
</p>
<p v-bind:style="{ color: textColor, backgroundColor: bgColor }" class="item">
  動的なスタイル
</p>
new Vue({
    el: '#app',
    data: {
        isChild: true,
        isActive: true,
        textColor: 'red',
        bgColor: 'lightgray'
    }
})
.item {
  padding: 4px 8px;
  transition: background-color 0.4s;
}
.is-active {
  background: #ffeaea;
}

オブジェクトデータで渡す方法
Vue.js側でひとまとめで定義

<p v-bind:class="classObject">Text</p>
<p v-bind:class="classObject_2">Text</p>
new Vue({
    el: '#app',
    data: {
        classObject: {
            isChild: true,
            isActive: true,
            textColor: 'red',
            bgColor: 'lightgray'
        },
        classObject_2: {
            isChild: true,
            isActive: true,
            textColor: 'red',
            bgColor: 'lightgray'
        }
    }
})

複数の属性のデータバインディング

沢山のプロパティがあっても

new Vue({
    el: '#app',
    data: {
        item: {
            id: 1,
            src: 'item1.jpg,
            alt: 'サムネ画像です',
            width: 200,
            height: 100,
        }
    }
})

まとめて定義できる

<img v-bind="item">

特定の要素のみに変更を加えることも可能

<img v-bind="item" v-bind:id="'thunb-' + item.id">
<!-- thunb-1 の id が付与される -->

SVGのデータバインディングベクター画像)

<div id="app">
  <svg xmlns="http://www.w3.org/2000/svg" version="1.1">
    <circle cx="100" cy="75" v-bind:r="radius" fill="lightpink" />
  </svg>
  <input type="range" min="0" max="100" v-model="radius">
</div>
new Vue({
    el: '#app',
    data: {
        radius: 50
    }
})

SECTION 09 テンプレートにおける条件分岐

v-if``v-showディレクディブは付与した要素の描画・表示に条件を適用する。

okプロパティがtrueの時のみdiv要素を表示する

<div v-if="ok">hoge</div>
<div v-show="ok">hoge</div>
new Vue({
    el: '#app',
    data: {
        ok: false
    }
})

条件を満たさない場合に生成されるhtml display:none となる

<div style="display: none;">hoge</div>

v-if と v-show の違いと使い分け

v-if の場合

DOMレベルでない事になる

v-show の場合

display:none が付与される
切り替え頻度が高いならこっちが処理早い

タグによるv-if グループ化

複数の要素を if で切り替えたい場合グループ化できる

<tamplate>
    <header>title</header>
    <div><contents/div>
</tamplate>

v-else-if 及び v-else によるグループ化

<div v-if="type === 'A'">AAA</div>
<div v-else-if="type === 'B'">BBB</div>
<div v-else>どちらでも無い場合</div>

v-else-if v-else key

keyを設定して属性の重複による発動しない状態を回避する

<!-- 2つのdivが違う要素である事を明示的にする -->
<div v-if="loaded" key="content-visible">
  content
</div>
<div v-else key="content-loading">
  loading now...
</div>

SECTIOM 10 リストデータの表示と更新

要素を繰り返して描画する

みたまんま、こんな感じで繰り返し描画できる。
v-for="item in list"php や JS のfor 文や foreach と同じ様に使える

  <ul>
    <li v-for="item in list" v-bind:key="item.id">
      ID.{{ item.id }} {{ item.name }} HP.{{ item.hp }}
    </li>
  </ul>
new Vue({
    el: '#app',
    data: {
        list: [
            { id: 1, name: 'スライム', hp: 100 },
            { id: 2, name: 'ゴブリン', hp: 200 },
            { id: 3, name: 'ドラゴン', hp: 500 }
        ]
    }
})

出力結果

<ul>
    <li>ID.1 スライム HP.100</li>
    <li>ID.2 ゴブリン HP.200</li>
    <li>ID.3 ドラゴン HP.500</li>
</ul>

インデックスとオブジェクトキーの使用

変数部分をカッコで囲んで配列インデックスを任意に受け取れる

<li v-for="(item, index ) in list"> ...</li>

オブジェクトなら「値」「キー」「インデックス」の順で任意に受け取れる

<li v-for="(item, key, index ) in list"> ...</li>

キーの役割

これ大事! v-bind:key="item.id"

<li v-for="item in list" v-bind:key="item.id">

要素にユニークなキー属性を追加するのが望ましい。ほぼ必須と考えて良い。
キーが無いと要素全部の更新が入る。なのでSQLidを入れる位に考えると良い。

繰り返し描画しながら様々な条件を適用する

v-if を絡めて、比較演算子で条件付けて、特定条件での表示などもできる。

  <ul>
    <li v-for="item in list" v-bind:key="item.id" v-bind:class="{ tuyoi: item.hp > 300 }">
      ID.{{ item.id }} {{ item.name }} HP.{{ item.hp }}
      <span v-if="item.hp > 300">つよい!</span>
    </li>
  </ul>

出力結果

<ul>
    <li>ID.1 スライム HP.100</li>
    <li>ID.2 ゴブリン HP.200</li>
    <li>ID.3 ドラゴン HP.500<span>つよい!</span></li>
</ul>

リストの更新

注意点として以下のケースで更新を検知できない

  1. インデックス数値を使った配列要素の更新
  2. 後から追加されたプロパティの更新

リストに要素を追加

push,unshiftを使う

this.list.push(要素)

以下、サンプル、ボタン押下でフォーム内の名前のモンスターがリストに追加される。IDは自動生成。HPは500固定

  <!-- このフォームの入力値を新しいモンスターの名前に使う -->
  名前
  <input v-model="name">
  <button v-on:click="doAdd">モンスターを追加</button>
  <ul>
    <li v-for="item in list" v-bind:key="item.id">
      ID.{{ item.id }} {{ item.name }} HP.{{ item.hp }}
    </li>
  </ul>
new Vue({
    el: '#app',
    data: {
        name: 'キマイラ',
        list: [
            { id: 1, name: 'スライム', hp: 100 },
            { id: 2, name: 'ゴブリン', hp: 200 },
            { id: 3, name: 'ドラゴン', hp: 500 }
        ]
    },
    methods: {
        // 追加ボタンをクリックしたときのハンドラ
        doAdd: function () {
            // リスト内で1番大きいIDを取得
            var max = this.list.reduce(function (a, b) {
                return a > b.id ? a : b.id
            }, 0)
            // 新しいモンスターをリストに追加
            this.list.push({
                id: max + 1, // 現在の最大のIDに+1してユニークなIDを作成
                name: this.name, // 現在のフォームの入力値
                hp: 500
            })
        }
    }
})

リストから削除する

リストからの削除は配列メソッドのspliceを使う

li毎に削除ボタンが表示され、クリックで対象を消せる

  <ul>
    <li v-for="(item, index) in list" v-bind:key="item.id">
      ID.{{ item.id }} {{ item.name }} HP.{{ item.hp }}
      <!-- 削除ボタンをv-for内に作成 -->
      <button v-on:click="doRemove(index)">モンスターを削除</button>
    </li>
  </ul>
new Vue({
    el: '#app',
    data: {
        list: [
            { id: 1, name: 'スライム', hp: 100 },
            { id: 2, name: 'ゴブリン', hp: 200 },
            { id: 3, name: 'ドラゴン', hp: 500 }
        ]
    },
    methods: {
        // 要素を削除ボタンをクリックしたときのハンドラ
        doRemove: function (index) {
            // 受け取ったインデックスの位置から1個要素を削除
            this.list.splice(index, 1)
        }
    }
})

リスト要素( <li>hoge</li> )に関しては以下の様な配列メソッドを使用して操作が可能

  • push
  • pop
  • shift
  • unshift
  • splice
  • sort
  • reverse

これは駄目

this.list[0] = { id: 1, name: 'hoge', hp: 500 }

これなら大丈夫

Vue.setメソッドを使用して明示的に更新できる。エイリアスthis.$setとなる

this.$set(更新するデータ , インデックスorキー , { 新しい値 })

上記からの具体例だとこんな感じ

this.$set(this.list, 0, { id: 1, name: 'hoge', hp: 500 })

プロパティを追加する

this.$setメソッドは持ってないプロパティをリアクティブデータとして追加するために使用できる。

new Vue({
    el: '#app',
    data: {
        list: [
            { id: 1, name: 'スライム', hp: 100 },
            { id: 2, name: 'ゴブリン', hp: 200 },
            { id: 3, name: 'ドラゴン', hp: 500 }
        ]
    },
    created: function() {
        // すべての要素にactiveプロパティを追加したい
        this.list.forEach(function(item) {
            this.$set(item, 'active', false)
            // 「item.active = false」ではリアクティブにならない
        }, this)
    }
})

リスト要素プロパティを更新する

プロパティ hp を更新する 作例

  <ul>
      <li v-for="(item, index) in list" v-bind:key="item.id" v-if="item.hp">
          ID.{{ item.id }} {{ item.name }} HP.{{ item.hp }}
      <span v-if="item.hp < 50">瀕死!</span>
      <!-- ボタンはv-for内に作成 -->
      <button v-on:click="doAttack(index)">攻撃する</button>
    </li>
  </ul>

liの最後にあるv-if="item.hp"は hpが0になると消える処理になる。
<span v-if="item.hp < 50">瀕死!</span>は hp 50未満で表示される。

new Vue({
    el: '#app',
    data: {
        list: [
            { id: 1, name: 'スライム', hp: 100 },
            { id: 2, name: 'ゴブリン', hp: 200 },
            { id: 3, name: 'ドラゴン', hp: 500 }
        ]
    },
    methods: {
        // 攻撃ボタンをクリックしたときのハンドラ
        doAttack: function (index) {
            this.list[index].hp -= 10 // HPを減らす
        }
    }
})

ユニークキーを持たない配列

出来ない訳ではない、簡易にする際はこれでもOK

<option v-for="item in list">{{ item }}</option>
data: {
    list: ['aaa', 'bbb', 'ccc']
}

オプションにデータを持たないv-for

v-forに数値をセットすると以下の例の様にspanで囲まれた1~15の値を出力できる

<span v-for="item in 15">{{ item }}</span>

同様に 1,5,10,15 の4つを出力する

<span v-for="item in [1, 5, 10, 15]">{{ item }}</span>

文字列に対するv-for

文字列にv-for を使うと1文字ずつ別々の要素で描画される

<span v-for="item in text">{{ item }}</span>
new Vue({
    el: '#app',
    data: {
        text: 'hoge'
    }
})

出力結果

<span>h</span>
<span>o</span>
<span>g</span>
<span>e</span>

これを利用するとテキストアニメーションが作れるらしい

外部からデータを取得する

外部データはJSONやWebAPIで取得する必要がある。
JSONを外部データから取り込んでみる。
htmlの下の方にある<script>内に javascriptライブラリのaxiosCDNを読み込む1行を追加する。

  <script src="https://cdn.jsdelivr.net/npm/axios@0.17.1/dist/axios.min.js"></script>

これでAJAXが使える様になる

  <ul>
    <li v-for="(item, index) in list" v-bind:key="item.id">
      ID.{{ item.id }} {{ item.name }} HP.{{ item.hp }}
    </li>
  </ul>
new Vue({
    el: '#app',
    data: {
        // あらかじめ空リストを用意しておく
        list: []
    },
    created: function () {
        axios.get('list.json').then(function (response) {
            // 取得完了したらlistリストに代入
            this.list = response.data
        }.bind(this)).catch(function (e) {
            console.error(e)
        })
    }
})

ライフサイクルフックの created を使って new 直後にjsonを非同期で取り込む、取り込む前は data.list の空配列[]が一瞬だが、適用されている。ここが表示されるまでに、ローディングアニメーション処理とか入るとカッコよくなる。

JSONファイル list.json

[
  { "id": 1, "name": "スライム", "hp": 100 },
  { "id": 2, "name": "ゴブリン", "hp": 200 },
  { "id": 3, "name": "ドラゴン", "hp": 500 }
]

SECTION 11 DOMを直接参照する $el と $refs

DOMにアクセスするには、インスタンスプロパティ$el,$refsを使用する。
但し、ライフサイクルフックのmounted以降でないと使えない

$el の使い方

テンプレートを囲んでいるルート要素は$el を使って参照できる。
例えば<canvas>要素などにアクセスしたい時などに使用する。

var app = new Vue({
  el: '#app'
      mounted: function() {
      console.log(this.$el)  // <div id="app"></div>
  }
})

$refsの使い方

<div id="app">
    <p ref="hello">hello</p>
    <!--  p要素にhelloと名を付けた   -->
</div>

以下の様にアクセス

new Vue({
    el: '#app',
      mounted: function() {
      console.log(this.$refs.hello)  // p要素のDOMとなる
    }
})

$el や$refsは一時的な変更です!

これらは仮想DOMではないので描画処理の最適化をしない
操作の都度描画するので注意

<div id="app">
  <button v-on:click="handleClick">カウントアップ</button>
  <button v-on:click="show=!show">表示/非表示</button>
  <span ref="count" v-if="show">0</span>
</div>
new Vue({
  el: '#app',
  data: {
    show: true
  },
  methods: {
    handleClick() {
      var count = this.$refs.count
      if (count) {
        count.innerText = parseInt(count.innerText, 10) + 1
      }
    }
  }
})

カウントアップに対して、表示・非表示ボタンがあるが、count up した状態で、非表示・再表示すると、カウントが0にもどってしまう。これは、DOMに対して加算をしたのみなので、DOMが消えると、値も消えてしまうため。 vue.js での指定なら仮想DOMなのでこうはならない。

SECTION12 テンプレート制御ディレクティブ

ディレクティブ 作用
v-pre テンプレートのコンパイルをスキップする XSS対策に有効
v-once 一度だけバインディングを行う
v-text Mustash {{ }} の代わりにテキストコンテンツを描画
v-html HTMLタグをそのまま描画する
v-cloak インスタンスの準備が終わると取り除かれる

v-pre

XSS対策などで使う

<a v-bind:href="#" v-pre>
    hello {{ message }}
</a>

<!-- これは以下のよう生だし描画される -->
<a v-bind:href="#" v-pre> hello {{ message }}</a>

v-once

描画されたあとに 指定したプロパティの値が変わってもDOMは更新されない

v-text

Mustash {{ kore }} を使わないで書くパターンがある場合に使える

var app = new Vue({
  el: '#app'
      data: {
          message: 'Hello!'
  }
})

こんな風にmessageでバインドできる。

<span v-text="message"></span>

v-html

v-pre (XSS対策)とは逆に、htmlのタグ等、をそのまま出してしまう奴
自分がコントロールできない外部やユーザー要因のデータ部分に使うと脆弱性ありありなので使い所には注意が必要。

var app = new Vue({
  el: '#app'
      data: {
          message: 'hello<strong>Vue.js!</strong>'
  }
})

こんな風にhtmlがそのまま出力する事ができる。

<span v-html="message"></span>
<!-- これは以下のよう描画される -->
<span>hello<strong>Vue.js!</strong></span>

v-cloak

インスタンスの準備ができると自動的に取り除かれる。コンパイル前のテンプレートが表示されるのを防げる

CSSに以下の様なスタイル定義をする

[v-cloak] { display: none}

以下のやつで画面読み込み時に#app要素を隠せる。
インスタンス生成でv-cloak属性が外れてフェードイン表示される。

@keyframes cloak-in {
  0% {
    opacity: 0;
  }
}
#app {
  animation: cloak-in 1s;
}
#app[v-cloak] {
  opacity: 0;
}

仮想DOMとは?

超要約するとDOMツリーの上にもう一枚仮想DOMのツリーを作ってVue.jsでは基本的に仮想側の操作をする。 DOM自体が変わったり、変えたりした際は非同期で本来のDOMを変更しているので、タイムラグや、DOMの入れ替え時に反映されない事がある。
例えば v-if,v-elseで分岐表示させた際等にこれが起こりうる。そのため、分岐の各要素に異なる key を付けて別物ということを認識させる事でちゃんと描画されるようになる。

jQuery などのDOMライブラリとの併用

も、一応可能だが、Vue.jsがDOMを直接いじる $el,$refsが同様の事ができるので、併用はできるけど、まあ、無意味化しつつあるよね。ということらしい。

まとめ

  • 使用したいデータはdataオプションに登録しよう
  • 操作するリストには不変でユニークなkey属性を設定しよう
  • 配列インデックスを使った更新はVue.setを使う
  • 関数の呼び出し方ではthisは変化することがある
  • $elと$refsはmounted以降で使う

Laravelのブラウザテストでテストメソッド毎にシーディングを毎回しない方法

前置き

アプリをある程度作り込んでから、自動テストやTDD(テスト駆動開発)を覚え、いざ自分のアプリで実践しようとした所、かなり手を入れないとろくなユニットテストができない状態という事が分りました。
なにしろユーザーのロール権限が5つもあり、権限毎に表示や動作が異なる箇所が多々ある。(どうしてこうなった)
メソッドの中で他のメソッドを呼びまくり、値を拾っては他に投げ、みたいな入出力の制御が複雑で大変危うい処理もある。
また、例えばあちらのバグをつぶすと、こちらでバグになる。とか、この権限での仕様を変えたけど、他の権限での動作や表示には対応してる?といった確認作業も多くなりました。

で、やむなく現在のアプリを自動ブラウザテストでそれぞれの権限での表示や動作を検証する、という事をしてます。要は自分で画面見て、操作して、というのを機械にやらせる処理を淡々と書く作業です。(正直しんどい)

本題

で、その際によくあるのが setUp() メソッド(テストのメソッド毎に実行される処理)に、シーディング処理を書く奴、こんなのです。

<?php

namespace Tests\Browser;
// use 省略
class Profile_superAdmin_user_Test extends DuskTestCase
{

    use RefreshDatabase;

    protected function setUp()
    {
        parent::setUp();
        Artisan::call('migrate:refresh');
        Artisan::call('db:seed');
    }
    // 以下略
}

この処理なんですが、setUp()はこの下に書かれるブラウザテストの複数のメソッド実行前に毎回実施されます。なので、DBの再構成をテストメソッド毎に毎回やると、テスト実施に結構時間がかかってしまいます。

なので、この中の処理をテストクラスの最初の1回のみ実施される、 setUpBeforeClass()を書いたのですが、理由は忘れましたがうまく行きませんでした。(BrowserテストにsetUpBeforeClass()が無かったか? Artisanコマンドが使えなかった?だったと思います。)

では上手い方法はないか?と調べた所、以下のページの記述に当たりました。

How Do I Seed My Database in the setupBeforeClass Method in a Laravel 4 Unit Test?

日本語直訳サイト
Laravel 4ユニットテストでsetupBeforeClassメソッドにデータベースをシードするにはどうすればよいですか?

<?php

namespace Tests\Browser;
// use 略

class Admin_Community_superAdmin_user_Test extends DuskTestCase
{
    protected static $db_inited = false;
    use RefreshDatabase;

    protected static function initDB()
    {
        Artisan::call('migrate:refresh');
        Artisan::call('db:seed');
    } 

    protected function setUp()
    {
        parent::setUp();
        if (!static::$db_inited) {
            static::$db_inited = true;
            static::initDB();
        }
    }
    // 略
}

setUp() メソッドをあたかもsetUpBeforeClass()の様にクラスインスタンス後に1度だけやってくれる書き方です。
$db_initedという静的プロパティを持たせてsetUp()で一度 true にしてしまったら、 setUp()内の ifの処理で以降は Artisanファサードを呼ばない。という実装。ピタゴラスイッチ的な動きですよね。こういうのって見ればそれなりに動き追えますが、自分で書ける気がしません。が、なにはともあれ解決。

しかしこれだと、テストクラス内でDBの値を変更してしまう処理を書くと、以降のテストに影響がでてしまうのですが、そこは、テストクラスを上手く分けて書いて行けば解決できそうです。

追記

その後以下の様にテストメソッド内に Artisanファサードを使ってDBのシーディングを行う処理を書くことで、さらに必要な時にだけ、シーディングが行えることがわかりました。このまま行くとsetUp()メソッドが無くても何とかなるる感じですね。

<?php

    /**
     * @test
     */
    public function DBを編集するテスト()
    {
        // 省略
    }

    /**
     * @test
     */
    public function 後処理()
    {
        $this->browse(function ($browser) {
            $browser->visit('/user/edit?id=1')
               // assert が書かれて無いとテスト完了時に警告が出る為、便宜上入れた assert
                ->assertSeeIn('.comp-title', 'プロフィール編集');
                echo 'now seeding!';
                Artisan::call('migrate:refresh');
                Artisan::call('db:seed');
        });
    } 

Let's Encrypt のTLS-SNI-01から http-01 方式への変更をした備忘録

無料で使えるSSL認証Let's Encrypt から以下の様なメールが来ました。

Action required: Let's Encrypt certificate renewals

Hello,

Action may be required to prevent your Let's Encrypt certificate renewals
from breaking.

If you already received a similar e-mail, this one contains updated
information.

Your Let's Encrypt client used ACME TLS-SNI-01 domain validation to issue
a certificate in the past 60 days. Below is a list of names and IP
addresses validated (max of one per account):

 www.livelynk.jp (160.16.207.76) on 2018-12-25

TLS-SNI-01 validation is reaching end-of-life. It will stop working
temporarily on February 13th, 2019, and permanently on March 13th, 2019.
Any certificates issued before then will continue to work for 90 days
after their issuance date.

You need to update your ACME client to use an alternative validation
method (HTTP-01, DNS-01 or TLS-ALPN-01) before this date or your
certificate renewals will break and existing certificates will start to
expire.

Our staging environment already has TLS-SNI-01 disabled, so if you'd like
to test whether your system will work after February 13, you can run
against staging: https://letsencrypt.org/docs/staging-environment/

If you're a Certbot user, you can find more information here:
https://community.letsencrypt.org/t/how-to-stop-using-tls-sni-01-with-certbot/83210

Our forum has many threads on this topic. Please search to see if your
question has been answered, then open a new thread if it has not:
https://community.letsencrypt.org/

For more information about the TLS-SNI-01 end-of-life please see our API
announcement:
https://community.letsencrypt.org/t/february-13-2019-end-of-life-for-all-tls-sni-01-validation-support/74209

Thank you,
Let's Encrypt Staff

Google翻訳にかけてみる

必要なアクション:証明書の更新を暗号化しましょう

こんにちは、

あなたのLet's Encrypt証明書の更新
が壊れないようにするための行動が必要かもしれません。

すでに同じようなEメールを受け取っている場合は、これには更新された
情報が含まれています。

Let's Encryptクライアント
が過去60日間に証明書を発行するためにACME TLS-SNI-01ドメイン検証を使用しました。以下は
検証された名前とIP アドレスのリストです(アカウントごとに最大1つ):2018-12-25の

 www.livelynk.jp(160.16.207.76)

TLS-SNI-01検証は廃止予定です。それは動作を停止します
2月13日、2019年に一時的に、かつ永久月13日に、2019年
それ以前に発行された証明書は90日間働き続ける
彼らの発行日後。


この日までに ACMEクライアントを更新して別の検証方法(HTTP-01、DNS-01、またはTLS-ALPN-01)を使用する必要があります。そうしないと
証明書の更新が中断され、既存の証明書の
有効期限が切れます。

私たちのステージング環境は、すでにあなたが好きなので、もし、TLS-SNI-01無効になってい
2月13日後にシステムが動作するかどうかをテストするために、あなたが実行することができます
ステージングに対して:https://letsencrypt.org/docs/s taging環境/

あなたがCertbotユーザーであるならば、あなたはここでより多くの情報を見つけることができます:
https://community.letsencrypt。org / t /どうやってtls-snを使うのかi-01-with-certbot / 83210

このフォーラムにはたくさんのスレッドがあります。あなたのかどうかを確認するために検索してください
質問が答えられたら、新しいスレッドを開いてください(
https://community.letsencrypt)。org /

TLS-SNI-01のサポート終了についての詳細は、当社のAPI 
発表
https://community.letsencryptをご覧ください。org / t / 2月13日 - 1919年 - すべてのtls-sni-01- validation-support / 74209

ありがとう、
スタッフを暗号化しましょう

とのことで、従来の検証方法TLS-SNI-01から HTTP-01、DNS-01、またはTLS-ALPN-01 へのいずれかへの変更が必要とのことで、ググって対応しました。

結論から言うと、先人方が既に変更方法を確立されており、同様の方法で事が済みましたが、参考にさせていただいたサイトのリンクと、その方法を張り付けておきます。
サーバー環境はさくらVPSのcentos7となります。

Let’s encryptのドメイン認証の方法をHTTP-01に変更するための準備で試行錯誤した件。
Let’s Encryptの更新エラーを直す(certbot renew失敗)
Let’s EncryptのTLS-SNI-01認証のバリデーションに伴う対応策まとめ

まず サーバーにログインして rootになって Let's Encryptを更新します。

# yum update certbot*

次に以下のサイトを参考に設定変更のコマンドを叩きました

Let’s encryptのドメイン認証の方法をHTTP-01に変更するための準備で試行錯誤した件。

# certbot renew –dry-run –preferred-challenges http-01,dns-01

ところが何やらエラーが

Traceback (most recent call last):
  File "/bin/certbot", line 5, in <module>
    from pkg_resources import load_entry_point
  File "/usr/lib/python2.7/site-packages/pkg_resources.py", line 3011, in <module>
    parse_requirements(__requires__), Environment()
  File "/usr/lib/python2.7/site-packages/pkg_resources.py", line 626, in resolve
    raise DistributionNotFound(req)
pkg_resources.DistributionNotFound: acme>=0.29.0

なんかよくわかりませんが、大事な事は最初か最後に書いてあるので、今回は最後の1行でググると以下のサイトの情報に行き当たりました。 Let’s Encryptの更新エラーを直す(certbot renew失敗)

同じエラーが載ってるのでドンピシャです。 手順も全く同じ経緯で原因の特定と解決ができました。

# yum search acme
読み込んだプラグイン:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
 * base: ftp.iij.ad.jp
 * epel: mirrors.aliyun.com
 * extras: ftp.iij.ad.jp
 * remi-safe: ftp.riken.jp
 * updates: ftp.iij.ad.jp
====================================================== N/S matched: acme ======================================================
acme-tiny-core.noarch : core python module of acme-tiny
python2-acme.noarch : Python library for the ACME protocol
acme-tiny.noarch : Tiny auditable script to issue, renew Let's Encrypt certificates
dehydrated.noarch : A client for signing certificates with an ACME server

  Name and summary matches only, use "search all" for everything.

python2-acme.noarch の更新が必要とのことで、epelからupdate

# yum --enablerepo=epel update python2-acme

依存性解決をしてアップデートができました。

再度以下のサイトを参考に本来の目的である、Let's Encrypt のTLS-SNI-01から http-01 方式への変更を行います。
Let’s EncryptのTLS-SNI-01認証のバリデーションに伴う対応策まとめ

本当に最新か確認

# certbot --version
certbot 0.29.1

現時点でリンク先のバージョンと同じなので、まず大丈夫でしょう。 そして

新しいバージョンの場合は自動的にHTTP-01の認証が実行されるようです。 とありますが、一応リンク先同様、 -dry-run オプションで更新テストをしてみます。

# certbot renew --dry-run --preferred-challenges http

出力内容

Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/www.livelynk.jp.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cert not due for renewal, but simulating renewal for dry run
Plugins selected: Authenticator apache, Installer apache
Starting new HTTPS connection (1): acme-staging-v02.api.letsencrypt.org
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for www.livelynk.jp
Waiting for verification...
Cleaning up challenges
Resetting dropped connection: acme-staging-v02.api.letsencrypt.org

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed with reload of apache server; fullchain is
/etc/letsencrypt/live/www.livelynk.jp/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/www.livelynk.jp/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

IMPORTANT NOTES:
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.

Performing the following challenges:

http-01 challenge for www.livelynk.jp

//中略

Congratulations, all renewals succeeded. The following certs have been renewed:

翻訳:おめでとうございます、すべての更新は成功しました。以下の証明書が更新されました。

という訳で無事完了。普段サーバーの設定関連は散々ググって悩んで解決するのですが、今回は皆が同じ対応を迫られている時期だったこともあり、とても簡単に問題が解決できました。良かった。