LoginSignup
17
7

More than 3 years have passed since last update.

めっちゃ便利なRubyのStructクラスのお話

Last updated at Posted at 2020-09-22

きっかけ

とちぎRuby会議09にリモートで参加し、そこで見たLTに今更聞けない! Struct の使い方と今後の可能性についてでRubyのStructクラスについて初めて知った。
なにこれめっちゃ便利じゃん!となったので啓蒙も兼ねて記事を書こう、となったのがきっかけ。
Rubyはかれこれ休み休み10年間は触っているけれど未だに発見がある。素敵!学んでいないだけでは??

Structクラスとは

Ruby 2.7.0 リファレンスマニュアル Structクラス

構造体クラス。Struct.new はこのクラスのサブクラスを新たに生成します。
個々の構造体はサブクラスから Struct.new を使って生成します。個々の構造体サブクラスでは構造体のメンバに対するアクセスメソッドが定義されています。

自分の解釈で説明すると

任意の名前のプロパティ(とメソッド)を持つオブジェクトをお手軽に作成できる便利構造体クラス

使い方

# 4つのパラメーターを持ったStructのサブクラスを生成する
BreakwaterClub = Struct.new(:id, :name, :grade, :age)
# 生成したサブクラスのインスタンスを作成
bucho = BreakwaterClub.new(1, 'kuroiwa', 3)

# 作成したインスタンスはパラメーター名のアクセサメソッドを持っている
p bucho.name
#=> "kuroiwa"
bucho.age = 17

# 初期化時にセットしていなかったageがセットされている
p bucho
#=>#<struct BreakwaterClub id=1, name="kuroiwa", grade=3, age=17>
# keyword_init: true を指定することで初期化時にキーワード引数を渡せるようになる
# キーワード引数のほうがわかりやすいけど文字数は多くなるのでどちらを使うかはお好みで
BreakwaterClub = Struct.new(:id, :name, :grade, :age, keyword_init: true)

hina = BreakwaterClub.new(name: 'tsurugi', grade: 1)

Hashや配列と比べて何が嬉しいの?

Rubyでちょっとした処理を書く時によく使いがちな配列やHash。
まだ頭の中で整理できてない処理とかをアウトプットしながら整理する時とかにも使ったりする。

# 配列
bucho = [1, 'kuroiwa', 3]
bucho[0] #=>1
bucho[1] #=>'kuroiwa'
bucho[2] #=>3

# Hash
bucho = {id: 1, name: 'kuroiwa', grade: 3}
bucho[:id]    #=>1
bucho[:name]  #=>'kuroiwa'
bucho[:grade] #=>3

定義してないパラメーターを指定するとエラーになる

Hashで定義してたりするとTypoしているのにそれに気付かず「なぜ動かない…合っているはずなのに…」とかあるからこれはありがたい。

senpai = BreakwaterClub.new(name: 'ohno', grade: 2)
senpai[:height] #=>NameError (no member 'height' in struct)
senpai.height   #=>NoMethodError
# Hashだと定義してなくても参照できてしまう
senpai[:height] #=>nil

Structクラスは配列、Hashと同じようにアクセス可能で型変換も可能

つまり上位互換って考えていいと思う。

natsumi = BreakwaterClub.new(name: 'hodaka', grade: 1)
# 配列のようにアクセスできる。indexの順番は`Struct.new`で定義した順番になる
natsumi[0] #=>nil
natsumi[1] #=>'hodaka'
# Hashのようにもアクセスできる
natsumi[:id]    #=>nil
natsumi[:grade] #=>1
# 配列にもHashにも変換できる
natsumi.to_a #=>[nil, "hodaka", 1, nil]
natsumi.to_h #=>{:id=>nil, :name=>"hodaka", :grade=>1, :age=>nil}

メソッド定義が可能

Struct.newの際ににブロックを指定することでメソッドを定義可能

BreakwaterClub = Struct.new(:id, :name, :grade, :age, keyword_init: true) do
  def gakunen
    "#{grade}年生"
  end
end
# `Struct.new`で生成したStructクラスを継承したサブクラスを作成することでも可能
Class BreakwaterClub < Struct.new(:id, :name, :grade, :age, keyword_init: true)
  def gakunen
    "#{grade}年生"
  end
end

Structクラス自体を継承したサブクラスを定義することは非推奨らしい。ここまだちょっとよく理解できてない。
継承元となるStructクラスが動的に生成した無名クラスなので不定なことに起因していると思う。

参考:

Customer = Struct.new(:name, :address) do
  def greeting
    "Hello #{name}!"
  end
end
Customer.new("Dave", "123 Main").greeting # => "Hello Dave!"

Structをカスタマイズする場合はこの方法が推奨されます。無名クラスのサブクラスを作成する方法でカスタマイズする場合は無名クラスが使用されなくなってしまうことがあるためです。
[SEE_ALSO] Class.new

まとめ

Structクラスは便利。多種多様な記述方法があるRubyらしいクラスだと思う。

Structクラスまとめ

  • 任意のパラメーター、メソッドを定義できる構造体クラス
  • 配列やHashのようにアクセスでき、型変換も可能
  • 初期化時に指定していないパラメーター名だとエラーになる(Hashだとnilになる)

どういう時に使うと便利?

  • 配列やHashですませちゃってるけどClassとして定義したほうがいいよなってとき
  • ちょっとコードを書いて検証とかしたいとき
  • Classを定義するほど考えがまとまってないとき
  • おいそれと叩けないAPIを介したテストをやりたいとき

まだ試してませんが、RailsのRspecでのテスト時などでも使えそうだと感じました。
irbとかでも気軽に試せるので興味が出た人は是非試してみてください。

17
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
7