This card will show you a cool way to define a class using
Struct.new
Show archive.org snapshot
.
A common usecase for Structs are temporary data structures which just hold state and don't provide behaviour. In many cases you could use a simple hash as a data structure instead. However, a Struct provides you with a nice constructor, attribute accessors and complains if you try to access undefined attributes. Structs are easy to compare (by attributes). A struct gives meaning to the data.
Disclaimer
Structs are great but sometimes there are arguments against their usage:
- Hashes are incredibly fast. If you have to handle lots of data it may be better to use hashes for performance reasons, even if the readability of the code suffers compared to using structs.
- Also, if the struct is used not only within a module / class but throughout the whole application it may be better to use an ActiveType::Object Show archive.org snapshot (or something similar) because other developers will expect the objects to behave like they are used to from ActiveRecord.
Define a class and create objects:
UserPreview = Struct.new(:gid, :email, :name)
u = UserPreview.new(1, 'hans@peter.de', 'Hans Peter')
=> #<struct UserPreview gid=1, email="hans@peter.de", name="Hans Peter">
access attributes:
u.gid
=> 1
u.name
=> "Hans Peter"
u['name']
=> "Hans Peter"
u[:name]
=> "Hans Peter"
u[2]
=> "Hans Peter"
u.to_a
=> [1, "hans@peter.de", "Hans Peter"]
u.to_h
=> {:gid=>1, :email=>"hans@peter.de", :name=>"Hans Peter"}
u.foo
NoMethodError: undefined method `foo' for #<UserPreview:0x00565427e1aa38>
compare objects:
UserPreview.new(1, 'hans@peter.de', 'Hans Peter') == UserPreview.new(1, 'hans@peter.de', 'Hans Peter')
=> true
More examples
Define Struct with methods:
Address = Struct.new(:street, :city, :country) do
def to_s
each.inject { |concat, attr| concat += "\n#{attr}" }
end
end
a = Address.new('Bahnhofstr. 1', '86150 Augsburg', 'Germany')
puts a
Bahnhofstr. 1
86150 Augsburg
Germany
Initialize with keyword arguments
If you prefer to create objects that feel more like ActiveRecord classes you could use the :keyword_init
argument.
This will allow you to make the new
method a bit more verbose and independent of argument order:
Customer = Struct.new(:name, :address, keyword_init: true)
Customer.new(name: "Dave", address: "123 Main")
=> #<struct Customer name="Dave", address="123 Main">
Initialize with default attributes
Customer = Struct.new(:name, :address, keyword_init: true) do
def initialize(name: "Batman", **attributes)
super
end
end
Anti patterns
You might sometimes see that a class inherits from a stuct like this:
# BAD
class Address < Struct.new(:street, :city, :country)
end
# GOOD
Address = Struct.new(:street, :city, :country) do
end
This should be avoided as it creates an extra anonymous class that will never be used ( see the docs Show archive.org snapshot ).