active_model_serializersをActiveModelに置き換える提案

active_model_serializers (AMS) rails-api/active_model_serializers: ActiveModel::Serializer implementation and Rails hooks は積極的にメンテナンスがされない宣言がされており、かつ、高機能で複雑な処理があるため、将来のRailsバージョンアップの障害となるリスクがあります。そのため、AMSライブラリを剥がす検討をしました。

切り替え候補とそれらの評価は以下。

  • jbuilder
    • Rails標準なので安心感がある
    • AMSからは差分が大きいので移行が大変
  • ActiveModel::Serializers::JSON
    • Rails標準なので安心感がある
    • AMSの表記に比較的近くできそう
  • そのほかのシリアライザーgem
    • サプライチェーン攻撃のリスクを考えると、標準でできることに対して別の依存を入れるのは強い動機がないと選択しにくい
    • 今なら alba などが良さそうではある

ということなので、ActiveModel::Serializers::JSONを選択することにしました。

active_model_serializersをActiveModel::Serializers::JSONに置き換えるのは、先行研究として active_model_serializersをRails標準のActiveModel::Serializers::JSONで置きかえる がありますが、もう少し表記を共用できる方法を考えてみます。

要件

  • アソシエーションは諦める
  • attributeでの定義の互換性は維持したい
  • attributeのifオプションも維持したい
  • コントローラーからの利用時は明示的に to_json を呼ぶのでいい

結論

以下のようなベースクラスを用意するとAPIの互換性をある程度保つことができました。

# frozen_string_literal: true

# active_model_serializersの開発が滞っているので代替手段を用意する
# 単純に ActiveModel::Model の機能でできるところを再現。
# アソシエーションの対応が難しいのでそれは諦める。
class BaseSerializer
  include ActiveModel::Model
  include ActiveModel::Attributes
  include ActiveModel::Serializers::JSON

  class << self
    attr_reader :conditions

    # ifオプションに対応したいのでattributeメソッドを拡張。
    # ActiveModel::Attributesからメソッドシグネチャをコピー。メソッドシグネチャが変わったら追随しないといけない
    # https://github.com/rails/rails/blob/7-0-stable/activemodel/lib/active_model/attributes.rb#L19
    def attribute(name, cast_type = nil, default: NO_DEFAULT_PROVIDED, **options)
      @conditions ||= {}
      @conditions[name.to_sym] = options[:if] if options[:if]
      super
    end

    # AMSはattributesメソッドだったがActiveModel::Modelのメソッドと衝突するので名前を変更する
    def attribute_multi(*requested_attrs)
      requested_attrs.each do |requested_attr|
        attribute requested_attr
      end
    end
  end

  # initializeをオーバーライド
  # 難しいケースが出てきたら別のメソッドに分ける
  def initialize(resources)
    # Activemodelとしての初期化も必要
    super()
    @resources = resources
    attribute_names.each do |attr_name|
      next unless resources.class.method_defined?(attr_name)
      next unless assignable?(attr_name)

      send("#{attr_name}=", resources.send(attr_name))
    end
  end

  # AMSとの互換のために用意
  def object
    @resources
  end

  private

  def assignable?(attr_name)
    condition = self.class.conditions[attr_name.to_sym]
    # 条件がなければ一律でアサイン可能
    return true unless condition

    case condition
    when Proc
      instance_exec(&condition)
    when Symbol
      send(condition)
    end
  end
end
# frozen_string_literal: true

class AppleSerializer < BaseSerializer
  attribute :id
  attribute :name
  attribute :color, if: :red?
  attribute_multi :created_at, :updated_at

  def red?
    true
  end
end

利用方法は以下。

irb(main):035> as = AppleSerializer.new(apple)
irb(main):040> as.name
=> "サンふじ"
irb(main):043> as.to_json
=> "{\"id\":4,\"name\":\"サンふじ\",\"color\":\"\",\"created_at\":\"2021-10-07T15:27:18.000+09:00\",\"updated_at\":\"2022-09-15T15:46:54.000+09:00\"}"

実装のメモ

Rubyのブロック、Procはクロージャ的に宣言時のコンテキストを保持してくれるのがいいところですが、今回のケースだと attribute メソッドの実行時ではなく、 initialize の実行時のコンテキストで実行してほしいです。なので、 BasicObject#instance_exec が用意されてるのが助かりました。これだと実際のモデルクラスの値に応じて属性を設定するかを判断できます。

BasicObject#instance_exec (Ruby 3.3 リファレンスマニュアル)

Rails 7.0~8.0あたりでのi18nの設定はどうあるべきか

対象のRailsバージョン: 7.0〜8.0くらい

結論

application.rb は以下の設定でいいと思います。 en も必要なければjaだけでOK.

config.i18n.available_locales = %i[en ja]
config.i18n.default_locale = :ja

config.i18n.fallbacks は、自動生成された produciton.rbconfig.i18n.fallbacks = true が入っているので以下のように評価されます。 これで、 en のリソースがなければ ja のリソースが使われます。

irb(main):001> I18n.fallbacks
=> {:en=>[:en, :ja], :ja=>[:ja]}

細かいところいろいろ

config.i18n.raise_on_missing_translations

デフォルト値はfalse なので、development/testでは true にした方がいいです。

config.i18n.load_path はネストしたリソースも読み込む

Railsガイドに以下の記述があるけど、これは誤りで、v7.0からlocales配下のネストしたリソースも自動で読み込まれます。

The default locale loading mechanism in Rails does not load locale files in nested dictionaries, like we have here. So, for this to work, we must explicitly tell Rails to look further:

Railsのデフォルトのロケール読み込みメカニズムでは、ここで使っているようなネストした辞書に含まれるロケールファイルを読み込みません。そのため、Railsでこれらが読み込まれるようにするためには、以下のように明示的に指定する必要があります。

# config/application.rb
config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}")]

ドキュメント修正のPRがすでにマージされています。 Remove note about nested locale folders configuration by pixeltrix · Pull Request #53966 · rails/rails

i18nバックエンドの実体は ruby-i18n/i18n

Railsi18nバックエンドは ruby-i18n/i18n: Internationalization (i18n) library for Ruby 。なので、ルックアップの挙動や詳細を知りたい場合はこのリポジトリのコードを読むのがいいです。

伊藤 比呂美 「野犬の仔犬チトー」を読んだ

詩人の人らしく、文章が上手なのでスルスル読めた。挿絵もとても良くて、写実とは違うけど雰囲気がよく伝わってくる。周囲の人と頼り頼られている描写もよくて、周りの人間と良いコミュニティを築けているのも微笑ましい。

犬や猫と暮らす不自由さを選ぶ気持ちの一節が興味深い。大きな生き物の世話をすることで自分が求められている状態を作りたいという気持ちも理解はできる。とはいえ、自分が老齢で独りになったとして動物を飼うのはあんまり考えられないかな。

生き物はみんな違うので一概に言えるものではないというのはよくわかる、けど、やはりリードをつけられずに病院にも外の散歩にも行けないのは違うんじゃないか。本文に書いてある通り、年をとってしかも独り暮らしで、やり切る気力が生み出せないというのもわかるけど、それというか、嫌われてもやり切る気合いが老齢になって発揮できないんだろうなと思った。

最後まで読んで、リードをつけられてめでたしめでたしというのとは違うカタルシスが来るのもびっくり。犬が増えたよ。

Oracle Cloud (OCI)の無料枠でマインクラフトサーバーを建てて運用して、ワールドデータもバックアップする

子供とマイクラをやり始めて、時間を合わせずに進められたほうが便利なのでサーバーを建てることにした。

OCI側の用意

armを使いたかったけど全然空きがない。AMDでも動くことは動くのでこちらにする。

bedrock serverのインストールから起動まで

インストール

sshでログインして作業する。

mkdir minecraft
cd minecraft/
curl -O https://minecraft.azureedge.net/bin-linux/bedrock-server-1.20.51.01.zip

sudo apt install unzip
unzip bedrock-server-1.20.51.01.zip -d bedrock-server-1.20.51.01

ln -s bedrock-server-1.20.51.01 latest

バージョンアップもあるので、シンボリックリンクを作成している。これで、 /home/ubuntu/minecraft/latest がサーバー一式のフォルダーになった。

iptables

iptablesでポートを開ける。上記の通り、OCI側でセキュリティグループでの許可も設定済みなので、これでクライアントからサーバーへの接続ができる。

sudo ufw allow 19132/udp
sudo ufw reload
sudo shutdown -r now

reloadしたら反映されるはずなのに反映されず。とりあえずでrebootしたら、なんか接続できた。ヨシ!

起動の確認

cd latest
LD_LIBRARY_PATH=. ./bedrock_server

こういうのが出ていたら成功。

NO LOG FILE! - setting up server logging...
[2024-05-06 03:41:38:889 INFO] Starting Server
[2024-05-06 03:41:38:889 INFO] Version: 1.20.51.01
[2024-05-06 03:41:38:889 INFO] Session ID: 577dbb5d-08f2-4fa1-9986-136465ee936c
[2024-05-06 03:41:38:889 INFO] Build ID: 24130113
[2024-05-06 03:41:38:889 INFO] Branch: r/20_u8
[2024-05-06 03:41:38:889 INFO] Commit ID: a9081c5429038dcf3f26269f7351d89f75cfb331
[2024-05-06 03:41:38:889 INFO] Configuration: Publish
[2024-05-06 03:41:38:890 INFO] Level Name: foobar
[2024-05-06 03:41:38:892 INFO] No CDN config file found for dedicated server
[2024-05-06 03:41:38:892 INFO] Game mode: 0 Survival
[2024-05-06 03:41:38:892 INFO] Difficulty: 1 EASY
[2024-05-06 03:41:38:893 INFO] Content logging to console is enabled.
[2024-05-06 03:41:39:220 INFO] Opening level 'worlds/xxxxxxxx/db'
[2024-05-06 03:41:44:228 INFO] IPv4 supported, port: 19132: Used for gameplay and LAN discovery
[2024-05-06 03:41:44:228 INFO] IPv6 supported, port: 19133: Used for gameplay
[2024-05-06 03:41:44:243 INFO] Server started.
[2024-05-06 03:41:44:243 INFO] ================ TELEMETRY MESSAGE ===================
[2024-05-06 03:41:44:243 INFO] Server Telemetry is currently not enabled.
[2024-05-06 03:41:44:243 INFO] Enabling this telemetry helps us improve the game.
[2024-05-06 03:41:44:243 INFO]
[2024-05-06 03:41:44:243 INFO] To enable this feature, add the line 'emit-server-telemetry=true'
[2024-05-06 03:41:44:243 INFO] to the server.properties file in the handheld/src-server directory
[2024-05-06 03:41:44:243 INFO] ======================================================

こういうのが出ていたら失敗。メッセージを読んで修正する。 これはカレントワーキングディレクトリが bedrock-server-1.20.51.01 じゃなかった。

NO LOG FILE! - setting up server logging...
[2024-05-06 03:41:05:497 INFO] Starting Server
[2024-05-06 03:41:05:497 INFO] Version: 1.20.51.01
[2024-05-06 03:41:05:497 INFO] Session ID: 17a5ab6b-13b2-49df-886a-c867fb5329d9
[2024-05-06 03:41:05:497 INFO] Build ID: 24130113
[2024-05-06 03:41:05:497 INFO] Branch: r/20_u8
[2024-05-06 03:41:05:497 INFO] Commit ID: a9081c5429038dcf3f26269f7351d89f75cfb331
[2024-05-06 03:41:05:497 INFO] Configuration: Publish
[2024-05-06 03:41:05:497 ERROR] Error reading file: server.properties
[2024-05-06 03:41:05:497 ERROR] Could not load server.properties file, Please check if the current working directory is the same as the bedrock_server directory.
[2024-05-06 03:41:05:497 INFO] Server stop requested.

ファイル編集

接続許可ユーザーやサーバー設定などの各種ファイルを希望に応じて編集。

vim allowlist.json
vim permissions.json
vim server.properties

systemdでサーバー起動

OCIのインスタンスはしばらく使ってないとシャットダウンされるみたい。サーバー起動時に自動で起動してくれる方が便利なのでsystemdを使う。

/etc/systemd/system/minecraft_server.service にファイルを作り、以下の内容を記述。

[Unit]
Description=Minecraft bedrock server
After=network.target

[Service]
User=ubuntu
Group=ubuntu
Type=simple
WorkingDirectory=/home/ubuntu/minecraft
Environment=LD_LIBRARY_PATH=.
ExecStart=/home/ubuntu/minecraft/bedrock-server
Restart=always

[Install]
WantedBy=multi-user.target

以下で設定ファイルを読み込み、有効化、状況確認、起動をする。

sudo systemctl daemon-reload
sudo systemctl enable minecraft_server
sudo systemctl status minecraft_server
sudo systemctl start minecraft_server
sudo systemctl status minecraft_server

最後のstatus確認で、 Active: active (running) と出ていればOK.

● minecraft_server.service - Minecraft bedrock server
     Loaded: loaded (/etc/systemd/system/minecraft_server.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2024-05-06 03:46:46 UTC; 49min ago
   Main PID: 1141547 (bedrock_server)
      Tasks: 14 (limit: 1053)
     Memory: 129.8M
        CPU: 1min 22.012s
     CGroup: /system.slice/minecraft_server.service
             └─1141547 /home/ubuntu/minecraft/latest/bedrock_server

activeじゃなかったら、次のコマンドでログが確認できるので何が起きたか確認して修正する。

journalctl -xeu minecraft_server

既存のワールドデータの展開

Windowsからのデータ移行なので、「世界をエクスポート」からエクスポートしたファイルをサーバーに持っていき、worldsフォルダー配下に置けばOK。

unzip foobar.mcworld -d foobar

ワールドデータのバックアップ

OCIのオブジェクトストレージに置く。

instance principalというのを使うとAWSでのIAMロールっぽいのが使える。

  • ダイナミックグループ作成
  • ポリシー作成

ocicliをインストール

bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh)"
oci -v
oci setup config

疎通確認

oci os ns get --auth instance_principal
OCI_CLI_AUTH=instance_principal
oci os object put --bucket-name bucket-20240117-2254 --file fuga.txt
export OCI_CLI_AUTH=instance_principal

スクリプトにまとめる

/home/ubuntu/backup.sh

mkdir -p /home/ubuntu/minecraft_worlds_backup
filename=/home/ubuntu/minecraft_worlds_backup/world_backup_$(date '+%Y%m%d-%H%M%S').tar.gz
cd /home/ubuntu/minecraft/worlds/
tar -czvf $filename foobar

export OCI_CLI_AUTH=instance_principal
oci os object put --bucket-name bucket-20240117-2254 --file $filename

バックアップのタイマー起動

これもsystemdで管理する。

/etc/systemd/system/minecraft_backup.service というファイルを作り、以下を記述。

[Unit]
Description=Minecraft backup

[Service]
User=ubuntu
Group=ubuntu
Type=oneshot
WorkingDirectory=/home/ubuntu
ExecStart=/home/ubuntu/backup.sh

/etc/systemd/system/minecraft_backup.timer というタイマー設定のファイルも作成。時間はなんでもいい。一日一回の起動。

[Unit]
Description=Minecraft backup timer

[Timer]
OnCalendar=16:00

[Install]
WantedBy=timers.target

サーバーと同じで、以下で読み込み、有効化、状況確認、起動をする。

sudo systemctl daemon-reload
sudo systemctl enable minecraft_backup.timer
sudo systemctl status minecraft_backup.timer
sudo systemctl start minecraft_backup.timer
sudo systemctl status minecraft_backup.timer

ジャーナルも見ておく。

journalctl -xeu minecraft_backup.timer

以下でタイマーの起動状況が確認できる。

systemctl list-timers
NEXT                        LEFT               LAST                        PASSED       UNIT                           ACTIVATES
Mon 2024-05-06 09:30:04 UTC 4h 39min left      Mon 2024-05-06 03:37:51 UTC 1h 12min ago fwupd-refresh.timer            fwupd-refresh.service
Mon 2024-05-06 12:08:22 UTC 7h left            Mon 2024-05-06 03:37:04 UTC 1h 13min ago motd-news.timer                motd-news.service
Mon 2024-05-06 16:05:06 UTC 11h left           Sun 2024-05-05 16:09:15 UTC 12h ago      minecraft_backup.timer         minecraft_backup.service
Tue 2024-05-07 00:00:00 UTC 19h left           Mon 2024-05-06 00:00:17 UTC 4h 49min ago dpkg-db-backup.timer           dpkg-db-backup.service
Tue 2024-05-07 00:00:00 UTC 19h left           Mon 2024-05-06 00:00:17 UTC 4h 49min ago logrotate.timer                logrotate.service
Tue 2024-05-07 02:15:32 UTC 21h left           Mon 2024-05-06 02:15:32 UTC 2h 34min ago update-notifier-download.timer update-notifier-download.service
Tue 2024-05-07 02:31:17 UTC 21h left           Mon 2024-05-06 02:31:17 UTC 2h 18min ago systemd-tmpfiles-clean.timer   systemd-tmpfiles-clean.service
Tue 2024-05-07 10:25:21 UTC 1 day 5h left      Mon 2024-05-06 03:46:42 UTC 1h 3min ago  man-db.timer                   man-db.service
Sun 2024-05-12 03:10:53 UTC 5 days left        Sun 2024-05-05 03:11:17 UTC 1 day 1h ago e2scrub_all.timer              e2scrub_all.service
Mon 2024-05-13 00:37:45 UTC 6 days left        Mon 2024-05-06 00:44:17 UTC 4h 5min ago  fstrim.timer                   fstrim.service
Mon 2024-05-13 09:11:46 UTC 1 week 0 days left Sun 2024-05-05 18:21:59 UTC 10h ago      update-notifier-motd.timer     update-notifier-motd.service

11 timers listed.
Pass --all to see loaded but inactive timers, too.

バージョンの更新

最新をインストール

先にサーバーを止める。

sudo systemctl stop minecraft_server

更新版をインストール。

cd minecraft/
curl -O https://minecraft.azureedge.net/bin-linux/bedrock-server-1.21.1.03.zip

export NEXT_VER=bedrock-server-1.21.1.03

unzip $NEXT_VER.zip -d $NEXT_VER
unlink latest
ln -s $NEXT_VER latest

データの移行

移行前にデフォルト設定が変更されてないかdiffを取りながらやる。

export PREV_VER=bedrock-server-1.20.81.01

diff $PREV_VER/allowlist.json $NEXT_VER/allowlist.json
cp $PREV_VER/allowlist.json $NEXT_VER/allowlist.json
diff $PREV_VER/permissions.json $NEXT_VER/permissions.json
cp $PREV_VER/permissions.json $NEXT_VER/permissions.json

server.propertiesは内容が変わっている可能性が高いのでdiffを見て、cpか直接編集するか決める。

diff $PREV_VER/server.properties $NEXT_VER/server.properties
cp $PREV_VER/server.properties $NEXT_VER/server.properties
vim $NEXT_VER/server.properties

worldデータは丸ごとコピー。

cp -Rp $PREV_VER/worlds $NEXT_VER/

起動

sudo systemctl start minecraft_server

起動に失敗したらジャーナルを確認して問題を解決する。成功してもjournalは見ておいた方がいい。

journalctl -xeu minecraft_server

網膜裂孔が見つかったのでレーザーで光凝固手術をした

数日前から飛蚊症が出ていたので眼科を受診したら、網膜に穴が空いている、網膜裂孔が原因ということだった。剥がれた網膜が硝子体にプカプカ浮いて飛蚊になる。 網膜裂孔自体は加齢が原因という説明。40代から発生もままあるとのこと。

診察を受けたらその場で治療するか訊かれ、承諾すると散瞳薬で瞳孔を開いて、すぐに処置。検査する機械の並んでる部屋の端っこの機械で始まった。 瞬きを防ぐために眼球にレンズを当てられ、レーザー光をバシバシされるたびにズキズキとした痛みが走ってけっこう痛い。ついでに薄いところも予防的に処置しておきますと言われちょっと延長して、5〜10分くらいで終わって、ちょっと休んだらそれで終了。眼帯も薬の処方もなく、かなりカジュアルに終わった。

手術をしたけど、元々の症状の飛蚊症自体は改善されていなくて、結構うっとうしい。検索しても症状自体を治すことについての記述が見つからない。どうにもならないってことっぽいぞ。うっとうしいから集中を遮られがちでけっこう困っている。

HomePod miniが音楽ライブラリの曲を勝手に再生し出して困っていたのを対応した

結論

HomePodの設定で、「ミュージックとポッドキャスト」 > 「有線ユーザ」 > 「HomePodアカウント」を選んで、HomePodアカウントをサインアウトの状態にしておくと、勝手に再生するのは止まった。

経緯

昨日くらいから、HomePod miniが突然、音楽ライブラリの曲を勝手に再生し出した。止めても数分でまた再生し出す。すごい困る。

Apple コミュニティに書いてあるのと同じ事象だと思う。

Home pod miniが、勝手に音… - Apple コミュニティ

普段はSpotifyとかで再生しているのをキャストして送っているので、HomePod単体で再生するのは不要。なので、HomePodアカウントをサインアウトして、ライブラリを参照できないようにしたら、再生が止まった。あーよかった。

実家のインターネット回線を考えてpovo 2.0がいいかなとなった

中古のL01sを買ってpovoで使うことにした。

実家は元々月額重視でADSLだったけどサービスが終了したので、楽天モバイルを契約してモバイルWi-Fiを使っていた。そんなに速くもないけど月額3,280円だったので許容範囲。でも諸事情あって再検討。

光回線はどうしても5000円前後かかるので選択肢から外れる。なのでやはり無線が良くて、WiMAXとかもありだなと思うけど値段がネックで外れる。

ドコモ系のMVNOはドコモが最近良くないので避けたい。ということで、povo 2.0がいいじゃないかとなった。

povoなら、例えば、

  • データ追加150GB(180日間) 税込12,980円/回

とかあるので、これだと月2200円くらい。

  • データ追加120GB(365日間) 税込20,000円/回

だと、月で1700円くらい。非常に安い。実家の通信量を見ててもそんなに使ってないのでこれでいい。

あとは、ホームルーターが必要なので、仮でSpeed Wi-Fi HOME L01sを買ってみた。4Gしかないけど、メルカリで1300円。 調子が良さそうなら5G対応のルーターを用意してみることにする。