Ruby: define a class with Struct.new

Updated . Posted . Visible to the public.

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 ).

Further reading

Daniel Straßner
Last edit
Emanuel
License
Source code in this card is licensed under the MIT License.
Posted by Daniel Straßner to makandra dev (2017-06-28 09:58)