A Struct is a convenient way to bundle a number of attributes together, using accessor methods, without having to write an explicit class.
The Struct class generates new subclasses that hold a set of members and their values. For each member a reader and writer method is created similar to Module#attr_accessor.
Customer = Struct.new(:name, :address) do def greeting "Hello #{name}!" end end dave = Customer.new("Dave", "123 Main") dave.name #=> "Dave" dave.greeting #=> "Hello Dave!"
See ::new for further examples of creating struct subclasses and instances.
In the method descriptions that follow, a “member” parameter refers to a
struct member which is either a quoted string
("name"
) or a Symbol
(:name
).
The first two forms are used to create a new Struct subclass class_name
that can
contain a value for each member_name
. This subclass can be
used to create instances of the structure like any other Class.
If the class_name
is omitted an anonymous structure class will
be created. Otherwise, the name of this struct will appear as a constant
in class Struct, so it must be unique for all
Structs in the system and must start with a capital letter. Assigning a
structure class to a constant also gives the class the name of the
constant.
# Create a structure with a name under Struct Struct.new("Customer", :name, :address) #=> Struct::Customer Struct::Customer.new("Dave", "123 Main") #=> #<struct Struct::Customer name="Dave", address="123 Main"> # Create a structure named by its constant Customer = Struct.new(:name, :address) #=> Customer Customer.new("Dave", "123 Main") #=> #<struct Customer name="Dave", address="123 Main">
If the optional keyword_init
keyword argument is set to
true
, .new takes keyword arguments instead of normal
arguments.
Customer = Struct.new(:name, :address, keyword_init: true) Customer.new(name: "Dave", address: "123 Main") #=> #<struct Customer name="Dave", address="123 Main">
If a block is given it will be evaluated in the context of
StructClass
, passing the created class as a parameter:
Customer = Struct.new(:name, :address) do def greeting "Hello #{name}!" end end Customer.new("Dave", "123 Main").greeting #=> "Hello Dave!"
This is the recommended way to customize a struct. Subclassing an anonymous struct creates an extra anonymous class that will never be used.
The last two forms create a new instance of a struct subclass. The number
of value
parameters must be less than or equal to the number
of attributes defined for the structure. Unset parameters default to
nil
. Passing more parameters than number of attributes will
raise an ArgumentError.
Customer = Struct.new(:name, :address) Customer.new("Dave", "123 Main") #=> #<struct Customer name="Dave", address="123 Main"> Customer["Dave"] #=> #<struct Customer name="Dave", address=nil>
static VALUE rb_struct_s_def(int argc, VALUE *argv, VALUE klass) { VALUE name, rest, keyword_init = Qfalse; long i; VALUE st; st_table *tbl; rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS); name = argv[0]; if (SYMBOL_P(name)) { name = Qnil; } else { --argc; ++argv; } if (RB_TYPE_P(argv[argc-1], T_HASH)) { static ID keyword_ids[1]; if (!keyword_ids[0]) { keyword_ids[0] = rb_intern("keyword_init"); } rb_get_kwargs(argv[argc-1], keyword_ids, 0, 1, &keyword_init); if (keyword_init == Qundef) { keyword_init = Qfalse; } --argc; } rest = rb_ident_hash_new(); RBASIC_CLEAR_CLASS(rest); tbl = RHASH_TBL(rest); for (i=0; i<argc; i++) { VALUE mem = rb_to_symbol(argv[i]); if (rb_is_attrset_sym(mem)) { rb_raise(rb_eArgError, "invalid struct member: %"PRIsVALUE, mem); } if (st_insert(tbl, mem, Qtrue)) { rb_raise(rb_eArgError, "duplicate member: %"PRIsVALUE, mem); } } rest = rb_hash_keys(rest); st_clear(tbl); RBASIC_CLEAR_CLASS(rest); OBJ_FREEZE_RAW(rest); if (NIL_P(name)) { st = anonymous_struct(klass); } else { st = new_struct(name, klass); } setup_struct(st, rest, (int)keyword_init); rb_ivar_set(st, id_keyword_init, keyword_init); if (rb_block_given_p()) { rb_mod_module_eval(0, 0, st); } return st; }
Equality—Returns true
if other
has the same
struct subclass and has equal member values (according to Object#==).
Customer = Struct.new(:name, :address, :zip) joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) joejr = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) jane = Customer.new("Jane Doe", "456 Elm, Anytown NC", 12345) joe == joejr #=> true joe == jane #=> false
static VALUE rb_struct_equal(VALUE s, VALUE s2) { if (s == s2) return Qtrue; if (!RB_TYPE_P(s2, T_STRUCT)) return Qfalse; if (rb_obj_class(s) != rb_obj_class(s2)) return Qfalse; if (RSTRUCT_LEN(s) != RSTRUCT_LEN(s2)) { rb_bug("inconsistent struct"); /* should never happen */ } return rb_exec_recursive_paired(recursive_equal, s, s2, s2); }
Attribute Reference—Returns the value of the given struct
member
or the member at the given index
. Raises
NameError if the member
does not
exist and IndexError if the
index
is out of range.
Customer = Struct.new(:name, :address, :zip) joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) joe["name"] #=> "Joe Smith" joe[:name] #=> "Joe Smith" joe[0] #=> "Joe Smith"
VALUE rb_struct_aref(VALUE s, VALUE idx) { int i = rb_struct_pos(s, &idx); if (i < 0) invalid_struct_pos(s, idx); return RSTRUCT_GET(s, i); }
Attribute Assignment—Sets the value of the given struct member
or the member at the given index
. Raises NameError if the member
does not
exist and IndexError if the
index
is out of range.
Customer = Struct.new(:name, :address, :zip) joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) joe["name"] = "Luke" joe[:zip] = "90210" joe.name #=> "Luke" joe.zip #=> "90210"
VALUE rb_struct_aset(VALUE s, VALUE idx, VALUE val) { int i = rb_struct_pos(s, &idx); if (i < 0) invalid_struct_pos(s, idx); rb_struct_modify(s); RSTRUCT_SET(s, i, val); return val; }
Returns the values for this struct as an Array.
Customer = Struct.new(:name, :address, :zip) joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) joe.to_a[1] #=> "123 Maple, Anytown NC"
static VALUE rb_struct_to_a(VALUE s) { return rb_ary_new4(RSTRUCT_LEN(s), RSTRUCT_CONST_PTR(s)); }
static VALUE rb_struct_deconstruct_keys(VALUE s, VALUE keys) { VALUE h; long i; if (NIL_P(keys)) { return rb_struct_to_h(s); } if (UNLIKELY(!RB_TYPE_P(keys, T_ARRAY))) { rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Array or nil)", rb_obj_class(keys)); } if (RSTRUCT_LEN(s) < RARRAY_LEN(keys)) { return rb_hash_new_with_size(0); } h = rb_hash_new_with_size(RARRAY_LEN(keys)); for (i=0; i<RARRAY_LEN(keys); i++) { VALUE key = RARRAY_AREF(keys, i); int i = rb_struct_pos(s, &key); if (i < 0) { return h; } rb_hash_aset(h, key, RSTRUCT_GET(s, i)); } return h; }
Extracts the nested value specified by the sequence of key
objects by calling dig
at each step, returning
nil
if any intermediate step is nil
.
Foo = Struct.new(:a) f = Foo.new(Foo.new({b: [1, 2, 3]})) f.dig(:a, :a, :b, 0) # => 1 f.dig(:b, 0) # => nil f.dig(:a, :a, :b, :c) # TypeError: no implicit conversion of Symbol into Integer
static VALUE rb_struct_dig(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS); self = rb_struct_lookup(self, *argv); if (!--argc) return self; ++argv; return rb_obj_dig(argc, argv, self, Qnil); }
Yields the value of each struct member in order. If no block is given an enumerator is returned.
Customer = Struct.new(:name, :address, :zip) joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) joe.each {|x| puts(x) }
Produces:
Joe Smith 123 Maple, Anytown NC 12345
static VALUE rb_struct_each(VALUE s) { long i; RETURN_SIZED_ENUMERATOR(s, 0, 0, struct_enum_size); for (i=0; i<RSTRUCT_LEN(s); i++) { rb_yield(RSTRUCT_GET(s, i)); } return s; }
Yields the name and value of each struct member in order. If no block is given an enumerator is returned.
Customer = Struct.new(:name, :address, :zip) joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) joe.each_pair {|name, value| puts("#{name} => #{value}") }
Produces:
name => Joe Smith address => 123 Maple, Anytown NC zip => 12345
static VALUE rb_struct_each_pair(VALUE s) { VALUE members; long i; RETURN_SIZED_ENUMERATOR(s, 0, 0, struct_enum_size); members = rb_struct_members(s); if (rb_block_arity() > 1) { for (i=0; i<RSTRUCT_LEN(s); i++) { VALUE key = rb_ary_entry(members, i); VALUE value = RSTRUCT_GET(s, i); rb_yield_values(2, key, value); } } else { for (i=0; i<RSTRUCT_LEN(s); i++) { VALUE key = rb_ary_entry(members, i); VALUE value = RSTRUCT_GET(s, i); rb_yield(rb_assoc_new(key, value)); } } return s; }
Hash equality—other
and
struct
refer to the same hash key if they have the same struct
subclass and have equal member values (according to Object#eql?).
static VALUE rb_struct_eql(VALUE s, VALUE s2) { if (s == s2) return Qtrue; if (!RB_TYPE_P(s2, T_STRUCT)) return Qfalse; if (rb_obj_class(s) != rb_obj_class(s2)) return Qfalse; if (RSTRUCT_LEN(s) != RSTRUCT_LEN(s2)) { rb_bug("inconsistent struct"); /* should never happen */ } return rb_exec_recursive_paired(recursive_eql, s, s2, s2); }
Yields each member value from the struct to the block and returns an Array containing the member values from the
struct
for which the given block returns a true value
(equivalent to Enumerable#select).
Lots = Struct.new(:a, :b, :c, :d, :e, :f) l = Lots.new(11, 22, 33, 44, 55, 66) l.select {|v| v.even? } #=> [22, 44, 66]
#filter is an alias for #select.
static VALUE rb_struct_select(int argc, VALUE *argv, VALUE s) { VALUE result; long i; rb_check_arity(argc, 0, 0); RETURN_SIZED_ENUMERATOR(s, 0, 0, struct_enum_size); result = rb_ary_new(); for (i = 0; i < RSTRUCT_LEN(s); i++) { if (RTEST(rb_yield(RSTRUCT_GET(s, i)))) { rb_ary_push(result, RSTRUCT_GET(s, i)); } } return result; }
Returns a hash value based on this struct's contents.
See also Object#hash.
static VALUE rb_struct_hash(VALUE s) { long i, len; st_index_t h; VALUE n; h = rb_hash_start(rb_hash(rb_obj_class(s))); len = RSTRUCT_LEN(s); for (i = 0; i < len; i++) { n = rb_hash(RSTRUCT_GET(s, i)); h = rb_hash_uint(h, NUM2LONG(n)); } h = rb_hash_end(h); return ST2FIX(h); }
Returns a description of this struct as a string.
static VALUE rb_struct_inspect(VALUE s) { return rb_exec_recursive(inspect_struct, s, 0); }
Returns the number of struct members.
Customer = Struct.new(:name, :address, :zip) joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) joe.length #=> 3
VALUE rb_struct_size(VALUE s) { return LONG2FIX(RSTRUCT_LEN(s)); }
Returns the struct members as an array of symbols:
Customer = Struct.new(:name, :address, :zip) joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) joe.members #=> [:name, :address, :zip]
static VALUE rb_struct_members_m(VALUE obj) { return rb_struct_s_members_m(rb_obj_class(obj)); }
Yields each member value from the struct to the block and returns an Array containing the member values from the
struct
for which the given block returns a true value
(equivalent to Enumerable#select).
Lots = Struct.new(:a, :b, :c, :d, :e, :f) l = Lots.new(11, 22, 33, 44, 55, 66) l.select {|v| v.even? } #=> [22, 44, 66]
#filter is an alias for #select.
static VALUE rb_struct_select(int argc, VALUE *argv, VALUE s) { VALUE result; long i; rb_check_arity(argc, 0, 0); RETURN_SIZED_ENUMERATOR(s, 0, 0, struct_enum_size); result = rb_ary_new(); for (i = 0; i < RSTRUCT_LEN(s); i++) { if (RTEST(rb_yield(RSTRUCT_GET(s, i)))) { rb_ary_push(result, RSTRUCT_GET(s, i)); } } return result; }
Returns the number of struct members.
Customer = Struct.new(:name, :address, :zip) joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) joe.length #=> 3
VALUE rb_struct_size(VALUE s) { return LONG2FIX(RSTRUCT_LEN(s)); }
Returns the values for this struct as an Array.
Customer = Struct.new(:name, :address, :zip) joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) joe.to_a[1] #=> "123 Maple, Anytown NC"
static VALUE rb_struct_to_a(VALUE s) { return rb_ary_new4(RSTRUCT_LEN(s), RSTRUCT_CONST_PTR(s)); }
Returns a Hash containing the names and values for the struct's members.
If a block is given, the results of the block on each pair of the receiver will be used as pairs.
Customer = Struct.new(:name, :address, :zip) joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) joe.to_h[:address] #=> "123 Maple, Anytown NC" joe.to_h{|name, value| [name.upcase, value.to_s.upcase]}[:ADDRESS] #=> "123 MAPLE, ANYTOWN NC"
static VALUE rb_struct_to_h(VALUE s) { VALUE h = rb_hash_new_with_size(RSTRUCT_LEN(s)); VALUE members = rb_struct_members(s); long i; int block_given = rb_block_given_p(); for (i=0; i<RSTRUCT_LEN(s); i++) { VALUE k = rb_ary_entry(members, i), v = RSTRUCT_GET(s, i); if (block_given) rb_hash_set_pair(h, rb_yield_values(2, k, v)); else rb_hash_aset(h, k, v); } return h; }
Returns the values for this struct as an Array.
Customer = Struct.new(:name, :address, :zip) joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) joe.to_a[1] #=> "123 Maple, Anytown NC"
static VALUE rb_struct_to_a(VALUE s) { return rb_ary_new4(RSTRUCT_LEN(s), RSTRUCT_CONST_PTR(s)); }
Returns the struct member values for each selector
as an Array. A selector
may be either an Integer offset or a Range
of offsets (as in Array#values_at).
Customer = Struct.new(:name, :address, :zip) joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) joe.values_at(0, 2) #=> ["Joe Smith", 12345]
static VALUE rb_struct_values_at(int argc, VALUE *argv, VALUE s) { return rb_get_values_at(s, RSTRUCT_LEN(s), argc, argv, struct_entry); }