module Stannum::Struct
Abstract class for defining objects with structured attributes.
@example Defining Attributes
class Widget include Stannum::Struct attribute :name, String attribute :description, String, optional: true attribute :quantity, Integer, default: 0 end widget = Widget.new(name: 'Self-sealing Stem Bolt') widget.name #=> 'Self-sealing Stem Bolt' widget.description #=> nil widget.quantity #=> 0 widget.attributes #=> # { # name: 'Self-sealing Stem Bolt', # description: nil, # quantity: 0 # }
@example Setting Attributes
widget.description = 'A stem bolt, but self sealing.' widget.attributes #=> # { # name: 'Self-sealing Stem Bolt', # description: 'A stem bolt, but self sealing.', # quantity: 0 # } widget.assign_attributes(quantity: 50) widget.attributes #=> # { # name: 'Self-sealing Stem Bolt', # description: 'A stem bolt, but self sealing.', # quantity: 50 # } widget.attributes = (name: 'Inverse Chronoton Emitter') # { # name: 'Inverse Chronoton Emitter', # description: nil, # quantity: 0 # }
@example Defining Attribute
Constraints
Widget::Contract.matches?(quantity: -5) #=> false Widget::Contract.matches?(name: 'Capacitor', quantity: -5) #=> true class Widget constraint(:quantity) { |qty| qty >= 0 } end Widget::Contract.matches?(name: 'Capacitor', quantity: -5) #=> false Widget::Contract.matches?(name: 'Capacitor', quantity: 10) #=> true
@example Defining Struct
Constraints
Widget::Contract.matches?(name: 'Diode') #=> true class Widget constraint { |struct| struct.description&.include?(struct.name) } end Widget::Contract.matches?(name: 'Diode') #=> false Widget::Contract.matches?( name: 'Diode', description: 'A low budget Diode', ) #=> true
Public Class Methods
@private
# File lib/stannum/struct.rb, line 230 def build(struct_class) return if struct_class?(struct_class) initialize_attributes(struct_class) initialize_contract(struct_class) end
Initializes the struct with the given attributes.
For each key in the attributes hash, the corresponding writer method will be called with the attribute value. If the hash does not include the key for an attribute, or if the value is nil, the attribute will be set to its default value.
If the attributes hash includes any keys that do not correspond to an attribute, the struct will raise an error.
@param attributes [Hash] The initial attributes for the struct.
@see attributes=
@raise ArgumentError if given an invalid attributes hash.
# File lib/stannum/struct.rb, line 287 def initialize(attributes = {}) @attributes = {} self.attributes = attributes end
Private Class Methods
# File lib/stannum/struct.rb, line 239 def included(other) super Struct.build(other) if other.is_a?(Class) end
# File lib/stannum/struct.rb, line 245 def initialize_attributes(struct_class) attributes = Stannum::Schema.new struct_class.const_set(:Attributes, attributes) if struct_class?(struct_class.superclass) attributes.include(struct_class.superclass::Attributes) end struct_class.include(attributes) end
# File lib/stannum/struct.rb, line 257 def initialize_contract(struct_class) contract = Stannum::Contract.new struct_class.const_set(:Contract, contract) return unless struct_class?(struct_class.superclass) contract.concat(struct_class.superclass::Contract) end
# File lib/stannum/struct.rb, line 267 def struct_class?(struct_class) struct_class.const_defined?(:Attributes, false) end
Public Instance Methods
Compares the struct with the other object.
The other object must be an instance of the current class. In addition, the attributes hashes of the two objects must be equal.
@return true if the object is a matching struct.
# File lib/stannum/struct.rb, line 299 def ==(other) return false unless other.class == self.class raw_attributes == other.raw_attributes end
Retrieves the attribute with the given key.
@param key [String, Symbol] The attribute key.
@return [Object] the value of the attribute.
@raise ArgumentError if the key is not a valid attribute.
# File lib/stannum/struct.rb, line 312 def [](key) validate_attribute_key(key) send(self.class::Attributes[key].reader_name) end
Sets the given attribute to the given value.
@param key [String, Symbol] The attribute key. @param value [Object] The value for the attribute.
@raise ArgumentError if the key is not a valid attribute.
# File lib/stannum/struct.rb, line 324 def []=(key, value) validate_attribute_key(key) send(self.class::Attributes[key].writer_name, value) end
Updates the struct’s attributes with the given values.
This method is used to update some (but not all) of the attributes of the struct. For each key in the hash, it calls the corresponding writer method with the value for that attribute. If the value is nil, this will set the attribute value to the default for that attribute.
Any attributes that are not in the given hash are unchanged.
If the attributes hash includes any keys that do not correspond to an attribute, the struct will raise an error.
@param attributes [Hash] The initial attributes for the struct.
@raise ArgumentError if the key is not a valid attribute.
@see attributes=
# File lib/stannum/struct.rb, line 347 def assign_attributes(attributes) unless attributes.is_a?(Hash) raise ArgumentError, 'attributes must be a Hash' end attributes.each do |attr_name, value| validate_attribute_key(attr_name) attribute = self.class.attributes[attr_name] send(attribute.writer_name, value) end end
@return [Hash] the current attributes of the struct.
# File lib/stannum/struct.rb, line 363 def attributes tools.hash_tools.deep_dup(@attributes) end
Replaces the struct’s attributes with the given values.
This method is used to update all of the attributes of the struct. For each attribute, the writer method is called with the value from the hash, or nil if the corresponding key is not present in the hash. Any nil or missing keys set the attribute value to the attribute’s default value.
If the attributes hash includes any keys that do not correspond to an attribute, the struct will raise an error.
@param attributes [Hash] The initial attributes for the struct.
@raise ArgumentError if the key is not a valid attribute.
@see assign_attributes
# File lib/stannum/struct.rb, line 383 def attributes=(attributes) # rubocop:disable Metrics/MethodLength unless attributes.is_a?(Hash) raise ArgumentError, 'attributes must be a Hash' end attributes.each_key { |attr_name| validate_attribute_key(attr_name) } self.class::Attributes.each_value do |attribute| send( attribute.writer_name, attributes.fetch( attribute.name, attributes.fetch(attribute.name.intern, attribute.default) ) ) end end
@return [String] a string representation of the struct and its attributes.
# File lib/stannum/struct.rb, line 402 def inspect # rubocop:disable Metrics/AbcSize if self.class.attributes.each_key.size.zero? return "#<#{self.class.name}>" end buffer = +"#<#{self.class.name}" self.class.attributes.each_key.with_index \ do |attribute, index| buffer << ',' unless index.zero? buffer << " #{attribute}: #{@attributes[attribute].inspect}" end buffer << '>' end
Protected Instance Methods
# File lib/stannum/struct.rb, line 420 def raw_attributes @attributes end
Private Instance Methods
# File lib/stannum/struct.rb, line 426 def tools SleepingKingStudios::Tools::Toolbelt.instance end
# File lib/stannum/struct.rb, line 430 def validate_attribute_key(key) raise ArgumentError, "attribute can't be blank" if key.nil? unless key.is_a?(String) || key.is_a?(Symbol) raise ArgumentError, 'attribute must be a String or Symbol' end raise ArgumentError, "attribute can't be blank" if key.empty? return if self.class::Attributes.key?(key.to_s) raise ArgumentError, "unknown attribute #{key.inspect}" end