opt_struct
                                
                                 opt_struct copied to clipboard
                                
                                    opt_struct copied to clipboard
                            
                            
                            
                        Option Struct for Ruby
The Opt Struct 
A struct around a hash. Great for encapsulating actions with complex configuration, like interactor/action classes.
gem "opt_struct"
Examples
Creating an OptStruct
class User < OptStruct.new
  required :email, :name
  option :role, default: "member"
  
  def formatted_email
    %{"#{name}" <#{email}>}
  end
end
Using an OptStruct
user = User.new(email: "[email protected]", name: "Ms. Admin", role: "admin")
# option accessors are available
user.name
# => "Ms. Admin"
user.formatted_email
# => "\"Ms. Admin\" <[email protected]>"
user.name = "Amber Admin"
# => "Amber Admin"
# values are also accessible through the `#options` Hash
user.options
# => {:email=>"[email protected]", :name=>"Amber Admin", :role=>"admin"}
user.options.fetch(:role)
# => "admin"
Documentation
Use As Inheritable Class
OptStruct.new returns an instance of Class that can be inherited or initialized directly.
The following are functionally equivalent
class User < OptStruct.new
  required :email
  option :name
end
User = OptStruct.new do
  required :email
  option :name
end
OptStruct classes can safely have descendants with their own isolated options.
class AdminUser < User
  required :token
end
User.new(email: "[email protected]")
# => #<User:0x0... @options={:email=>"[email protected]"}>
AdminUser.new(email: "[email protected]")
# ArgumentError: missing required keywords: [:token]
AdminUser.new(email: "[email protected]", token: "a2236843f0227af2")
# => #<AdminUser:0x0... @options={:email=>"[email protected]", :token=>"..."}>
Use As Mixin Module
OptStruct.build returns an instance of Module that can be included into a class or another module.
The following are functionally equivalent
module Visitable
  include OptStruct.build
  options :expected_at, :arrived_at, :departed_at
end
class AuditLog
  include Visitable
end
Visitable = OptStruct.build { options :expected_at, :arrived_at, :departed_at }
class AuditLog
  include Visitable
end
These examples result in an AuditLog class with identical behavior, but no explicit Visitable module.
class AuditLog
  include OptStruct.build
  options :expected_at, :arrived_at, :departed_at
end
class AuditLog
  include(OptStruct.build do
    options :expected_at, :arrived_at, :departed_at
  end)
end
Optional Arguments
Optional arguments are simply accessor methods for values expected to be in the #options Hash. Optional arguments can be defined in multiple ways.
All of the examples in this section are functionally equivalent.
class User < OptStruct.new
  option :email
  option :role, default: "member"
end
class User < OptStruct.new
  options :email, role: "member"
end
class User < OptStruct.new
  options email: nil, role: "member"
end
Passing a Hash to .new or .build is equivalent to passing the same hash to options
User = OptStruct.new(email: nil, role: "member")
Default blocks can also be used and are late evaluated on the each struct instance.
class User < OptStruct.new
  option :email, default: -> { nil }
  option :role, -> { "member" }
end
class User < OptStruct.new
  options :email, role: -> { "member" }
end
class User < OptStruct.new
  option :email, nil
  option :role, -> { default_role }
  
  private
  
  def default_role
    "member"
  end
end
Default symbols are treated as method calls if the struct #respond_to? the method.
class User < OptStruct.new
  options :email, :role => :default_role
  
  def default_role
    "member"
  end
end
Required Arguments
Required arguments are just like optional arguments, except they are also added to the .required_keys collection, which is checked when an OptStruct is initialized. If the #options Hash does not contain all .required_keys then an ArgumentError is raised.
The following examples are functionally equivalent.
class Student < OptStruct.new
  required :name
end
class Student < OptStruct.new
  option :name, required: true
end
class Student < OptStruct.new
  option :name
  required_keys << :name
end
Expected Arguments
OptStructs can accept non-keyword arguments if the struct knows to expect them.
For code like this to work...
user = User.new("[email protected]", "admin")
user.email # => "[email protected]"
user.role  # => "admin"
... the OptStruct needs to have some .expected_arguments.
The following User class examples are functionally equivalent and allow the code above to function.
User = OptStruct.new(:email, :role)
class User < OptStruct.new(:email)
  expect_argument :role
end
class User
  include OptStruct.build(:email, :role)
end
class User
  include OptStruct.build
  expect_arguments :email, :role
end
class User < OptStruct.new(:email)
  expected_arguments << :role
end
Expected arguments are similar to required arguments, except they are in .expected_arguments collection, which is checked when an OptStruct is initialized.
Expected arguments can also be supplied using keywords. An ArgumentError is only raised if the expected argument is not in the list of arguments passed to OptStruct#new and the argument is not present in the #options Hash.
The following examples will initialize any of the User class examples above without error.
User.new(email: "[email protected]", role: "member")
User.new("[email protected]", role: "member")
User.new(role: "member", email: "[email protected]")
The #options Hash
All OptStruct arguments are read from and stored in a single Hash instance. This Hash can be accessed directly using the options method.
Person = OptStruct.new(:name)
Person.new(name: "John", age: 32).options
# => {:name=>"John", :age=>32}
Feel free to write your own accessor methods for things like dependent options or other complex/private behavior.
class Person < OptStruct.new
  option :given_name
  option :family_name
  
  def name
    options.fetch(:name) { "#{given_name} #{family_name}" }
  end
end
On Initialization
All of the following examples are functionally equivalent.
OptStruct classes are initialized in an initialize method (in OptStruct::InstanceMethods) like most classes. Also, like most classes, you can override initialize as long as you remember to call super properly to retain OptStruct functionality.
class UserReportBuilder < OptStruct.new(:user)
  attr_reader :report
  
  def initialize(*)
    super
    @report = []
  end
end
OptStruct also provides initialization callbacks to make hooking into and customizing the initialization of OptStruct classes easier and require less code.
class UserReportBuilder < OptStruct.new(:user)
  attr_reader :report
  init { @report = [] }
end
class UserReportBuilder < OptStruct.new(:user)
  attr_reader :report
  
  around_init do |instance|
    instance.call
    @report = []
  end
end
Available callbacks
- around_init
- before_init
- init
- after_init
Inheritance, Expanded
See spec/inheritance_spec.rb for examples of just how crazy you can get.