何でも屋エンジニアのブログ

ソフトウェア関連技術、コミュニティ、日々の雑貨

育児休業を取得している

8月から半年予定で育児休業を取得しているので、現時点の雑感を残しておこうと思う。

育児休業かなり多くのメリットを感じているし、子供と過ごす時間がとても貴重なので本当に取得して良かったと感じている。

生後一ヶ月頃から私は育休を開始した。育児休業に入る前から積極的に育児に参加していたため、家の中で行う分には特に困ることはなかったものの、何事も二人でやったほうが楽なので、担当が二人いるというのはかなりメリットだった。「家の中で行う分には」と書いたことからも分かるように、慣れない場所でのお世話の難易度は家で行うときよりもはるかに高かった。育児休業開始時点で子の外出許可が出たので、育児休業開始と外出し始めが同時期だったが、育休のタイミングでたくさん外出し経験値を上げられたのはとても良かった。持ち物や必ず近隣の授乳室を探しておくなどの知見もたまり、今ではかなりスムーズに外出を楽しめるようになった(持ち物に関しては妻が対応してくれているのでとても助かる)。

また、娘は比較的敏感な子なので子育てに関する負荷は高めで、特に夜はまぁまぁキツイ。今は交代で見ているが、どちらかが働いていて一人で全部見る状態になると考えると、かなり厳しかっただろうなと思う。

実務的なメリットを上げてきたが、感じるメリットは他にもある。

一般的かどうかは分からないが、赤ちゃんは非線形な成長をするように見える。例えば、ある日を境に

  • 表情が豊かになる
  • 声を出すようになる
  • なにかに興味を持つようになる

など、突然急に何かが変わるのである。

継続して寝ることをあまりしない娘がミルク飲む以外ほぼ一日寝てる日があったのだが、その日を境に色々できるようになることが増え、顔つきも変わったということがあった。

非線形な成長と言ったもののそれらは本当に些細な変化であり、毎日一日中面倒を見ているからこそこの変化に気付けるのだろう。このような日々の変化を妻から聞くのではなく、自分で気づきリアルタイムで感動できるのは間違いなく育児休業のおかげである。このような体験が子育てで最も嬉しい瞬間の一つであり、まさに貴重な体験だと思う。

この記事、「さすがに育児休業取って3ヶ月くらい経つしさすがに書かないと...」と思って書いてるあたり、どれだけ密度の濃い時間を過ごせているかが分かる(まだ一ヶ月しか経過してなかった…)。

ところで、デメリットについて、心配なのは金銭面と仕事復帰への不安だが、まだ一ヶ月なので大きな問題だとは感じていない。また何かあれば書こうと思う。

RuboCop::AST::ProcessedSourceのAPIを触るときのメモ

コードのメトリクス自分で取りたいなと思いRubocopのソースコードを読んでいる。

Copが呼ばれるまでにソースコードがどう処理されて渡されるのか、Cop内でどう解析するのか気になって手元でいじっていた。

手元でRubocopを実行しブレークポイントを貼り見るのも良いけど、processed_source周りのAPIを自分でたたいて遊びたかったので以下のようなスクリプトを書いた。呼び出すcopの設定をしたりキャッシュをoffにしたりとちょっと面倒に感じたのもあるが、pockeさんの記事を読めばクリアできるのであくまでも副次的な理由である。

qiita.com

# 標準入力からソースコードを渡している
require 'pry'
require 'rubocop'
require 'rubocop-ast'

arg = ARGV.first
source = +arg

ps = RuboCop::AST::ProcessedSource.new(source, 3.0)

def visit_depth_last(node, &block)
  node.each_child_node { |child| visit_depth_last(child, &block) }
  yield node
end

visit_depth_last(ps.ast) do |child|
  binding.pry
end

Rubocopの各所で引数として渡されているnodeはprocessed_source.astなので、visit_depth_lastに渡してあげてゴニョニョするとRuboCop::AST::*NodeAPIを触れるようになる。これで自分の参考にしたいCopの実装を見つつ手元でデバッグするのが分かりやすかった。

React.js docs読んでいるのでメモ

今改めてReact.jsを学びなおしている。

以前チュートリアルをやって分かったような分からないような気持ちだったので、ドキュメントを読んでいるが詳しく解説があって読みやすい。

以下のページから1ページずつ読んでいるのでそのメモ。h3は雑にページのタイトルだったり項のタイトルだったりする。

ja.reactjs.org


要素

Ractアプリケーションの最小単位の構成ブロック

React要素はプレーンオブジェクト(notブラウザのDOM要素)

要素をDOMとして描画するには

ReactDOM.render() にelement(React要素)とHTML内の要素を渡す(document.getElementById('root')

DOMやHTMLとの関係がよく分からなくなったときは:

Document Object Model (DOM) は HTML や XML 文書のためのプログラミングインターフェイスです。ページを表現するため、プログラムが文書構造、スタイル、内容を変更することができます。 DOM は文書をノードとオブジェクトで表現します。そうやって、プログラミング言語をページに接続することができます

https://developer.mozilla.org/ja/docs/Web/API/Document_Object_Model/Introduction

レンダーされた要素の更新

React DOM は要素とその子要素を以前のものと比較し、DOM を望ましい状態へと変えるのに必要なだけの DOM の更新を行う

コンポーネントとprops

コンポーネント: JavaScriptの関数のようなもの。任意の入力を受け取りReact要素を返す。任意の入力はpropsと呼ばれる。

関数コンポーネントとクラスコンポーネント

クラスコンポーネント: ES6クラスを利用したもので、React.Componentをextendしrenderをオーバーライドする

コンポーネントのレンダー

ユーザー定義のコンポーネントをレンダーすることもできる。具体例は: https://ja.reactjs.org/docs/components-and-props.html

コンポーネントを組み合わせる

function Welcome(props) {
  return <h1> Hello, {props.name}</h1>
}

function App() {
  return (
    <div>
      <Welcome name='Sarah' />
      <Welcome name='Taro' />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

Propsは読み取り専用

すべてのReactコンポーネントは自己のpropsに対し純関数のように振る舞わなければならない。

state とライフサイクル

「何が起こったのかをメソッドが呼び出される順序にそって簡単に振り返ってみましょう」の箇所は繰り返し読むこと。

データは下方向に伝わる

単一方向の(トップダウンの)データフロー

flex-git-configにGithub ActionsでCI/CD環境を整備した

flex-git-configを作ったが、CI/CD環境もなくバイナリも手動でリポジトリにcommitしていた。

同僚から便利じゃんって言ってもらえたのと、Github Actions使いたいと思いつつ機会がなかったので利用し導入してみた。 併せて、雑だった各種メッセージや終了時の挙動を改善した。

ちなみにflex-git-configを作成した動機は以下に書いている。 blog.ebihara99999.com

作成にあたり以下を参考にさせてもらいとても助かった。 qiita.com

Golang、あまり業務で使うことが少ないんだけどflex-git-configを盆栽にして細々とメンテしていこうと思う。

開発環境でActionController::InvalidAuthenticityTokenが出るようになった

結論

SameSite=None; Secureにしているサービスは開発環境もHTTPS化しておきましょうというお話です。Chromeは84よりデフォルトの挙動が変わり、FirefoxFirefox 69からSameSite属性が利用できるようになっています(デフォルトの挙動はまだのよう)。

自分の担当しているサービスでもメンバーが既にHTTPS化してくれていましたが、HTTPとHTTPS両方使える状態だったので今回の事象に遭遇してしまいました。

経緯

初回リクエスト時のレスポンスを返す際に生成したCSRFトークンがsession[:_csrf_token]に格納され、次のリクエスト時には当該データとリクエスト時に受け取ったCSRFトークンを突き合わせリクエストの整合性を担保している。

具体的にはこのあたり。

def real_csrf_token(session) # :doc:
  session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
  Base64.strict_decode64(session[:_csrf_token])
end

出典: actionpack/lib/action_controller/metal/request_forgery_protection.rb

しかし2回目のリクエスト時にはその前に生成されているはずのCSRFトークンがsession[:_csrf_token]に格納されておらず、不正なリクエストと判定されているようだった。Safariで確認したところ想定通りのタイミングでsession[:_csrf_token]に値が格納されていた。

自分で挙動を調査しチームメンバーにヒアリングしたところ

  • HTTPSでログイン可能(上記不具合はHTTPによるアクセス)
  • Chromeではログインできる人とできない人がいる(HTTP)
  • Chrome以外のブラウザではログイン可能(HTTP)

という状態だった。

HTTPSでログイン可能という挙動とデバッグの過程から、SameSite Cookiesの機能ではないかと疑った。 Chromeでログインできるメンバーに確認したところその人だけバージョン63のChromeを使っており、ログインできない組は64を使用していた。確認したところ、新型コロナウィルスの影響で延期されていたSameSite cookieの対応が7/14 バージョン84から再開されており、その影響であることが判明した。

参考

qiita.com

www.chromium.org

web.dev

ActiveSupport::MessageEncryptor について調べたのでメモ

IV(初期化ベクトル)をどこに保存してあるのだろう、どうやって復号化しているのだろうか?と調べたのでメモ。

#encrypt_and_signの返り値は、--区切りで暗号化データ・初期化ベクトル・認証タグとなっている(それぞれBase64符号化を行っている)。

# Rely on OpenSSL for the initialization vector
iv = cipher.random_iv
cipher.auth_data = "" if aead_mode?

encrypted_data = cipher.update(Messages::Metadata.wrap(@serializer.dump(value), metadata_options))
encrypted_data << cipher.final

blob = "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}"
blob = "#{blob}--#{::Base64.strict_encode64 cipher.auth_tag}" if aead_mode?
blob

ref: active_support/message_encryptor.rb

実行結果

crypt = ActiveSupport::MessageEncryptor.new('a'*32)
=> #<ActiveSupport::MessageEncryptor:0x00007f9fc8fefaa0 @secret="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", @sign_secret=nil, @cipher="aes-256-gcm", @aead_mode=true, @verifier=ActiveSupport::MessageEncryptor::NullVerifier, @serializer=Marshal, @options={}, @rotations=[]>

crypt.encrypt_and_sign('secret data that needs to be concealed')
=> "PnsLv1E/iLi0ZZxE4NKAavBO3UJc4Xe3KUZnvsJfqmA61Rt/iGPjtYbxy55/OUng--HcmSJhaaOrSoBGVL--oGfjcvq66Kw4i2Yk3A6neQ=="
irb(main):054:0>

上に示したように返り値は PnsLv1E/iLi0ZZxE4NKAavBO3UJc4Xe3KUZnvsJfqmA61Rt/iGPjtYbxy55/OUng--HcmSJhaaOrSoBGVL--oGfjcvq66Kw4i2Yk3A6neQ==となっており、--で分解するとそれぞれ

  • PnsLv1E/iLi0ZZxE4NKAavBO3UJc4Xe3KUZnvsJfqmA61Rt/iGPjtYbxy55/OUng (暗号化データ)
  • HcmSJhaaOrSoBGVL(IV)
  • oGfjcvq66Kw4i2Yk3A6neQ==(認証タグ)

となっている。

したがって初期化ベクトルをDBに保存する必要がない。以下に引用するように、実際の処理ではは encrypted_data, iv, auth_tag = encrypted_message.split("--").map { |v| ::Base64.strict_decode64(v) } としている。ここが attr-encrypted gemとの違いで気軽に使いやすい。

def _decrypt(encrypted_message, purpose)
        cipher = new_cipher
        encrypted_data, iv, auth_tag = encrypted_message.split("--").map { |v| ::Base64.strict_decode64(v) }

        # Currently the OpenSSL bindings do not raise an error if auth_tag is
        # truncated, which would allow an attacker to easily forge it. See
        # https://github.com/ruby/openssl/issues/63
        raise InvalidMessage if aead_mode? && (auth_tag.nil? || auth_tag.bytes.length != 16)

        cipher.decrypt
        cipher.key = @secret
        cipher.iv  = iv
        if aead_mode?
          cipher.auth_tag = auth_tag
          cipher.auth_data = ""
        end

        decrypted_data = cipher.update(encrypted_data)
        decrypted_data << cipher.final

        message = Messages::Metadata.verify(decrypted_data, purpose)
        @serializer.load(message) if message
      rescue OpenSSLCipherError, TypeError, ArgumentError
        raise InvalidMessage
end

ref: active_support/message_encryptor.rb

参考

DFSについてのメモ

競技プロでDFSの問題が出てきて調べたので主にリンクのメモ。

以下2記事の DFS (深さ優先探索) 超入門! 〜 グラフ・アルゴリズムの世界への入口 〜 がとてもわかり易かった。概念から丁寧に解説があり、図・コードも記載されているのでイメージがかんたんに湧く。言葉の説明もとても分かりやすい。

qiita.com

qiita.com

合わせてベクトルの定義を忘れていたので復習しようと思い調べたら以下の資料がとてもわかり易かった。

第1章 線形代数の基礎のキソ - 東京工業大学 理学院 数学系

あと色々調べたら以下のサイトを見つけた。ちゃんと読んでないので後で読む。

www.geeksforgeeks.org

再帰難しいなぁと思っていたけど以下が参考になった。

www.ibm.com

www.nct9.ne.jp