Class: Diagrams::Base
- Inherits:
-
Object
- Object
- Diagrams::Base
- Defined in:
- lib/diagrams/base.rb
Overview
Abstract base class for all diagram types. Provides common functionality like versioning, checksum calculation, serialization, and equality comparison.
Direct Known Subclasses
ClassDiagram, ERDiagram, FlowchartDiagram, GanttDiagram, GitgraphDiagram, PieDiagram, StateDiagram, TimelineDiagram
Instance Attribute Summary collapse
-
#checksum ⇒ Object
readonly
Returns the value of attribute checksum.
-
#version ⇒ Object
readonly
Returns the value of attribute version.
Class Method Summary collapse
-
.from_hash(hash) ⇒ Diagrams::Base
Deserializes a diagram from a hash representation.
-
.from_json(json_string) ⇒ Diagrams::Base
Deserializes a diagram from a JSON string.
Instance Method Summary collapse
-
#diff(other) ⇒ Hash{Symbol => Hash{Symbol => Array<Diagrams::Elements::*>}}
Performs a basic diff against another diagram object.
-
#identifiable_elements ⇒ Hash{Symbol => Array<Diagrams::Elements::*>}
Abstract method: Subclasses must implement this to return a hash mapping element type symbols (e.g., :nodes, :edges) to arrays of the corresponding element objects within the diagram.
-
#initialize(version: 1) ⇒ Base
constructor
Initializes the base diagram attributes.
-
#to_h ⇒ Hash
Returns a hash representation of the diagram, suitable for serialization.
-
#to_h_content ⇒ Hash
Abstract method: Subclasses must implement this to return a hash representing their specific content, suitable for serialization.
-
#to_json ⇒ String
Returns a JSON string representation of the diagram.
Constructor Details
#initialize(version: 1) ⇒ Base
Initializes the base diagram attributes. Subclasses should call super.
18 19 20 21 22 23 24 |
# File 'lib/diagrams/base.rb', line 18 def initialize(version: 1) # Prevent direct instantiation of the base class raise NotImplementedError, 'Cannot instantiate abstract class Diagrams::Base' if instance_of?(Diagrams::Base) @version = version @checksum = nil # Will be calculated by subclasses via #update_checksum! after content is set end |
Instance Attribute Details
#checksum ⇒ Object (readonly)
Returns the value of attribute checksum.
12 13 14 |
# File 'lib/diagrams/base.rb', line 12 def checksum @checksum end |
#version ⇒ Object (readonly)
Returns the value of attribute version.
12 13 14 |
# File 'lib/diagrams/base.rb', line 12 def version @version end |
Class Method Details
.from_hash(hash) ⇒ Diagrams::Base
Deserializes a diagram from a hash representation. Acts as a factory, dispatching to the appropriate subclass based on the ‘type’ field.
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/diagrams/base.rb', line 153 def from_hash(hash) # Ensure keys are symbols for consistent access symbolized_hash = hash.transform_keys(&:to_sym) type_string = symbolized_hash[:type] raise ArgumentError, "Input hash must include a 'type' key." unless type_string data_hash = symbolized_hash[:data] || {} version = symbolized_hash[:version] checksum = symbolized_hash[:checksum] # Pass checksum for potential verification begin # Convert snake_case type string back to CamelCase class name part camel_case_type = snake_to_camel_case(type_string) # Construct full class name (e.g., "Diagrams::FlowchartDiagram") klass_name = "Diagrams::#{camel_case_type}" klass = Object.const_get(klass_name) rescue NameError raise NameError, "Unknown diagram type '#{type_string}' corresponding to class '#{klass_name}'" end # Ensure the resolved class is actually a diagram type raise TypeError, "'#{klass_name}' is not a valid subclass of Diagrams::Base" unless klass < Diagrams::Base # Delegate to the specific subclass's from_h method # Each subclass must implement `from_h(data_hash, version:, checksum:)` klass.from_h(data_hash, version:, checksum:) end |
.from_json(json_string) ⇒ Diagrams::Base
Deserializes a diagram from a JSON string. Parses the JSON and delegates to ‘.from_hash`.
187 188 189 190 191 192 |
# File 'lib/diagrams/base.rb', line 187 def from_json(json_string) hash = JSON.parse(json_string) from_hash(hash) rescue JSON::ParserError => e raise JSON::ParserError, "Failed to parse JSON: #{e.message}" end |
Instance Method Details
#diff(other) ⇒ Hash{Symbol => Hash{Symbol => Array<Diagrams::Elements::*>}}
Performs a basic diff against another diagram object. Only compares diagrams of the same type. Identifies added and removed elements based on common identifiers (id/name) or object equality. Does NOT currently detect modified elements.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/diagrams/base.rb', line 53 def diff(other) diff_result = {} return diff_result unless other.is_a?(self.class) # Only compare same types return diff_result if self == other # Use existing equality check for quick exit self_elements = identifiable_elements other_elements = other.identifiable_elements # Ensure both diagrams define the same element types for comparison element_types = self_elements.keys & other_elements.keys element_types.each do |type| self_collection = self_elements[type] || [] other_collection = other_elements[type] || [] # Determine identifier method (prefer id, then name, then title, then label, fallback to object itself) identifier_method = if self_collection.first.respond_to?(:id) :id elsif self_collection.first.respond_to?(:name) :name elsif self_collection.first.respond_to?(:title) # For TimelineSection :title elsif self_collection.first.respond_to?(:label) # For Slice, TimelinePeriod :label else :itself # Fallback to object identity/equality end self_ids = self_collection.map(&identifier_method) other_ids = other_collection.map(&identifier_method) added_ids = other_ids - self_ids removed_ids = self_ids - other_ids added_elements = other_collection.select { |el| added_ids.include?(el.send(identifier_method)) } removed_elements = self_collection.select { |el| removed_ids.include?(el.send(identifier_method)) } # Basic check for modified elements (same ID, different content via checksum/hash if available, or simple !=) # This is a very basic modification check potential_modified_ids = self_ids & other_ids modified_elements = [] potential_modified_ids.each do |id| self_el = self_collection.find { |el| el.send(identifier_method) == id } other_el = other_collection.find { |el| el.send(identifier_method) == id } # Use Dry::Struct equality if available, otherwise basic != next unless self_el != other_el modified_elements << { old: self_el, new: other_el } # Remove from added/removed if detected as modified added_elements.delete(other_el) removed_elements.delete(self_el) end type_diff = {} type_diff[:added] = added_elements if added_elements.any? type_diff[:removed] = removed_elements if removed_elements.any? type_diff[:modified] = modified_elements if modified_elements.any? # Add modified info diff_result[type] = type_diff if type_diff.any? end diff_result end |
#identifiable_elements ⇒ Hash{Symbol => Array<Diagrams::Elements::*>}
Abstract method: Subclasses must implement this to return a hash mapping element type symbols (e.g., :nodes, :edges) to arrays of the corresponding element objects within the diagram. Used for comparison and diffing.
40 41 42 |
# File 'lib/diagrams/base.rb', line 40 def identifiable_elements raise NotImplementedError, "#{self.class.name} must implement #identifiable_elements" end |
#to_h ⇒ Hash
Returns a hash representation of the diagram, suitable for serialization. Includes common metadata and calls ‘#to_h_content` for specific data.
121 122 123 124 125 126 127 128 129 130 |
# File 'lib/diagrams/base.rb', line 121 def to_h { # Extract class name without module prefix (e.g., "FlowchartDiagram") # Convert class name to snake_case (e.g., FlowchartDiagram -> flowchart_diagram) type: camel_to_snake_case(self.class.name.split('::').last), version: @version, checksum: @checksum, # Ensure checksum is up-to-date before calling data: to_h_content } end |
#to_h_content ⇒ Hash
Abstract method: Subclasses must implement this to return a hash representing their specific content, suitable for serialization.
30 31 32 |
# File 'lib/diagrams/base.rb', line 30 def to_h_content raise NotImplementedError, "#{self.class.name} must implement #to_h_content" end |
#to_json ⇒ String
Returns a JSON string representation of the diagram. Delegates to ‘#to_h` and uses `JSON.generate`. Accepts any arguments valid for `JSON.generate`.
138 139 140 |
# File 'lib/diagrams/base.rb', line 138 def to_json(*) JSON.generate(to_h, *) end |