Slide 1

Slide 1 text

Rubyに(ちょっと)コントリビュートできた話 Roppongi.rb #24 2024/11/14 X: @razokulover 1

Slide 2

Slide 2 text

自己紹介 中坂雄平(Yuhei Nakasaka) X: @razokulover 株式会社GA Technologies 不動産投資に関する各種サービスを提供 主にRailsでフロントエンドからバックエンドまで幅広くやっています 入社1ヶ月くらいなのでまだまだ何もわからん状態 最近はCursorに甘やかされながら何とか生きております... 2

Slide 3

Slide 3 text

今日話すこと Rubyに初めてコントリビュートできたのでその経緯と内容の話 どんなPRか 問題の解説 どう修正したか 学び 3

Slide 4

Slide 4 text

どんなPRか 4

Slide 5

Slide 5 text

Fix behavior of trying to parse non-string objects #499 - ruby/json 5

Slide 6

Slide 6 text

先月マージされました(2024/10) 2年の時を経てマージ。PRしてたことすら完全に忘れていた。 標準ライブラリとはいえRuby本体へのコントリビュートは初めて 6

Slide 7

Slide 7 text

要約 respond_to?(:to_str) で true を返すオブジェクトに to_str を呼ぶとStringが返っ てくると想定されているのに nil を返してしまう変なオブジェクトがあり、それを JSON に渡すと TypeError: no implicit conversion of nil into String が発生す る挙動を修正 7

Slide 8

Slide 8 text

8

Slide 9

Slide 9 text

問題の解説 9

Slide 10

Slide 10 text

ActiveSupportにActiveSupport::InheritableOptionsというOpenStructのような便利なクラ スがあります。 h = ActiveSupport::InheritableOptions.new({ girl: 'Mary', boy: 'John' }) h.girl # => 'Mary' h.boy # => 'John' 身近なところだと credentials.yml に対して Rails.credentials.foo.bar のように アクセスできるようにしてくれています。 https://github.com/rails/rails/blob/main/railties/lib/rails/application/configuration.rb#L77 10

Slide 11

Slide 11 text

こいつは見た目はHashっぽくて is_a?(Hash) で true を返します(これは良い)。 しかしなぜか respond_to?(:to_str) で true を返します(ちなみに標準のHashは false )。 h = ActiveSupport::InheritableOptions.new({ a: 1, b: 2 }) h.is_a? Hash #=> true h.respond_to?(:to_str) #=> true では to_str を呼んでみましょう。 11

Slide 12

Slide 12 text

h = ActiveSupport::InheritableOptions.new({ a: 1, b: 2 }) h.to_str #=> nil nilが返ります(なんで) 12

Slide 13

Slide 13 text

以前の JSON(object, args) の実装は引数で受け取るオブジェクトとして普通のHash を想定していました。 def JSON(object, *args) # ↓`to_str`を持ってるObjectは文字列を返すのは当たり前やろ if object.respond_to? :to_str JSON.parse(object.to_str, args.first) else JSON.generate(object, args.first) end end だがしかし、 、お分かりのように ActiveSupport::InheritableOptions の to_str は nil を返します。 13

Slide 14

Slide 14 text

よって↓こんなことをすると TypeError: no implicit conversion of nil into String が発生します。 h = ActiveSupport::InheritableOptions.new({ a: 1, b: 2 }) JSON(h) #=> TypeError: no implicit conversion of nil into String 14

Slide 15

Slide 15 text

要約(再掲) respond_to?(:to_str) で true を返すオブジェクトに to_str を呼ぶとStringが返っ てくると想定されているのに nil を返してしまう変なオブジェクトがあり、それを JSON に渡すと TypeError: no implicit conversion of nil into String が発生す る挙動を修正 解説終了 15

Slide 16

Slide 16 text

どう修正したか 16

Slide 17

Slide 17 text

A. to_str を信用しないようにした ruby/jsonに下記の変更を含むPRを送りました(2022)。 def JSON(object, *args) if object.is_a?(String) return JSON.parse(object, args.first) elsif object.respond_to?(:to_str) # ここ↓。どんなObjectの`to_str`も信用しない。 str = object.to_str if str.is_a?(String) return JSON.parse(object.to_str, args.first) end end JSON.generate(object, args.first) end 17

Slide 18

Slide 18 text

学び 重箱の隅を突いたようなユースケースでも必要な修正と判断されれば取り込まれる 今回は ActiveSupport::InheritableOptions という比較的Railsのコアで使わ れているクラスだったから対応されたのかも Rubyの処理系へのコントリビュートはCプログラマとしての深い経験がないと難 しそうだけど標準ライブラリならRubyで書かれているものも多いのでそこまで難 しくない ライブラリによっては CONTRIBUTING.md が用意されている場合もあるが、無 い場合も多い。過去のPRの雰囲気を読みながら手探りでやっていきましょ う。 18

Slide 19

Slide 19 text

終わり 19