Posted over 5 years ago. Visible to the public.

Ruby: define a class with Struct.new

This card will show you a cool way to define a class using Struct.new Archive .
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 Archive (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:

Copy
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:

Copy
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:

Copy
UserPreview.new(1, 'hans@peter.de', 'Hans Peter') == UserPreview.new(1, 'hans@peter.de', 'Hans Peter') => true

More examples

Define Struct with methods:

Copy
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:

Copy
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:

Copy
# 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 Archive ).

Further reading

By refactoring problematic code and creating automated tests, makandra can vastly improve the maintainability of your Rails application.

Owner of this card:

Avatar
Daniel Straßner
Last edit:
5 months ago
by Daniel Straßner
About this deck:
We are makandra and do test-driven, agile Ruby on Rails software development.
License for source code
Posted by Daniel Straßner to makandra dev
This website uses short-lived cookies to improve usability.
Accept or learn more