This card is a short summary on different ways of assigning multiple attributes to an instance of a class.
Using positional parameters
Using parameters is the default when assigning attributes. It works good for a small number of attributes, but becomes more difficult to read when using multiple attributes.
Example:
class User
def initialize(salutation, first_name, last_name, street_and_number, zip_code, city, phone_number, email, newsletter)
@salutation = salutation
@first_name = first_name
@last_name = last_name
@street_and_number = street_and_number
@zip_code = zip_code
@city = city
@phone_number = phone_number
@email = email
@newsletter = newsletter
end
end
User.new(
'Mr.',
'John',
'Doe',
'Potsdamer Platz 1',
'10117',
'Berlin',
'+49 0151 1122334455',
'john.doe@example.com',
true
)
Using keyword arguments
Using keyword arguments is easier for others to instantiate the class without knowing the correct attribute order in the constructor. On the other hand people try to avoid long lines and breaking method arguments on new lines is used seldom.
Example:
class User
def initialize(salutation:, first_name:, last_name:, street_and_number:, zip_code:, city:, phone_number:, email:, newsletter:)
@salutation = salutation
@first_name = first_name
@last_name = last_name
@street_and_number = street_and_number
@zip_code = zip_code
@city = city
@phone_number = phone_number
@email = email
@newsletter = newsletter
end
end
User.new(
salutation: 'Mr.',
first_name: 'John',
last_name: 'Doe',
street_and_number: 'Potsdamer Platz 1',
zip_code: '10117',
city: 'Berlin',
phone_number: '+49 0151 1122334455',
email: 'john.doe@example.com',
newsletter: true
)
Example with breaking method arguments on multiple lines:
class User
def initialize(
salutation:,
first_name:,
last_name:,
street_and_number:,
zip_code:,
city:,
phone_number:,
email:,
newsletter:
)
@salutation = salutation
@first_name = first_name
@last_name = last_name
@street_and_number = street_and_number
@zip_code = zip_code
@city = city
@phone_number = phone_number
@email = email
@newsletter = newsletter
end
end
Using ActiveType::Object or ActiveModel::Attributes
Enhancing a class with ActiveType::Object
or ActiveModel::Attributes
makes the attributes clearer visible and adds the ability for validations and type casting. On the other hand there is no build-in way to ensure that e.g. all attributes need to be present when initializing an object.
Example:
class User
include ActiveModel::Attributes
attribute :salutation
attribute :first_name
attribute :last_name
attribute :street_and_number
attribute :zip_code
attribute :city
attribute :phone_number
attribute :email
attribute :newsletter, :boolean
end
User.new(
salutation: 'Mr.',
first_name: 'John',
last_name: 'Doe',
street_and_number: 'Potsdamer Platz 1',
zip_code: '10117',
city: 'Berlin',
phone_number: '+49 0151 1122334455',
email: 'john.doe@example.com',
newsletter: true
)
Using a hash argument
Using a hash argument allows you to assign multiple attributes without any kind of definition. But it reduces the ability for others to understand the necessary or allowed arguments at a first glance. Therefore sometimes people delete the attributes from the hash and raise an exception in case attributes are present after the initialization. It's possible to use Hash#fetch
to ensure an attribute must be present.
Example:
class User
def initialize(**attributes)
@salutation = attributes.delete(:salutation)
@first_name = attributes.delete(:first_name)
@last_name = attributes.delete(:last_name)
@street_and_number = attributes.delete(:street_and_number)
@zip_code = attributes.delete(:zip_code)
@city = attributes.delete(:city)
@phone_number = attributes.delete(:phone_number)
@email = attributes.delete(:email)
@newsletter = attributes.delete(:newsletter)
if attributes.present?
raise(ArgumentError, "Invalid attributes found #{attributes.inspect}")
end
end
end
User.new(
salutation: 'Mr.',
first_name: 'John',
last_name: 'Doe',
street_and_number: 'Potsdamer Platz 1',
zip_code: '10117',
city: 'Berlin',
phone_number: '+49 0151 1122334455',
email: 'john.doe@example.com',
newsletter: true
)
Using a struct with keyword_init
Using a struct with keyword_init
gives you the benefit of default attribute accessors. But it makes it harder to read in case you need to add custom methods as block argument or modify the values during the initialization.
Example:
class User < Struct.new(
:salutation,
:first_name,
:last_name,
:street_and_number,
:zip_code,
:city,
:phone_number,
:email,
:newsletter,
keyword_init: true
)
end
User.new(
salutation: 'Mr.',
first_name: 'John',
last_name: 'Doe',
street_and_number: 'Potsdamer Platz 1',
zip_code: '10117',
city: 'Berlin',
phone_number: '+49 0151 1122334455',
email: 'john.doe@example.com',
newsletter: true
)
Tip
In Ruby 3.2+ you can also use Data Show archive.org snapshot as a convenient way to define simple classes for value-alike objects.
Using ordered options or open struct
With some
subtile differences
Show archive.org snapshot
the OrderedOptions
and OpenStruct
can help when assigning multiple attributes. They both have the disadvantage that their content is arbitrary, but they are a lightweight way to pass data through different layers.
Example 1:
require 'ostruct'
user = OpenStruct.new(
salutation: 'Mr.',
first_name: 'John',
last_name: 'Doe',
street_and_number: 'Potsdamer Platz 1',
zip_code: '10117',
city: 'Berlin',
phone_number: '+49 0151 1122334455',
email: 'john.doe@example.com',
newsletter: true
)
Example 2:
class User
def initialize(salutation:, first_name:, last_name:, street_and_number:, zip_code:, city:, phone_number:, email:, newsletter:)
@salutation = salutation
@first_name = first_name
@last_name = last_name
@street_and_number = street_and_number
@zip_code = zip_code
@city = city
@phone_number = phone_number
@email = email
@newsletter = newsletter
end
end
user_attributes = OpenStruct.new(
salutation: 'Mr.',
first_name: 'John',
last_name: 'Doe',
street_and_number: 'Potsdamer Platz 1',
zip_code: '10117',
city: 'Berlin',
phone_number: '+49 0151 1122334455',
email: 'john.doe@example.com',
newsletter: true
)
User.new(**user_attributes)