6時間ぐらいのフライトで羽田からバンコクのスワンナプーム空港に移動。

Image from Gyazo

飛行機に乗るときに、これまではでっかいモバイルバッテリーを持ち込んでスマホやタブレットのバッテリー切れに備えてたんですが、iPhoneもiPad miniも全然バッテリーは持ったので進化を感じました。

滞在するホテルは日本人がたくさん住んでるらしいスクンビット(sukhumvit)エリアにあります。日系の店や看板表記がたくさんあります。

ホテルのミスかわからないけど俺の名前が明らかに中国名のチャンなんとかさんになっていて揉めるが無事チェックイン。

初日は移動ですごく疲れたのでターミナル21というショッピングモールのフードコートでご飯を食べて就寝。

Image from Gyazo

普通のパッタイが30バーツ(約120円)。安いな〜。ちょっと甘めなので辛いやつとかかければよかった。

明日(9日)から20日までタイのバンコクに行ってきます。

コロナ前にマレーシアのクアラルンプールに行ったぶりの海外です。

クアラルンプール 1日目 - komagataのブログ

夫婦であったかい国に移住したいな〜と思ってるので、色んな国を見てみたいと思って試しています。

いつものペットシッターさんにお願いしているのと、監視カメラがあるので大丈夫だと思いますが、猫の長期間留守番が心配です。

Image from Gyazo

ss

淡海乃海 水面が揺れる時を5巻まで読みました。

戦国大名に転生したら…というよくあるやつなんだけど、転生先が朽木元綱。誰?

基本的にストーリーはほぼ近江北部(琵琶湖の上の方)だけで進みます。戦国もので良く描かれる織田とか徳川とかほとんど出てきません。近江なんで基本的に浅井と六角、あと三好とか。

確かに近江って超重要な土地っぽいけど全然知らなかったので面白くてためになります。あの有名な六角氏の観音寺騒動が起きるのか否か…?とか桶狭間の戦いみたいなメジャー事件の感じで言われても俺知らないし…。

夫婦でスマホの回線をIIJmioのファミリーシェアプランからahamoに変えました。

会社の電話をスマホに転送してるので、海外に行く時困ると思ってたんですが、ahamoなら20GB普通に使えるとのことで変えました。

これまでは海外旅行の時は3〜4GBぐらいのSIMを買っていて、ネットが使えないと詰む環境で新しいSIMにするのに緊張してました。

海外行くと、その国でしか使えない便利サービスに登録するのにSMS認証が必要になったりして意外と面倒だったのですが、これで楽になりそうです。

本番環境の状態を見る必要があって稀に(3ヶ月に一回ぐらい)必要になるんだけど、毎回忘れてるのでメモ。

Cloud SQL Auth Proxyを起動する。

$ cloud_sql_proxy -instances=xxxxxxx-123456:asia-northeast1:xxxxxx=tcp:5433

ローカルのpostgresも使いたいので5433をproxyする。

railsのdatabase.ymlの設定は下記のようにする。

development:
  <<: *default
  database: xxxxxxxx_development
  host:     '127.0.0.1'
  port:     5433
  database: 'xxxxxxxx_production'
  username: 'xxxxxxxx'
  password: 'xxxxxxxx'

hostはlocalhostではダメで127.0.0.1じゃないとダメなのがハマりポイント。 毎回、「あ〜そうだったなぁ〜」って思い出すまでに試行錯誤に1時間ぐらいかかる。

abstract_notifierで通知を実装する - komagataのブログ

ここでminitestでのテストが書きづらいのでPRするって書いてましたが、それがマージされたバージョン0.3.2がリリースされました。

READMEに書いてありますがminitestではこういうふうに書くといい感じです。

require 'abstract_notifier/testing/minitest'

class EventsNotifierTestCase < Minitest::Test
  include AbstractNotifier::TestHelper

  test 'canceled' do
    assert_notifications_sent 1, identify: "123", body: "Alarma!" do
      EventsNotifier.with(profile: profile).canceled(event).notify_now
    end

    assert_notifications_enqueued 1, identify: "123", body: "Alarma!" do
      EventsNotifier.with(profile: profile).canceled(event).notify_later
    end
  end
end

去年MacBook Pro買ったときからハードウェアに関する物欲がスッと消えてる気がする。

MacBook Pro16インチ買った - komagataのブログ

MacBook Proが良すぎる!ベタ惚れ!って感じではなく、

「イラつく欠点が無い」「困る点が全くない」

という感じでハードのことを考える時間が減った。

このMacBook Proが特別というより、俺のパソコンでやる作業の用途・OSのこなれ感・パソコンの処理速度・ディスプレイ・キーボード・トラックパッドが調和したタイミングハードへ意識を向けることが少なくなり、単なる道具化するんだと思う。

昔もこういうタイミングあったな〜と思い出すのは、ThinkPad X61でWindows XPとcoLinuxを使ってた2008年ごろのセットアップ。

この時もOSもこなれてたし、ディスプレイ・キーボード・ポインティングデバイスに不満が一切なくてハードへの興味が薄れたなぁ。

ソフトウェアに集中できるのと良い状態だと思います。

Callbacksの問題点についてはこちら。 ActiveRecordのObserversやCallbacksの問題点 - komagataのブログ

Callbacksの問題点を解決する薄いpub/subライブラリのnewspaper gemを作りました。

こういうのを

class User
  after_create UserCallbacks.new
end

class UserCallbacks
  def after_save(user)
    # do something
  end
end

@user.save

こう書き換えられます。

# app/models/sign_up_notifier.rb
class SignUpNotifier
  def call(payload)
    # do something
  end
end

# config/initializers/newspaper.rb
Newspaper.subscribe(:user_create, SignUpNotifier.new)

# app/controllers/users_controller.rb
Newspaper.publish(:user_create, payload)

SubscriberはただのRubyオブジェクトなのでテストし易いです。

既存のpub/subライブラリのように非同期実行する機能などはありません。また、ちゃんとしたPublisher / Subscriberパターンのようなクラス構成にはなっておらず、簡単な構造になっています。

このgemを作ってるところの動画

僕らがやっているプログラミングスクールのフィヨルドブートキャンプでは最近ペアプロやモブプロが盛んに行われています。もっと敷居を下げるために「このgemを作っているところ」をモブプロでやって録画しました。

オープソースで公開しているフィヨルドブートキャンプ本体のCallbacks をこのgemに置き換えるところもやりました。

簡単なgemなのでRubyプログラマーの人にはあまり面白く無いかもですが、今Ruby入門中の人やgemを作りたい人の参考になれば幸いです。

2点あると思う。

  1. Callbackを呼ばれて欲しくない時が結構ある。暗黙的にそれが起こるとキツイ。
  2. 複数の処理を一箇所に書いちゃいがち。複数の処理に依存関係があるとキツイ。

Serviceクラスの問題点

明示的に呼ぶので1をクリアしてる。 2をクリアしてない。

しっかり処理毎にクラスに分けてServiceクラスから呼び出してればいいが、Serviceクラスの粒度がCallbackのエントリポイントと変わりない場合が多いので複数の処理を一箇所に書いちゃいがち。

pub/subを使う

明示的に呼ぶので1をクリアしている。 いかにも処理毎にクラスを作るようにできてるので2をクリアしている。

既存のpub/subライブラリ

これでいいんだけど、この用途には非同期で実行する機能はオーバースペックに感じる。 (特によくあるユースケースの通知などではそちらの方で非同期の仕組みを備えているため)

薄いpub/subライブラリ

それを解決するために、非同期機能を持たず、イベントに文字列(シンボル)を使うpub/subライブラリを作りました。

komagata/newspaper

newspaperについて詳しくは下記エントリーに書きました。

newspaperでActiveRecordのCallbacksを置き換える - komagataのブログ

active_delivery gemがいい感じ。

active_deliveryはActionMailer 的な処理 をまとめるWrapperです。

例によってWeb上にはREADMEと作者の方のブログエントリーしか見当たらなかったので、使い方を知るにはソース読んだ方が早いです。僕が代わりに読んでおいたのでちょっとしたプラス情報を書いておきます。

使い方

def after_signup
  ActivityMailer.user_signup.deliver_later if user.receive_emails?
  DiscordNotifier.user_signup.notify_later if user.receive_discord?
end

こんな感じに書いてたのを

def after_signup
  ActivityDelivery.notify(:user_signup, user)
end

こんな感じに描けるようになります。

ActivityDelivery.notify!(:user_signup, user)

また、デフォルトでは非同期の方を呼び出しますが、notify!の方だと同期版を呼んでくれます。

ActivityDelivery.with(user: user).notify(:user_signup)

そして、ActionMailerのようにParameterizedな呼び方もできます。

概要

直接使うDeliveryクラスと実際に動くMailerクラス(やその他の独自のDeliveryクラス)、その間をつなぐLineクラスがあります。

Image from Gyazo

デフォルトで使われるActiveDelivery::Lines::Mailerを見るとどうやって間を繋いでいるのかがわかります。

実装

これを用意しておくだけで既に、

# app/deliveries/activity_delivery
class ActivityDelivery < ActiveDelivery::Base
end

下記が呼べてメールが送信されます。

ActivityDelivery.notify(:user_signup, user)

何故かとういうと、デフォルトでこういうクラスのメソッドを探してメソッドを呼んでくれるからです。

class_name.gsub(/Delivery$/, "Mailer")

Mailerはデフォルトで呼ぶんですね。

class ActivityDelivery < ActiveDelivery::Base
  unregister_line :mailer
end

こうしておけばデフォルトのMailerを呼ぶのもなしにできます。

独自のDeliveryクラスを使うには自分でLineクラスを書く必要があります。

しかし、abstract_notifierを使って作った通知クラスがあれば、そちらにActiveDelivery::Lines::Notiferクラスが同伴されているので自分で書く必要はないです。便利!

abstract_notifierについてはこちらのエントリーを見てください。

abstract_notifierで通知を実装する - komagataのブログ

class ActivityDelivery < ActiveDelivery::Base
  register_line :discord,
                ActiveDelivery::Lines::Notifier,
                resolver: ->(_) { DiscordNotifier }
end

これでActivityMailerとDiscordNotifierのメソッドを一緒に呼んでくれるようになります。

テスト

テストはこんな感じで同期版・非同期版、普通版・Parameterized版をそれぞれテストしておけば安心だと思います。

require 'test_helper'

class ActivityDeliveryTest < ActiveSupport::TestCase
  test '.notify(:user_signup)' do
    params = {
      body: 'user signup!'
      user: users(:foo)
    }

    assert_difference -> { AbstractNotifier::Testing::Driver.deliveries.count }, 2 do
      ActivityDelivery.notify!(:user_signup, **params)
    end

    assert_difference -> { AbstractNotifier::Testing::Driver.enqueued_deliveries.count }, 2 do
      ActivityDelivery.notify(:user_signup, **params)
    end

    assert_difference -> { AbstractNotifier::Testing::Driver.deliveries.count }, 2 do
      ActivityDelivery.with(**params).notify!(:user_signup)
    end

    assert_difference -> { AbstractNotifier::Testing::Driver.enqueued_deliveries.count }, 2 do
      ActivityDelivery.with(**params).notify(:user_signup)
    end
  end
end