Class: Farce::Abstract::Molecule Abstract

Inherits:
Object
  • Object
show all
Includes:
Enumerable, Internal::Freeze::Tracked, Internal::Inspect, Internal::MarshalSupport::Initialize
Defined in:
lib/farce/abstract/molecule.rb

Overview

This class is abstract.

A record whose fields are independently atomic references.

Define a record class with Molecule.define, then read or assign its named fields. Use each field's <name>_atom for updates, comparisons, and waits. Operations on several fields are not a transaction or a consistent snapshot.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*input, compare_by_identity: nil, **attributes) ⇒ Molecule

Initialize fields by position or name. A field may only be supplied once. Existing Atom instances are retained directly, preserving their policy.

Parameters:

  • input (Array<BasicObject>) —

    values in declaration order

  • attributes (Hash{Symbol => BasicObject}) —

    values by field name

  • compare_by_identity (Boolean, nil) (defaults to: nil) —

    the comparison policy, or nil for the class default

Raises:

  • (ArgumentError) —

    for unknown, repeated, or excess values



122
123
124
125
126
127
128
129
130
131
# File 'lib/farce/abstract/molecule.rb', line 122

def initialize(*input, compare_by_identity: nil, **attributes)
  raise ArgumentError, "too many positional values" if input.length > members.length
  @compare_by_identity = compare_by_identity.nil? ? self.class.compare_by_identity? : !!compare_by_identity # rubocop:disable Style/DoubleNegation
  @molecule_atoms = {}
  input.each_with_index { initialize_atom(members[_2], _1) }
  attributes.each { initialize_atom(_1, _2) }
  members.each { initialize_atom(it, nil) unless public_send(:"#{it}_atom") }
  @molecule_atoms.freeze
  super()
end

Class Method Details

.atoms ⇒ Array<Symbol>

Returns the atom accessor names in declaration order.

Returns:

  • (Array<Symbol>) —

    the atom accessor names in declaration order



103
104
105
106
107
# File 'lib/farce/abstract/molecule.rb', line 103

def self.atoms
  return @atoms if defined?(@atoms) && @atoms
  return superclass.atoms if superclass.respond_to?(:atoms)
  EMPTY_ARRAY
end

.compare_by_identity? ⇒ Boolean

Returns the default comparison policy for new records.

Returns:

  • (Boolean) —

    the default comparison policy for new records



77
# File 'lib/farce/abstract/molecule.rb', line 77

def self.compare_by_identity? = false

.define(*fields, compare_by_identity: nil) ⇒ Class<Molecule>

Create a record class with readers, writers, and <name>_atom accessors. Missing initial values default to nil. Generated classes may be subclassed.

Parameters:

  • fields (Array<Symbol, String>) —

    unique field names

  • compare_by_identity (Boolean, nil) (defaults to: nil) —

    the default comparison policy, or nil to inherit

Returns:

  • (Class<Molecule>) —

    the record class

Raises:

  • (ArgumentError) —

    if names conflict with the record interface



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/farce/abstract/molecule.rb', line 39

def self.define(*fields, compare_by_identity: nil)
  fields = fields.map(&:to_sym)
  names  = fields.flat_map { [it, :"#{it}=", :"#{it}_atom", :"#{it}_atom="] }

  conflicts = names.any? do |name|
    next true if name == :freeze
    next false unless method_defined?(name) || private_method_defined?(name)
    instance_method(name).owner != Enumerable
  end

  if names.uniq.length != names.length || conflicts
    raise ArgumentError, "field names must be unique and must not override the molecule interface"
  end

  Class.new(self) do
    singleton_class.class_eval { undef define }
    unless nil.equal?(compare_by_identity)
      class_eval "def self.compare_by_identity? = #{(!!compare_by_identity).inspect}", __FILE__, __LINE__ - 1
    end

    atoms = []
    members = fields.map do |field|
      define_member(field)
      atoms << :"#{field}_atom"
      field
    end

    @atoms   = Ractor.make_shareable(atoms.freeze)
    @members = Ractor.make_shareable(members.freeze)

    set_temporary_name("#{superclass.name}#{@members.inspect}")
  end
end

.members ⇒ Array<Symbol>

Returns the field names in declaration order.

Returns:

  • (Array<Symbol>) —

    the field names in declaration order



110
111
112
113
114
# File 'lib/farce/abstract/molecule.rb', line 110

def self.members
  return @members if defined?(@members) && @members
  return superclass.members if superclass.respond_to?(:members)
  EMPTY_ARRAY
end

Instance Method Details

#atoms ⇒ Array<Symbol>

Returns the atom accessor names in declaration order.

Returns:

  • (Array<Symbol>) —

    the atom accessor names in declaration order



158
# File 'lib/farce/abstract/molecule.rb', line 158

def atoms = self.class.atoms

#compare_by_identity? ⇒ Boolean

Whether newly created atoms compare by identity instead of equality. Explicitly supplied atoms retain their own comparison policy.

Returns:

  • (Boolean)


155
# File 'lib/farce/abstract/molecule.rb', line 155

def compare_by_identity? = @compare_by_identity

#each {|member, value| ... } ⇒ Array<Symbol>, Enumerator Also known as: each_pair

Yield field names and current values in declaration order.

Yield Parameters:

  • member (Symbol) —

    the field name

  • value (BasicObject) —

    its current value

Returns:

  • (Array<Symbol>, Enumerator)


167
168
169
170
# File 'lib/farce/abstract/molecule.rb', line 167

def each
  return enum_for(:each) unless block_given?
  members.each { yield it, public_send(it) }
end

#each_atom {|name, atom| ... } ⇒ Array<Symbol>, Enumerator

Yield atom accessor names and atomic references in declaration order.

Yield Parameters:

  • name (Symbol) —

    the atom accessor name

  • atom (Atom) —

    the field's atomic reference

Returns:

  • (Array<Symbol>, Enumerator)


177
178
179
180
# File 'lib/farce/abstract/molecule.rb', line 177

def each_atom
  return enum_for(:each_atom) unless block_given?
  atoms.each { yield it, public_send(it) }
end

#each_member ⇒ Array<Symbol>, Enumerator Also known as: each_key

Yield field names in declaration order.

Returns:

  • (Array<Symbol>, Enumerator)


184
185
186
187
# File 'lib/farce/abstract/molecule.rb', line 184

def each_member
  return enum_for(:each_member) unless block_given?
  members.each { yield it }
end

#each_value ⇒ Array<Symbol>, Enumerator

Yield current field values in declaration order.

Returns:

  • (Array<Symbol>, Enumerator)


192
193
194
195
# File 'lib/farce/abstract/molecule.rb', line 192

def each_value
  return enum_for(:each_value) unless block_given?
  members.each { yield public_send(it) }
end

#freeze ⇒ self

Prevent field replacement and atom updates without freezing stored values. Coordinate with writers before freezing. This is not a snapshot operation.

Returns:

  • (self)


200
201
202
203
204
# File 'lib/farce/abstract/molecule.rb', line 200

def freeze
  return self if frozen?
  each_atom { |_, atom| atom.freeze }
  super
end

#members ⇒ Array<Symbol>

Returns the field names in declaration order.

Returns:

  • (Array<Symbol>) —

    the field names in declaration order



161
# File 'lib/farce/abstract/molecule.rb', line 161

def members = self.class.members