Class: SerialChronology

Inherits:
ApplicationRecord show all
Includes:
Housekeeping::Timestamps, Housekeeping::Users, Shared::IsData, Shared::SharedAcrossProjects
Defined in:
app/models/serial_chronology.rb

Overview

Stores the chronological relationship between two serials.

Direct Known Subclasses

SerialMerge, SerialSequence

Defined Under Namespace

Classes: SerialMerge, SerialSequence

Instance Attribute Summary collapse

Attributes included from Housekeeping::Users

#by

Instance Method Summary collapse

Methods included from Shared::IsData

#errors_excepting, #full_error_messages_excepting, #identical, #is_community?, #is_destroyable?, #is_editable?, #is_in_use?, #is_in_users_projects?, #metamorphosize, #similar

Methods included from Housekeeping::Users

#set_created_by_id, #set_updated_by_id

Methods inherited from ApplicationRecord

transaction_with_retry

Instance Attribute Details

#preceding_serial_idInteger

Returns the reference/historical serial.

Returns:

  • (Integer)

    the reference/historical serial



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'app/models/serial_chronology.rb', line 15

class SerialChronology < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::IsData
  include Shared::SharedAcrossProjects

  belongs_to :preceding_serial, class_name: 'Serial', foreign_key: :preceding_serial_id, inverse_of: :preceding_serial_chronologies
  belongs_to :succeeding_serial, class_name: 'Serial', foreign_key: :succeeding_serial_id, inverse_of: :succeeding_serial_chronologies

  validates_presence_of :preceding_serial_id, :succeeding_serial_id, :type
  validates_uniqueness_of :preceding_serial_id, scope: [:succeeding_serial_id]

  # Scaffolded from GPT 4o 21-5-2025 
  validate :no_self_reference
  validate :no_cycles

  private

  def no_self_reference
    if preceding_serial_id == succeeding_serial_id
      errors.add(:base, "A serial cannot precede or succeed itself")
    end
  end

  def no_cycles
    if creates_cycle?
      errors.add(:base, "This link would create a cycle in the chronology")
    end
  end

  def creates_cycle?
    # We want to see if the `preceding_serial` is reachable from the `succeeding_serial`
    # If so, adding this link would introduce a cycle
    visited = Set.new
    depth_first_search(succeeding_serial, visited).include?(preceding_serial)
  end

  def depth_first_search(serial, visited)
    return visited if visited.include?(serial)

    visited.add(serial)
    successors = SerialChronology.where(preceding_serial: serial).includes(:succeeding_serial).map(&:succeeding_serial)
    successors.each { |succ| depth_first_search(succ, visited) }

    visited
  end
end

#succeeding_serial_idInteger

Returns the “new” serial.

Returns:

  • (Integer)

    the “new” serial



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'app/models/serial_chronology.rb', line 15

class SerialChronology < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::IsData
  include Shared::SharedAcrossProjects

  belongs_to :preceding_serial, class_name: 'Serial', foreign_key: :preceding_serial_id, inverse_of: :preceding_serial_chronologies
  belongs_to :succeeding_serial, class_name: 'Serial', foreign_key: :succeeding_serial_id, inverse_of: :succeeding_serial_chronologies

  validates_presence_of :preceding_serial_id, :succeeding_serial_id, :type
  validates_uniqueness_of :preceding_serial_id, scope: [:succeeding_serial_id]

  # Scaffolded from GPT 4o 21-5-2025 
  validate :no_self_reference
  validate :no_cycles

  private

  def no_self_reference
    if preceding_serial_id == succeeding_serial_id
      errors.add(:base, "A serial cannot precede or succeed itself")
    end
  end

  def no_cycles
    if creates_cycle?
      errors.add(:base, "This link would create a cycle in the chronology")
    end
  end

  def creates_cycle?
    # We want to see if the `preceding_serial` is reachable from the `succeeding_serial`
    # If so, adding this link would introduce a cycle
    visited = Set.new
    depth_first_search(succeeding_serial, visited).include?(preceding_serial)
  end

  def depth_first_search(serial, visited)
    return visited if visited.include?(serial)

    visited.add(serial)
    successors = SerialChronology.where(preceding_serial: serial).includes(:succeeding_serial).map(&:succeeding_serial)
    successors.each { |succ| depth_first_search(succ, visited) }

    visited
  end
end

#typeString

Returns the type of transition b/w the old and new.

Returns:

  • (String)

    the type of transition b/w the old and new



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'app/models/serial_chronology.rb', line 15

class SerialChronology < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::IsData
  include Shared::SharedAcrossProjects

  belongs_to :preceding_serial, class_name: 'Serial', foreign_key: :preceding_serial_id, inverse_of: :preceding_serial_chronologies
  belongs_to :succeeding_serial, class_name: 'Serial', foreign_key: :succeeding_serial_id, inverse_of: :succeeding_serial_chronologies

  validates_presence_of :preceding_serial_id, :succeeding_serial_id, :type
  validates_uniqueness_of :preceding_serial_id, scope: [:succeeding_serial_id]

  # Scaffolded from GPT 4o 21-5-2025 
  validate :no_self_reference
  validate :no_cycles

  private

  def no_self_reference
    if preceding_serial_id == succeeding_serial_id
      errors.add(:base, "A serial cannot precede or succeed itself")
    end
  end

  def no_cycles
    if creates_cycle?
      errors.add(:base, "This link would create a cycle in the chronology")
    end
  end

  def creates_cycle?
    # We want to see if the `preceding_serial` is reachable from the `succeeding_serial`
    # If so, adding this link would introduce a cycle
    visited = Set.new
    depth_first_search(succeeding_serial, visited).include?(preceding_serial)
  end

  def depth_first_search(serial, visited)
    return visited if visited.include?(serial)

    visited.add(serial)
    successors = SerialChronology.where(preceding_serial: serial).includes(:succeeding_serial).map(&:succeeding_serial)
    successors.each { |succ| depth_first_search(succ, visited) }

    visited
  end
end

Instance Method Details

#creates_cycle?Boolean (private)

Returns:

  • (Boolean)


45
46
47
48
49
50
# File 'app/models/serial_chronology.rb', line 45

def creates_cycle?
  # We want to see if the `preceding_serial` is reachable from the `succeeding_serial`
  # If so, adding this link would introduce a cycle
  visited = Set.new
  depth_first_search(succeeding_serial, visited).include?(preceding_serial)
end

#depth_first_search(serial, visited) ⇒ Object (private)



52
53
54
55
56
57
58
59
60
# File 'app/models/serial_chronology.rb', line 52

def depth_first_search(serial, visited)
  return visited if visited.include?(serial)

  visited.add(serial)
  successors = SerialChronology.where(preceding_serial: serial).includes(:succeeding_serial).map(&:succeeding_serial)
  successors.each { |succ| depth_first_search(succ, visited) }

  visited
end

#no_cyclesObject (private)



39
40
41
42
43
# File 'app/models/serial_chronology.rb', line 39

def no_cycles
  if creates_cycle?
    errors.add(:base, "This link would create a cycle in the chronology")
  end
end

#no_self_referenceObject (private)



33
34
35
36
37
# File 'app/models/serial_chronology.rb', line 33

def no_self_reference
  if preceding_serial_id == succeeding_serial_id
    errors.add(:base, "A serial cannot precede or succeed itself")
  end
end