Module: Invariable

Defined in:
lib/invariable.rb,
lib/invariable/version.rb

Overview

An Invariable bundles a number of read-only attributes. It can be used like a Hash as well as an Array. It supports subclassing and pattern matching.

An Invariable can be created explicitly as a Class like a Struct. Or existing classes can easily be extended to an Invariable.

Examples:

class Person
  include Invariable
  attributes :name, :last_name
  attribute address: Invariable.new(:city, :zip, :street)

  def full_name
    "#{name} #{last_name}"
  end
end
...
john = Person.new(name: 'John', last_name: 'Doe')
john.full_name #=> "John Doe"
john.address.city #=> nil
john = john.update(
  address: { street: '123 Main St', city: 'Anytown', zip: '45678' }
)
john.dig(:address, :city) #=> "Anytown"

Constant Summary collapse

VERSION =

current version number

'0.1.0'

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.membersArray<Symbol> (readonly)

Returns all attribute names of this class.

Returns:

  • (Array<Symbol>)

    all attribute names of this class



# File 'lib/invariable.rb', line 32


Instance Attribute Details

#membersArray<Symbol> (readonly)

Returns all attribute names.

Returns:

  • (Array<Symbol>)

    all attribute names



260
261
262
# File 'lib/invariable.rb', line 260

def members
  @__attr__.keys
end

#sizeInteger (readonly)

Returns number of attributes.

Returns:

  • (Integer)

    number of attributes



268
269
270
# File 'lib/invariable.rb', line 268

def size
  @__attr__.size
end

Class Method Details

.attributes(*names, **defaults) ⇒ Array<Symbols>

Defines new attributes

Parameters:

  • names (Array<Symbol>)

    attribute names

  • defaults (Hash<Symbol,Object|Class>)

    attribute names with default values

Returns:

  • (Array<Symbols>)

    names of defined attributes



# File 'lib/invariable.rb', line 32


.member?(name) ⇒ Boolean

Returns wheter the given name is a valid attribute name for this class.

Returns:

  • (Boolean)

    wheter the given name is a valid attribute name for this class



# File 'lib/invariable.rb', line 32


.new(*names, **defaults, &block) ⇒ Class .new(base_class, *names, **defaults, &block) ⇒ Class

Creates a new class with the given attribute names. It also allows to specify default values which are used when an instance is created.

With an optional block the class can be extended.

Overloads:

  • .new(*names, **defaults, &block) ⇒ Class

    Examples:

    create a simple User class

    User = Invariable.new(:name, :last_name)
    User.members #=> [:name, :last_name]
    

    create a User class with a default value

    User = Invariable.new(:name, :last_name, processed: false)
    User.new(name: 'John', last_name: 'Doe').to_h
    #=> {:name=>"John", :last_name=>"Doe", :processed=>false}
    

    create a User class with an additional method

    User = Invariable.new(:name, :last_name) do
      def full_name
        "#{name} #{last_name}"
      end
    end
    User.new(name: 'John', last_name: 'Doe').full_name
    #=> "John Doe"
    
  • .new(base_class, *names, **defaults, &block) ⇒ Class

    Examples:

    create a Person class derived from a User class

    User = Invariable.new(:name, :last_name)
    Person = Invariable.new(User, :city, :zip, :street)
    Person.members #=> [:name, :last_name, :city, :zip, :street]
    

Parameters:

  • names (Array<Symbol>)

    attribute names

  • defaults (Hash<Symbol,Object|Class>)

    attribute names with default values

Yield Parameters:

  • new_class (Class)

    the created class

Returns:

  • (Class)

    the created class



86
87
88
89
90
91
92
# File 'lib/invariable.rb', line 86

def new(*names, **defaults, &block)
  Class.new(names.first.is_a?(Class) ? names.shift : Object) do
    include(Invariable)
    attributes(*names, **defaults)
    class_eval(&block) if block
  end
end

Instance Method Details

#==(other) ⇒ Boolean

Compares attributes of itself with the attributes of a given other Object.

This means that the given object needs to implement the same attributes and all it's attribute values have to be equal.

Returns:

  • (Boolean)

    wheter the attribute values are equal



133
134
135
136
137
138
# File 'lib/invariable.rb', line 133

def ==(other)
  @__attr__.each_pair do |k, v|
    return false if !other.respond_to?(k) || (v != other.__send__(k))
  end
  true
end

#[](name) ⇒ Object #[](index) ⇒ Object

Returns the value of the given attribute or the attribute at the given index.

Overloads:

  • #[](name) ⇒ Object

    Parameters:

    • name (Symbol)

      the name of the attribute

  • #[](index) ⇒ Object

    Parameters:

    • index (Integer)

      the index of the attribute

Returns:

  • (Object)

    the attribute value

Raises:

  • (NameError)

    if the named attribute does not exist

  • (IndexError)

    if the index is out of bounds



155
156
157
158
159
160
161
162
# File 'lib/invariable.rb', line 155

def [](arg)
  return @__attr__[arg] if @__attr__.key?(arg)
  raise(NameError, "not member - #{arg}", caller) unless Integer === arg
  if arg >= @__attr__.size || arg < -@__attr__.size
    raise(IndexError, "invalid offset - #{arg}")
  end
  @__attr__.values[arg]
end

#dig(*identifiers) ⇒ Object?

Finds and returns the object in nested objects that is specified by the identifiers. The nested objects may be instances of various classes.

Parameters:

  • identifiers (Array<Symbol,Integer>)

    one or more identifiers or indices

Returns:

  • (Object)

    object found

  • (nil)

    if nothing was found



179
180
181
182
183
# File 'lib/invariable.rb', line 179

def dig(*identifiers)
  (Integer === identifiers.first ? @__attr__.values : @__attr__).dig(
    *identifiers
  )
end

#each {|value| ... } ⇒ Invariable #eachEnumerator

Overloads:

  • #each {|value| ... } ⇒ Invariable

    Yields the value of each attribute in order.

    Yield Parameters:

    • value (Object)

      attribute value

    Returns:

  • #eachEnumerator

    Creates an Enumerator about its attribute values.

    Returns:

    • (Enumerator)


197
198
199
200
201
# File 'lib/invariable.rb', line 197

def each(&block)
  return to_enum(__method__) unless block
  @__attr__.each_value(&block)
  self
end

#each_pair {|name, value| ... } ⇒ Invariable #eachEnumerator

Overloads:

  • #each_pair {|name, value| ... } ⇒ Invariable

    Yields the name and value of each attribute in order.

    Yield Parameters:

    • name (Symbol)

      attribute name

    • value (Object)

      attribute value

    Returns:

  • #eachEnumerator

    Creates an Enumerator about its attribute name/values pairs.

    Returns:

    • (Enumerator)


216
217
218
219
220
# File 'lib/invariable.rb', line 216

def each_pair(&block)
  return to_enum(__method__) unless block
  @__attr__.each_pair(&block)
  self
end

#eql?(other) ⇒ Boolean

Compares its class and all attributes of itself with the class and attributes of a given other Object.

Returns:

  • (Boolean)

    wheter the classes and each attribute value are equal

See Also:



230
231
232
# File 'lib/invariable.rb', line 230

def eql?(other)
  self.class == other.class && self == other
end

#initialize(attributes = nil) ⇒ Invariable

Initializes a new instance with the given attributes Hash.

Returns:



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/invariable.rb', line 106

def initialize(attributes = nil)
  super()
  attributes ||= {}.compare_by_identity
  @__attr__ = {}
  self
    .class
    .instance_variable_get(:@__attr__)
    .each_pair do |key, default|
      @__attr__[key] =
        if default.is_a?(Class)
          default.new(attributes[key]).freeze
        elsif attributes.key?(key)
          attributes[key]
        else
          default
        end
    end
end

#inspectString Also known as: to_s

Returns description of itself as a string.

Returns:

  • (String)

    description of itself as a string



242
243
244
245
# File 'lib/invariable.rb', line 242

def inspect
  attributes = @__attr__.map { |k, v| "#{k}: #{v.inspect}" }
  "<#{self.class}::#{__id__} #{attributes.join(', ')}>"
end

#member?(name) ⇒ Boolean Also known as: key?

Returns wheter the given name is a valid attribute name.

Returns:

  • (Boolean)

    wheter the given name is a valid attribute name



251
252
253
# File 'lib/invariable.rb', line 251

def member?(name)
  @__attr__.key?(name)
end

#to_aArray<Object> Also known as: values

Returns the values of all attributes.

Returns:

  • (Array<Object>)

    the values of all attributes



275
276
277
# File 'lib/invariable.rb', line 275

def to_a
  @__attr__.values
end

#to_hHash<Symbol,Object> #to_h(compact: true) ⇒ Hash<Symbol,Object> #to_h {|name, value| ... } ⇒ Hash<Object,Object>

Overloads:

  • #to_hHash<Symbol,Object>

    Returns names and values of all attributes.

    Returns:

    • (Hash<Symbol,Object>)

      names and values of all attributes

  • #to_h(compact: true) ⇒ Hash<Symbol,Object>

    Returns names and values of all attributes which are not nil and which are not empty Invariable results.

    Returns:

    • (Hash<Symbol,Object>)

      names and values of all attributes which are not nil and which are not empty Invariable results

  • #to_h {|name, value| ... } ⇒ Hash<Object,Object>

    Returns a Hash containing the results of the block on each pair of the receiver as pairs.

    Yield Parameters:

    • name (Symbol)

      the attribute name

    • value (Object)

      the attribute value

    Yield Returns:

    • (Array<Symbol,Object>)

      the pair to be stored in the result

    Returns:

    • (Hash<Object,Object>)

      pairs returned by the block



302
303
304
305
306
# File 'lib/invariable.rb', line 302

def to_h(compact: false, &block)
  return to_compact_h if compact
  return Hash[@__attr__.map(&block)] if block
  @__attr__.transform_values { |v| v.is_a?(Invariable) ? v.to_h : v }
end

#update(attributes) ⇒ Invariable

Updates all given attributes.

Returns:

  • (Invariable)

    a new updated instance of itself



312
313
314
315
316
317
318
# File 'lib/invariable.rb', line 312

def update(attributes)
  opts = {}
  @__attr__.each_pair do |k, v|
    opts[k] = attributes.key?(k) ? attributes[k] : v
  end
  self.class.new(opts)
end

#values_atArray<Object>

Returns Array whose elements are the atttributes of self at the given Integer indexes.

Returns:

  • (Array<Object>)

    Array whose elements are the atttributes of self at the given Integer indexes



323
324
325
# File 'lib/invariable.rb', line 323

def values_at(...)
  @__attr__.values.values_at(...)
end