本番環境の状態を見る必要があって稀に(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点あると思う。
- Callbackを呼ばれて欲しくない時が結構ある。暗黙的にそれが起こるとキツイ。
- 複数の処理を一箇所に書いちゃいがち。複数の処理に依存関係があるとキツイ。
Serviceクラスの問題点
明示的に呼ぶので1をクリアしてる。 2をクリアしてない。
しっかり処理毎にクラスに分けてServiceクラスから呼び出してればいいが、Serviceクラスの粒度がCallbackのエントリポイントと変わりない場合が多いので複数の処理を一箇所に書いちゃいがち。
pub/subを使う
明示的に呼ぶので1をクリアしている。 いかにも処理毎にクラスを作るようにできてるので2をクリアしている。
既存のpub/subライブラリ
- krisleech/wisper: A micro library providing Ruby objects with Publish-Subscribe capabilities
- Kris Leech / ma · GitLab
これでいいんだけど、この用途には非同期で実行する機能はオーバースペックに感じる。 (特によくあるユースケースの通知などではそちらの方で非同期の仕組みを備えているため)
薄いpub/subライブラリ
それを解決するために、非同期機能を持たず、イベントに文字列(シンボル)を使うpub/subライブラリを作りました。
newspaperについて詳しくは下記エントリーに書きました。
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クラスがあります。
デフォルトで使われる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
abstract_notifier gemがいい感じ。
abstract_notifierはPush Notificationのようなテキストベースの通知をActionMailerと同じような感じに実装するためのgemです。色々やってくれるというより、名前の通り枠組みを提供する感じです。
実装
要するにabstract_notifierに従って実装しておくと、
DiscordNotifier.user_signup(user).notify_now
DiscordNotifier.user_signup(user).notify_later
こんな感じにActionMailerのように同期版、非同期版を選んで実行できたり、
DiscordNotifier.with(user).user_signup.notify_later
こんな感じにActionMailerのようにParametarizedな呼び方ができます。
弊社のフィヨルドブートキャンプアプリではサイト内通知とDiscord通知をこれを使うものに置き換えました。
class DiscordNotifier < AbstractNotifier::Base
self.driver = DiscordDriver.new
self.async_adapter = DiscordAsyncAdapter.new
end
実装は、AbstractNotifier::Base
を継承したクラスで同期処理用のdriver
と非同期用のasync_adapter
を実装してセットしておくとOKです。
普通のRubyクラスでOKですが、それぞれcall
メソッドとenqueue
メソッドを実装している必要があります。両方ともAbstractNotifier::Base
が持ってるnotification
を経由して呼び出されます。
普通は通知でやりたいことは共通だと思うので、同期用のdriver
は普通に書いて、非同期用のasync_adapter
はActiveJob経由でdriverを呼び出すように書いておけば実装が共通にできていいと思います。
driverはこんな感じ。
class DiscordDriver
def call(params)
Discord::Notifier.message(
params[:body],
username: params[:name],
url: params[:webhook_url]
)
end
end
async_adapterはこんな感じ。 Parameterized版はコンストラクタが呼ばれるのでenqueueでもそれが使われるように書いておく必要があります。
class DiscordAsyncAdapter
def initialize(params = {})
@params = params
end
def enqueue(_, params = {})
params.merge!(@params)
DiscordJob.perform_later(params)
end
end
jobはこんな感じにしておくだけで非同期にできて便利ですね。
class DiscordJob < ApplicationJob
queue_as :default
def perform(params)
DiscordDriver.new.call(params)
end
end
テスト
テストは同期版、非同期版、Parameterized版をそれぞれテストしておくと安心です。gemにはrspec向けのhelperしかないですが、AbstractNotifier::Testing::Driver.deliveries
とAbstractNotifier::Testing::Driver.enqueued_deliveries
にそれぞれdeliverしたものが入るようになっていたのでminitestではこれらを直接見る様に書くといいんじゃないかと思います。
長いのでassert_delivery
とかあった方が便利そうなのでminitest_helperを後でPRしようかなと思います。
2022年06年04日:マージされました。
abstract_notifierをminitestでテストする - komagataのブログ
require 'test_helper'
class DiscordNotifierTest < ActiveSupport::TestCase
setup do
@params = {
body: 'new user signup!',
sender: users(:bob),
name: 'bob',
webhook_url: 'https://discord.com/api/webhooks/0123456789/xxxxxxxx'
}
end
test '.user_signup' do
notification = DiscordNotifier.user_signup(@params)
assert_difference -> { AbstractNotifier::Testing::Driver.deliveries.count }, 1 do
notification.notify_now
end
assert_difference -> { AbstractNotifier::Testing::Driver.enqueued_deliveries.count }, 1 do
notification.notify_later
end
# Parameterized
notification = DiscordNotifier.with(@params).user_signup
assert_difference -> { AbstractNotifier::Testing::Driver.deliveries.count }, 1 do
notification.notify_now
end
assert_difference -> { AbstractNotifier::Testing::Driver.enqueued_deliveries.count }, 1 do
notification.notify_later
end
end
end
ちなみにこのgemに関する情報は作者本人のブログエントリーとREADMEぐらいしか見当たらなかったのでコード読んだ方が早いです。
その後
「別に同期・非同期切り替える必要ないし、似たような実装で今動いてるから置き換えるまではいかないかな〜」
と思うかもですね。
しかし僕はこれで通知という視点で抽象化するというアイデアを知るまではDiscord通知とメール通知を同じものだと思ってなかったので共通化してスッキリしました。他のメンバーに通知を実装してもらう時もオリジナル実装よりもこういうgemになっている方がルールが明確でよさそうに思います。
そして真の目的はこれに対応させておくと後でエントリーを書こうと思っているactive_deliveryでシュッと使えるようになる点です。
つづく
PiroさんからITエンジニア1年生のためのまんがでわかるLinuxをご献本いただきました。ありがとうございます!
こちらは僕らがやっているフィヨルドブートキャンプでも参考書籍にさせていただいている「まんがでわかるLinux シス管系女子」にLinuxの本当の入門者用の内容を追加して新しくした感じの本になっています。(今から買うなら今回の新しい方一択です)
完全に「新人教育する人の目線」になっちゃうんですが、Linuxに入門する人にとってより入りやすくなっていて素晴らしかったです。
弊社スクールでもLinuxを入門するときに、
「Terminalをなぜ使うのかわからない」 「どういうシチュエーションでみんな使うのかわからない」
などイメージがしづらいという部分でつまづく人が多くいます。
新人が入社したところから仕事で必要になって一つづつ学んでいくというストーリーがまんがで描かれることによってその辺りのイメージがとっても掴みやすくなっていると思います。
Next.jsと組み合わせる永続化のAPIって何が一般的でしょう?
僕が開発するなら何でもいいんですが、フィヨルドブートキャンプのJavascriptコース最終盤のカリキュラムで生徒の方もスクラムで開発するものになるので、そこで使う技術はカリキュラムとしても用意する必要があります。
考え方としては、Javascriptのプログラマーとして就職したら遭遇するよくある構成がいいなと思っています。
Next.jsを使うというのは決定していて、DB・API部分の選択肢が豊富で迷っちゃいます。
- Firebase(ちょっとしたサイトならいいが、しっかりしたサービスだと避けるイメージ)
- OpenAPI(サーバーサイドがガッツリし過ぎ感)
- GraphQL(最有力か?)
みなさんは何を使ってますかね〜?