Read more

Ruby: define a class with Struct.new

Daniel Straßner
June 28, 2017Software engineer at makandra GmbH

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

Illustration web development

Do you need DevOps-experts?

Your development team has a full backlog? No time for infrastructure architecture? Our DevOps team is ready to support you!

  • We build reliable cloud solutions with Infrastructure as code
  • We are experts in security, Linux and databases
  • We support your dev team to perform
Read more Show archive.org snapshot

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">

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
June 28, 2017Software engineer at makandra GmbH
Posted by Daniel Straßner to makandra dev (2017-06-28 11:58)