How to define a Ruby Struct which accepts its initialization arguments as a hash?


If you don't care about performance, you can use an OpenStruct.

require 'ostruct'

user = OpenStruct.new(id: 1, username: 'joe', first_name: 'Joe', ...)
user.first_name

=> "Joe"

See this for more details.


It's entirely possible to make it a class and define methods on it:

class Customer < Openstruct
  def hello
    "world"
  end
end

joe = Customer.new(id: 1, username: 'joe', first_name: 'Joe', ...)
joe.hello

=> "world"

But again, because OpenStructs are implemented using method_missing and define_method, they are pretty slow. I would go with BroiSatse's answer. If you care about required parameters, you should so something along the lines of

def initialize(params = {})   
    if missing_required_param?(params)
      raise ArgumentError.new("Missing required parameter")   
    end   
    params.each do |k,v|
      send("#{k}=", v)   
    end 
end

def missing_required_params?(params)
  ...
end

Cant you just do:

def initialize(hash)
  hash.each do |key, value|
    send("#{key}=", value)
  end
end

UPDATE:

To specify default values you can do:

def initialize(hash)
  default_values = {
    first_name: ''
  }
  default_values.merge(hash).each do |key, value|
    send("#{key}=", value)
  end
end

If you want to specify that given attribute is required, but has no default value you can do:

def initialize(hash)
  requried_keys = [:id, :username]
  default_values = {
    first_name: ''
  }
  raise 'Required param missing' unless (required_keys - hash.keys).empty?
  default_values.merge(hash).each do |key, value|
    send("#{key}=", value)
  end
end

In ruby 2.5 you can do the following:

Customer = Struct.new(
  :id, 
  :username,
  :first_name, 
  keyword_init: true
)

Customer.new(username: "al1ce", first_name: "alice", id: 123)
=> #<struct Customer id=123, username="al1ce", first_name="alice">

references:

  • https://ruby-doc.org/core-2.5.0/Struct.html

Tags:

Ruby