I have seen codebases using Structs to wrap around attributes and behavior inside a class. What is the difference between a Ruby Class and a Struct? And when should one be used
To add to the other answers, there are some things you can not do with a Struct, and some than you can.
For example, you can not create a Struct with no arguments:
Bar = Struct.new
=> ArgumentError: wrong number of arguments (given 0, expected 1+)
Bar = Struct.new(:bar)
bar = Bar.new(nil)
bar.class
=> Bar
However, a class will let you do that:
class Foo; end
foo = Foo.new
foo.class
=> Foo
You can not set a default value for Struct arguments:
Bar = Struct.new(bar: 'default')
=> ArgumentError: unknown keyword: bar
Bar = Struct.new(bar = 'default')
=> NameError: identifier default needs to be constant
But you can do it with a class, either passing a hash, were the arguments can be in any order or even missing:
class Bar
attr_reader :bar, :rab
def initialize(bar: 'default', rab:)
@bar = bar
@rab = rab
end
end
bar = Bar.new(rab: 'mandatory')
bar.rab
=> 'mandatory'
bar.bar
=> 'default'
bar = Bar.new(rab: 'mandatory', bar: 'custom_value')
bar.rab
=> 'mandatory'
bar.bar
=> 'custom_value'
or passing the values directly, were the arguments should be given in the same order, with the defaulted ones always at the end:
class Bar
attr_reader :rab, :bar
def initialize(rab, bar = 'default')
@rab = rab
@bar = bar
end
end
bar = Bar.new('mandatory')
bar.rab
=> 'mandatory'
bar.bar
=> 'default'
bar = Bar.new('mandatory', 'custom_value')
bar.rab
=> 'mandatory'
bar.bar
=> 'custom_value'
You can not do any of that with Structs, unless you set default values for your arguments in this super verbose way:
A = Struct.new(:a, :b, :c) do
def initialize(a:, b: 2, c: 3)
super(a, b, c)
end
end
(example taken from this answer)
You can define methods in a Struct:
Foo = Struct.new(:foo) do
def method(argument)
# do something with argument
end
end
end
Structs can be useful to create data objects, like the point example mentioned in one of the answers.
I sometimes use them to create fakes and mocks in tests in a simple way. Sometimes RSpec allow(foo).to receive(:blah)
etc. can get a bit too verbose and using a Struct is much simple.