概要
hashie/hashie は Ruby のハッシュを拡張するためのライブラリです
ハッシュのフィールドにアクセスするときにスクエアブラケットではなくドットにしたりハッシュに対して型成約を付けたりすることができます
今回はいろいろな使い方のサンプルを紹介します
環境
- macOS 10.15.7
- Ruby 2.7.1p83
Getting Started
とりあえず使ってみましょう
既存のハッシュから Hashie::Mash
オブジェクトを作成すればキーにドットでアクセスできるようになります
ネストしている Array は Hashie::Array
、Hash は Hashie::Mash
として変換されています
require 'hashie'
profile = Hashie::Mash.new({
name: 'hawk',
age: 10,
langs: [
'ruby',
'swift',
'python'
],
score: {
'japanese': 10,
'arithmetic': 20,
'science': 30,
}
})
puts profile.name
puts profile.age
puts profile.langs.first
puts profile.score.science
型を強制する
ハッシュには型がありませんが Coercion という機能を使うと入力するハッシュの型成約ができます
Hashie::Extensions::Coercion
と Hashie::Extensions::MergeInitializer
を include しましょう
そして coerce_key
を使って型成約するフィールドを指定します
class Profile < Hash
include Hashie::Extensions::Coercion
include Hashie::Extensions::MergeInitializer
coerce_key :name, String
coerce_key :age, Integer
end
profile = Profile.new({
name: 'hawk',
age: 10
})
puts profile[:name]
puts profile[:age]
強制した上でドットでフィールドにアクセスする
先程は普通にハッシュに対して型成約しただけなのでフィールドにアクセスする場合はスクウェアブラケットを使います
それだと Hashie っぽくないのでドットでアクセスできるようにする場合は Hashie::Mash
を継承しましょう
class Profile < Hashie::Mash
include Hashie::Extensions::Coercion
include Hashie::Extensions::MergeInitializer
coerce_key :name, String
coerce_key :age, Integer
end
profile = Profile.new({
name: 'hawk',
age: 10
})
puts profile.name
puts profile.age
強制なので強制できない場合は変換メソッドのデフォルトの値になる
例えば coerce_key :age, Integer
した場合は age フィールドには数値が入ることが想定されます
しかし以下のように数値以外が来た場合には to_i
メソッドの返り値がそのまま入ります
class Profile < Hashie::Mash
include Hashie::Extensions::Coercion
include Hashie::Extensions::MergeInitializer
coerce_key :name, String
coerce_key :age, Integer
end
profile = Profile.new({
name: 'hawk',
age: 'snowlog'
})
puts profile.name
puts profile.age # -> 0
この場合 age は 0 で初期化されます
なぜなから 'snowlog'.to_i
の結果が 0 になるからです
クラスの入れ子にすると自動で初期化してくれる
例えば強制するハッシュクラス内に別のクラスがある場合にはそのクラスの initialize を自動で読んでセットしてくれます
class Score < Hashie::Mash
include Hashie::Extensions::Coercion
include Hashie::Extensions::MergeInitializer
coerce_key :japanese, Integer
coerce_key :arithmetic, Integer
coerce_key :science, Integer
end
class Profile < Hashie::Mash
include Hashie::Extensions::Coercion
include Hashie::Extensions::MergeInitializer
coerce_key :name, String
coerce_key :age, Integer
coerce_key :score, Score
end
profile = Profile.new({
name: 'hawk',
age: 10,
score: {
japanese: 20,
arithmetic: 30,
science: 40
}
})
puts profile.name
puts profile.age
puts profile.score.japanese
入れ子になるクラスは Hashie::Mash を継承していなくてもいい
入れ子にするクラスは既存のクラスを使いたい場合もあると思います
そんな場合はハッシュを受取る initialize を定義すればそちらを自動で読んでくれます
class Score
attr_accessor :japanese, :arithmetic, :science
def initialize(score)
@japanese = score[:japanese]
@arithmetic = score[:arithmetic]
@science = score[:science]
end
end
class Profile < Hashie::Mash
include Hashie::Extensions::Coercion
include Hashie::Extensions::MergeInitializer
coerce_key :name, String
coerce_key :age, Integer
coerce_key :score, Score
end
profile = Profile.new({
name: 'hawk',
age: 10,
score: {
japanese: 20,
arithmetic: 30,
science: 40
}
})
puts profile.name
puts profile.age
puts profile.score.japanese
coerce_key は lambda を受け取ることもできる
型が 1 つじゃない可能性がある場合は coerce_key
を lambda で定義することもできます
class Profile < Hashie::Mash
include Hashie::Extensions::Coercion
include Hashie::Extensions::MergeInitializer
coerce_key :name, String
coerce_key :age, Integer
coerce_key :point, lambda { |v|
if v > 0
v
else
'error'
end
}
end
profile = Profile.new({
name: 'hawk',
age: 'snowlog',
point: -1
})
puts profile.name
puts profile.age
puts profile.point
アロー演算子を使った場合は以下のように定義できます
class Profile < Hashie::Mash
include Hashie::Extensions::Coercion
include Hashie::Extensions::MergeInitializer
coerce_key :name, String
coerce_key :age, Integer
coerce_key :point, ->(v) do
if v > 0
v
else
'error'
end
end
end
最後に
Ruby の Hashie を使ってハッシュの拡張をしてみました
とりあえずハッシュのフィールドにドットでアクセスするだけでも便利かなと思います
他にもキーが存在しない場合にはエラーにしたり定義していないキーを無視したりといろいろな機能があります
ハッシュの自由度を少し制限してしまう感じもあるので型強制については使いすぎると Ruby の良さも失ってしまうかもしれません
また型強制することで本来入らないであろうデータが入ってきてエラーを握りつぶす可能性もあるのでその辺りの考慮も必要になるかなと思います
リレーショナルなデータベースなどで型が厳密に決まっている場合には使えそうな気がします
0 件のコメント:
コメントを投稿