RSpecでブラウザのダイアログを操作する方法

ブラウザのダイアログ

Railsエンジニアにはおなじみではありますが(*)、リスクのある操作を行う際に、以下のようなブラウザのダイアログを表示することで確認を促す処理はケースとして良くあることと思います。(画像はGoogle Chromeの例。)
f:id:kazukiyunoue:20180613114850p:plain
今回はそのような処理をRailsでスタンダードなテストフレームワークであるRSpecで実行させたいと思います。(*:Railsの持つ自動コード生成機能が生成するリソース削除の処理が、まさにブラウザのダイアログを表示し、"Are you sure?"と確認を促す流れとなっている。今回の題材もそれ。)

テスト時の環境

ライブラリ バージョン
Ruby 2.5.1
Rails 5.2.0
RSpec 3.6.0
Capybara 3.2.1
Javascript Driver selenium_chrome

実際のコード

require 'rails_helper'

RSpec.feature "Tasks", type: :feature do
  scenario "User can delete a task", js: true do
    create(:task)

    visit tasks_path

    click_link "Destroy", match: :first
    expect {
      page.accept_confirm "Are you sure?"
      expect(page).to have_content "Task was successfully destroyed."
    }.to change { Task.count }.by(-1)
  end
end

pageインスタンスにaccept_confirmとdismiss_confirmというブラウザのダイアログ操作用メソッドが用意されていて、引数にダイアログで表示されるテキスト(今回のケースでは"Are you sure?")を入れることで、該当のダイアログをacceptまたはcancelできるようです。ちなみに"OK"ボタンしか表示されないようなダイアログ用と思われるaccept_alertもあり、今回のケースだとacceptさせたい場合はaccept_alertでも実行できました。RSpecではおなじみのテクニックではありますが、expectのブロック内にひとつ以上のexpectもしくはfindを入れないと、ダイアログが表示されてacceptされる前に次へ進んでしまうので注意が必要です。

macOS純正メモアプリ”メモ”(英名:Notes)で特定のメモを別ウインドウで開く方法

あまり注目してる人は少ないかもしれないmacOS純正メモアプリ”メモ”(英名:Notes)。軽快な動作でありながら、手書きや画像の添付、リッチテキスト対応など機能性も高いメモアプリでもあります。

特定のメモを別ウインドウで開きたい場合は、

f:id:kazukiyunoue:20170529144750p:plain

画像のように、赤枠で囲われた部分をダブルタップ(ダブルクリック)することで、

f:id:kazukiyunoue:20170529144838p:plain

このような別ウインドウでメモを開くことができます。メモを比較したり、参照したりするときに便利ですね!

MySQLv4.0(!)で、DISK容量の空きは十分なのにThe table is fullエラーが出た時の話

あるMyISAMテーブル(ここではtest1とします)を、スキーマ変更のため一時テーブル(test2)へコピーしようとした時、

mysql> create table test2 select * from test1;
ERROR 1114 (HY000): The table 'test2' is full

のようなエラーが発生した。

test1はそこそこ大きなテーブルなので、MySQLがDATA領域(tmp領域を含む)として使うDISKの空きを確認するが、十分なスペースがあった。

調べてみると、
どうやらcreate table A select * from B構文だとMAX_ROWSの値は受け継がれず、
デフォルトの領域しか割与えられない仕組みのよう。

試しに1件だけ挿入すると

mysql> show create table test1\G
*************************** 1. row ***************************
       Table: test1
Create Table: CREATE TABLE `test1` (
  `id` int(11) default NULL
) TYPE=MyISAM MAX_ROWS=100000000
1 row in set (0.00 sec)

mysql> create table test2 select * from test1 limit 1;
Query OK, 1 rows affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> show create table test2\G
*************************** 1. row ***************************
       Table: test2
Create Table: CREATE TABLE `test2` (
  `id` int(11) default NULL
) TYPE=MyISAM
1 row in set (0.00 sec)

test2からはMAX_ROWS=100000000の設定が消えてしまっている。

そこでcreate table A select * from B構文は使わず、

CREATE TABLE `test2` (
  `id` int(11) default NULL
) TYPE=MyISAM MAX_ROWS=100000000;

でテーブルを先に作成した上で、

INSERT INTO test2 SELECT * FROM test1;

とすると、無事コピーできた。

v4.0!

ActiveResourceでauth_token的なトークンで認証する方法

Rails4からは正式にプリインストール対象から外れたActiveResourceではありますが、アクセス先が絵に描いたようなRESTfulAPI、とりわけ開発者が内情を知ってるRailsアプリ間の連携等では今なお大いに力を発揮するもの。

しかし、

http://example.com/posts.json?auth_token=TOOOOKEN

このような形でのトークンの認証には対応していない。
そこで、

blog.mmmcorp.co.jp

を大変参考にさせていただき、モンキーパッチをあて、トークン認証に対応させる。

ActiveResourceが発行するURLのクエリーストリングを生成するメソッド、

def query_string(options)
        
  "?#{options.to_query}" unless options.nil? || options.empty?

end

というのがあるので、これをオーバーライドする。

全体ではこうなる。

class ActiveResource::Base
  class << selfdef query_string(options)

      if auth_token

        options.store(:auth_token, auth_token)

        "?#{options.to_query}" unless options.nil? || options.empty?

      elseraise ArgumentErrorendend
    def auth_tokenreturn @auth_token if defined?(@auth_token)

      nilend
    def auth_token=(auth_token)

      @auth_token = auth_token

    endend

end

下2つが単純な属性"auth_token"に対するゲッター、セッターメソッド。そしてquery_stringメソッドを、発行するURLのクエリーストリングになるハッシュ"options"にauth_tokenの要素を追加し、ない場合はArgumentErrorの例外を発生させるようにした。

このパッチを、Railsではlib配下等、読み込み対象ディレクトリに設置し、

class Post < ActiveResource::Base
  self.site = "http://example.com"
  self.auth_token = "TOOOOOOOOKEN"
  self.format = :xml
  self.timeout = 15
end

Post.all

このようにモデル側を実装すれば、トークンでの認証に対応が可能。

(当パッチはActiveResourceすべての処理において検証はしておりません。
ご利用の際はご注意ください。)

MySQLのv4.0からv5.6へダンプファイルをインポートする際にTIMESTAMPがずれる場合の対処

MySQLをバージョン4.0からバージョン5.6へ移行するため、mysqldumpを使ってダンプファイルをv4.0から取得し、v5.6に投入した際、timestamp型のカラムの時間がズレる事象が発生した。

タイムゾーンの設定はどちらもJSTである。

v4.0

mysql> show variables like '%time%zone%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| timezone      | JST   |
+---------------+-------+
1 row in set (0.00 sec)

v5.6

mysql> show variables like '%time%zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | JST    |
| time_zone        | SYSTEM |
+------------------+--------+
2 rows in set (0.00 sec)

原因はv4.0からダンプファイルを取得する際、v5.6のmysqldumpを使用したため、"tz-utc"という

異なるタイムゾーンのサーバ間でTIMESTAMPカラムをダンプしてリロードできる

https://dev.mysql.com/doc/refman/5.6/ja/mysqldump.html#option_mysqldump_tz-utc

機能が有効に働いたのにもかかわらず、
v4.0側はこの概念がないため、

/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;

ダンプファイル内に上記のようなタイムゾーンの設定が入っているが、実際のTIMESTAMPカラムは生データのままになってしまい、v5.6側に投入すると時間がズレていたもよう。

どちらのサーバも時間設定は同じなので、mysqldumpのオプションである"tz-utc"を無効にする"skip−tz-utc"を付与することで、無事時間のズレなく投入できた。

pagerdutyで復旧通知メールによってインシデントを発生しないようにしたり自動的にインシデントをResolved(リゾルブ)に変えたりする方法

pagerdutyとは?

言わずと知れたシステムアラートの通知管理サービスです。メールやAPIサードパーティの監視システムからの信号をきっかけに、担当者に電話やSMS、メール等で優しく、時に厳しく連絡を送ってくれるものです。連絡する担当者をローテーションや順番等を細かく設定できるのも大きな特徴です。pagerduty側に送られた信号はインシデントという単位で、それぞれの状態を、"Triggered"(発砲)、"Acknowledged"(認知)、"Resolved"(解消)などのステータスで管理できます。

www.pagerduty.com

復旧通知メールを無視する方法

システムのメトリクス監視の中には、ロードアベレージやセッション数など、閾値を超えた場合発砲通知メールを送信し、その後は発砲せず、閾値を下回ったら復旧通知メールを送信して、その後は通常通り発砲するタイプのものがあります。この場合、まず復旧通知メールをpagerduty側でインシデントとして認識させないようにする必要があります。pagerdutyにはメールやAPI等で送られた信号を条件に合致した場合にインシデントを生成する、またはインシデントを解消するルール設定が可能です。Edit Serviceページにあります。

f:id:kazukiyunoue:20160115170946p:plain
f:id:kazukiyunoue:20160115171004p:plain
f:id:kazukiyunoue:20160115171013p:plain

例えば、復旧通知メールに必ず"[Resolved]"というキーワードが入る場合は、以下のような条件を設定すると、復旧通知メールを無視できます。

f:id:kazukiyunoue:20160115172819p:plain

厳密には無視をする機能ではありません。"[Resolved]"のキーワードが入った件名の文言全てと合致する発生済みインシデントをResolved状態にさせようとしていて、当然そのようなインシデントはありえないので、実質無視する形になります。

復旧通知メールでインシデントをResolvedにする方法

こうすると、監視システム側では解消となっているのにもかかわらず、pagerduty側ではまだResolvedになっていません。pagerdutyには、親切にも設定された時間を経過すると改めて通知する機能があって、深夜に障害は解消しているのに、電話でまた起こされるなんていう羽目になってしまいます。そこで、先ほど説明したように、条件に合致した発生済みインシデントをResolvedにする機能を使って、自動的にResolvedに変えたいと思います。合致の条件は以下のような種類が選べます。

f:id:kazukiyunoue:20160115171116p:plain

例えば発砲通知メールを、

[Alert] Xxxxxx is Xxxxxx

として、復旧通知メールを、

[Resolved][Alert] Xxxxxx is Xxxxxx

とします。この場合、以下のようにルールを設定すると、

f:id:kazukiyunoue:20160115172840p:plain

あら不思議、復旧通知メールをきっかけにpagerduty側のインシデントもResolved状態になりました。

まとめ

これでpagerdutyの更新忘れで無駄に起こされることがなくなりましたね。サードパーティ製の監視ツールの場合、pagerduty側での受け入れ体制が整っているので、もともと対応している場合もあります。独自の監視システムをお持ちの方のご参考になれば幸いです。