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.7'

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



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

def members
  @__attr__.keys
end

#sizeInteger (readonly)

Returns number of attributes.

Returns:

  • (Integer)

    number of attributes



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

def size
  @__attr__.size
end

Class Method Details

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

Defines new attributes

Parameters:

  • names (Array<Symbol>)

    attribute names

  • defaults ({Symbol => Object, Class})

    attribute names with default values

Returns:

  • (Array<Symbol>)

    names of defined attributes



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

.member?(name) ⇒ Boolean

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

Returns:

  • (Boolean)

    whether 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 ({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)

    whether 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



184
185
186
187
188
# File 'lib/invariable.rb', line 184

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)


202
203
204
# File 'lib/invariable.rb', line 202

def each(&block)
  block ? @__attr__.each_value(&block) : to_enum(__method__)
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)


219
220
221
# File 'lib/invariable.rb', line 219

def each_pair(&block)
  block ? @__attr__.each_pair(&block) : to_enum(__method__)
end

#eql?(other) ⇒ Boolean

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

Returns:

  • (Boolean)

    whether the classes and each attribute value are equal

See Also:



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

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

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

Returns whether the given name is a valid attribute name.

Returns:

  • (Boolean)

    whether the given name is a valid attribute name



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

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



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

def to_a
  @__attr__.values
end

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

Overloads:

  • #to_h{Symbol => Object}

    Returns names and values of all attributes.

    Returns:

    • ({Symbol => Object})

      names and values of all attributes

  • #to_h(compact: true) ⇒ {Symbol => Object}

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

    Returns:

    • ({Symbol => Object})

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

  • #to_h {|name, value| ... } ⇒ {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:

    • ({Object => Object})

      pairs returned by the block



298
299
300
301
302
# File 'lib/invariable.rb', line 298

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

#update(attributes) ⇒ Invariable

Updates all given attributes.

Returns:



308
309
310
311
312
313
314
# File 'lib/invariable.rb', line 308

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 attributes of self at the given Integer indexes.

Returns:

  • (Array<Object>)

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



319
320
321
# File 'lib/invariable.rb', line 319

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