110.1K Views
January 06, 22
スライド概要
https://omotesandorb.connpass.com/event/235092/
Ruby / Vim / C++
Ruby 3.1 で非互換になる YAML.load Omotesando.rb #70
自己紹介 名前:osyo Twitter : @pink_bangbi github : osyo-manga ブログ : Secret Garden(Instrumental) Rails エンジニア 好きな Ruby の機能は Refinements Ruby 3.1 で気になる機能は Hash のショートハンドと debug.gem RubyKaigi Takeout 2021 Use Macro all the time ~ マクロを使いまくろ ~ Advent Calendar やってるよ! Ruby の TracePoint を使ってメソッド呼び出しをトレースしよう 一人 bugs.ruby Advent Calendar
Ruby 3.1 で非互換になる YAML.load
概要
概要 YAML は psych というライブラリで実装されている `YAML # => Psych` `YAML.load` は実質 `Psych.load` にあたる
概要 YAML は psych というライブラリで実装されている `YAML # => Psych` `YAML.load` は実質 `Psych.load` にあたる この psych が 4.0.0 で非互換になった `Psych.load` 周りが非互換になった
概要 YAML は psych というライブラリで実装されている `YAML # => Psych` `YAML.load` は実質 `Psych.load` にあたる この psych が 4.0.0 で非互換になった `Psych.load` 周りが非互換になった Ruby 3.1 ではこの psych 4.0.0 がバンドルされている
psych 4.0.0 の非互換な変更
その前に
Psych.safe_load について
Psych.safe_load について `Psych.load` ソッドがある とは別に安全に YAML ファイルを読み込む `Psych.safe_load` というメ
Psych.safe_load について `Psych.load` ソッドがある とは別に安全に YAML ファイルを読み込む `Psych.safe_load` というメ と比較して再帰的な特定のクラスのオブジェクトしか変換しなかったり、 データ構造はデフォルトでは許可されていなかったりする `Psych.load`
Psych.safe_load について `Psych.load` ソッドがある とは別に安全に YAML ファイルを読み込む `Psych.safe_load` というメ と比較して再帰的な特定のクラスのオブジェクトしか変換しなかったり、 データ構造はデフォルトでは許可されていなかったりする `Psych.load_file` と対になる `Psych.safe_load_file` メソッドも存在している `Psych.load`
デフォルトでは以下のクラスのオブジェクトしか変換されない `TrueClass` `FalseClass` `NilClass` `Numeric` `String` `Array` `Hash`
デフォルトでは以下のクラスのオブジェクトしか変換されない
`TrueClass` `FalseClass` `NilClass` `Numeric` `String` `Array` `Hash`
`Psych.safe_load`
だと `Date` オブジェクトは変換されない
1
2
3
4
require "psych"
require "date"
5
6
7
8
pp Psych.safe_load("name: homu")
# => {"name"=>"homu"}
9
10
# OK: String
# NG: Date
は問題ない
オブジェクトは変換できずにエラーになる
# error: `find': Tried to load unspecified class: Date (Psych::DisallowedClass)
pp Psych.safe_load("date: 2020-01-01")
デフォルトでは再帰的なデータ構造はデフォルトで許可されてない
デフォルトでは再帰的なデータ構造はデフォルトで許可されてない `&default` のようなエイリアスがあるとエラーになる 1 2 3 require "psych" 4 5 6 default: &default aaa: aaa bbb: bbb data = <<~EOS 7 8 9 10 11 development: <<: *default test: <<: *default production: 12 13 14 15 16 17 <<: *default EOS 読み込めない # NG: # error: Unknown alias: default (Psych::BadAlias) pp Psych.safe_load(data)
これらは引数で制御する事ができる `Date` などを許可する場合は
これらは引数で制御する事ができる
`Date` などを許可する場合は
`permitted_classes:` に許可するクラスのオブジェクトの配列を渡す
1
require "psych"
2
3
4
5
6
require "date"
# OK
pp Psych.safe_load("date: 2020-01-01", permitted_classes: [Date])
# => {"date"=>#<Date: 2020-01-01 ((2458850j,0s,0n),+0s,2299161j)>}
再帰的なデータ構造を許可する場合は
再帰的なデータ構造を許可する場合は
`aliases: true` を渡す
1
2
3
require "psych"
4
5
6
default: &default
aaa: aaa
development:
data = <<~EOS
7
8
9
10
11
<<: *default
EOS
12
# => {"default"=>{"aaa"=>"aaa"}, "development"=>{"aaa"=>"aaa"}}
# OK
pp Psych.load(data, aliases: true)
psych 4.0.0 だと `Psych.load` -> `Psych.safe_load` `Psych.load_file` -> `Psych.safe_load_file` に置き換わった
Ruby 3.1 では psych 4.0.0 がバンドルされる
なので今まで読み込めていた YAML ファイルが読み込めなくなる 可能性がある
もう1つの非互換な変更
引数のシグネチャが変わった psych 4.0.0 以前はオプション引数とキーワード引数の2つ受け取るようになっていた 1 2 3 def self.safe_load( yaml, legacy_permitted_classes = NOT_GIVEN, 4 5 6 7 legacy_permitted_symbols = NOT_GIVEN, legacy_aliases = NOT_GIVEN, legacy_filename = NOT_GIVEN, permitted_classes: [], 8 9 10 11 permitted_symbols: [], aliases: false, filename: nil, fallback: nil, 12 13 14 15 symbolize_names: false, freeze: false) # ... end
オプション引数とキーワード引数を両方渡せる
1
2
3
require "psych"
require "date"
4
5
6
7
8
pp Psych::VERSION
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# => "3.2.0"
data = <<~EOS
default: &default
aaa: 2020-01-1
development:
<<: *default
EOS
オプション引数を受け取る
#
pp Psych.safe_load(data, [Date], [], true)
キーワード引数でも受け取れる
#
pp Psych.safe_load(data, permitted_classes: [Date], aliases: true)
両方渡した場合はオプション引数が優先される
#
# no error
pp Psych.safe_load("2020-01-1", [Date], permitted_classes: [])
# error: Tried to load unspecified class: Date (Psych::DisallowedClass)
pp Psych.safe_load("2020-01-1", [], permitted_classes: [Date])
psych 4.0.0 以降はキーワード引数のみを受け取る 1 2 3 4 5 6 7 8 9 10 def self.load( yaml, permitted_classes: [Symbol], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false) # ... end
キーワード引数だけ渡せる
1
2
3
require "psych"
require "date"
4
5
6
7
8
pp Psych::VERSION
9
10
11
12
13
14
15
16
17
18
# => "3.2.0"
data = <<~EOS
default: &default
aaa: 2020-01-1
development:
<<: *default
EOS
キーワード引数でも受け取れる
#
pp Psych.safe_load(data, permitted_classes: [Date], aliases: true)
エラー
#
pp Psych.safe_load(data, [Date], [], true)
psych 4.0.0 への対応策
引数の値で制御する
必要に応じてキーワード引数で制御を行う
1
2
3
require "psych"
require "date"
4
5
6
7
data = <<~EOS
default: &default
aaa: 2020-01-1
development:
8
9
10
11
<<: *default
EOS
12
13
14
pp Psych.load(data, permitted_classes: [Date], aliases: true)
# => {"default"=>{"aaa"=>#<Date: 2020-01-01 ((2458850j,0s,0n),+0s,2299161j)>},
#
"development"=>{"aaa"=>#<Date: 2020-01-01 ((2458850j,0s,0n),+0s,2299161j)>}}
# OK
バージョンを固定する Gemfile にバージョンを指定する gem 内で `Psych.load` が使われている場合などはこの対処になりそう 1 2 gem "psych", "< 4.0.0" 3 4 # 4.0.0 # gem "psych", ">= 4.0.0" 逆に 以上を使用したい場合はこんな感じ
Psych.unsafe_load を使う
の変わりに `Psych.unsafe_load` を使う
`Psych.unsafe_load` は以前の `Psych.load` と同じ挙動になる
`Psych.load`
1
2
3
4
require "psych"
require "date"
5
6
7
8
default: &default
aaa: 2020-01-1
development:
<<: *default
9
10
11
12
13
14
data = <<~EOS
EOS
# OK
pp Psych.unsafe_load(data)
# => {"default"=>{"aaa"=>#<Date: 2020-01-01 ((2458850j,0s,0n),+0s,2299161j)>},
#
"development"=>{"aaa"=>#<Date: 2020-01-01 ((2458850j,0s,0n),+0s,2299161j)>}}
まとめ
まとめ
まとめ YAML は psych というライブラリで実装されている `YAML == Psych``
まとめ YAML は psych というライブラリで実装されている `YAML == Psych`` psych 4.0.0 から `Psych.safe_load` が `Psych.load` に置き換わるので結果的に非互換 になる 更にキーワード引数のみ受け付けるようになる
まとめ YAML は psych というライブラリで実装されている `YAML == Psych`` psych 4.0.0 から `Psych.safe_load` が `Psych.load` に置き換わるので結果的に非互換 になる 更にキーワード引数のみ受け付けるようになる Ruby 3.1 では psych 4.0.0 がデフォルトでバンドルされるので注意
まとめ YAML は psych というライブラリで実装されている `YAML == Psych`` psych 4.0.0 から `Psych.safe_load` が `Psych.load` に置き換わるので結果的に非互換 になる 更にキーワード引数のみ受け付けるようになる Ruby 3.1 では psych 4.0.0 がデフォルトでバンドルされるので注意 psych 4.0.0 を使う場合は Ruby 3.1 未満でも影響がある
参照 Ruby の Psych.safe_load(YAML.safe_load)の引数が Psych v4.0.0 から非互換になる Secret Garden(Instrumental) Ruby の YAML.load が非互換になる(かもしれない) - Secret Garden(Instrumental)