diff options
| author | Szymon Szukalski <szymon@skas.io> | 2024-10-24 21:10:57 +1100 |
|---|---|---|
| committer | Szymon Szukalski <szymon@skas.io> | 2024-10-24 21:10:57 +1100 |
| commit | 9bc26146397acb5a216e20d5eb55bb2a582fdd3e (patch) | |
| tree | 4c02f13ca30e673417870114050f7a3d653ad47d /lib | |
| parent | 55475178a8c0e610103e37027cc0a7a387d72f91 (diff) | |
Implement key data model
- Added classes for Person, Gender, Family, FamilyTree
- Replaced FamilyTreeManager with FamilyTree
- Add FamilyFactory for seeding the initial FamilyTree for King Arthur and Queen Margaret
- Added a RelationshipManager for linking spouses correctly
- Refactored ActionFileExecutor for readability
- More test coverage
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/action_file_executor.rb | 52 | ||||
| -rw-r--r-- | lib/family.rb | 43 | ||||
| -rw-r--r-- | lib/family_factory.rb | 155 | ||||
| -rw-r--r-- | lib/family_tree.rb | 68 | ||||
| -rw-r--r-- | lib/family_tree_manager.rb | 21 | ||||
| -rw-r--r-- | lib/gender.rb | 16 | ||||
| -rw-r--r-- | lib/person.rb | 57 | ||||
| -rw-r--r-- | lib/relationship_manager.rb | 21 |
8 files changed, 400 insertions, 33 deletions
diff --git a/lib/action_file_executor.rb b/lib/action_file_executor.rb index 1cd3a94..49cb4b5 100644 --- a/lib/action_file_executor.rb +++ b/lib/action_file_executor.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative 'family_tree_manager' +require_relative 'family_tree' class ActionFileExecutor def initialize(file_path) @@ -11,15 +11,7 @@ class ActionFileExecutor def execute_actions File.open(@file_path, 'r') do |file| file.each_line do |line| - action, *params = line.split(' ') - case action - when 'ADD_CHILD' - FamilyTreeManager.instance.add_child(*params) - when 'GET_RELATIONSHIP' - FamilyTreeManager.instance.query_hierarchy(*params) - else - puts "Ignoring unsupported action: [#{action}]" - end + process_line(line.strip) end end end @@ -32,4 +24,44 @@ class ActionFileExecutor puts "Error: The file '#{@file_path}' does not exist." exit 1 end + + def process_line(line) + return if line.empty? || comment?(line) + + action, params = extract_action_and_params(line) + execute_action(action, params) if action + end + + def comment?(line) + line.start_with?('#') + end + + def extract_action_and_params(line) + match = line.match(/^(\S+)(.*)$/) + return unless match + + action = match[1] + params = match[2].scan(/"([^"]+)"|(\S+)/).flatten.compact + [action, params] + end + + def execute_action(action, params) + case action + when 'ADD_CHILD' + handle_add_child(*params) + when 'GET_RELATIONSHIP' + handle_get_relationship(*params) + else + puts "Ignoring unsupported action: [#{action}]" + end + end + + def handle_add_child(*params) + FamilyTree.instance.add_child(*params) + end + + def handle_get_relationship(*params) + result = FamilyTree.instance.get_relationship(*params) + puts result.empty? ? 'NONE' : result.map(&:name).join(', ') + end end diff --git a/lib/family.rb b/lib/family.rb new file mode 100644 index 0000000..2c1acca --- /dev/null +++ b/lib/family.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require_relative 'person' + +class Family + attr_reader :mother, :father, :children + + def initialize(mother = NilPerson.new, father = NilPerson.new, children = []) + raise ArgumentError, 'Mother must be female' if !mother.is_a?(NilPerson) && mother.gender != Gender::FEMALE + raise ArgumentError, 'Father must be male' if !father.is_a?(NilPerson) && father.gender != Gender::MALE + + @mother = mother + @father = father + @children = children + end + + def assign_mother(mother) + raise ArgumentError, 'Mother must be female' if mother.gender != Gender::FEMALE + + @mother = mother + end + + def assign_father(father) + raise ArgumentError, 'Father must be male' if father.gender != Gender::MALE + + @father = father + end + + def add_child(child) + @children << child unless @children.include?(child) + end + + def get_siblings(person) + @children.reject { |child| child == person } + end + + def to_s + mother_str = mother.is_a?(NilPerson) ? 'UNKNOWN' : mother.name + father_str = father.is_a?(NilPerson) ? 'UNKNOWN' : father.name + children_str = children.empty? ? 'NONE' : children.map(&:name).join(', ') + "Family: Mother: #{mother_str}, Father: #{father_str}, Children: #{children_str}" + end +end diff --git a/lib/family_factory.rb b/lib/family_factory.rb new file mode 100644 index 0000000..a42c5ea --- /dev/null +++ b/lib/family_factory.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +require_relative 'family' +require_relative 'person' +require_relative 'gender' +require_relative 'relationship_manager' + +class FamilyFactory + def initialize + @people = {} + @relationship_manager = RelationshipManager.instance + end + + def create_families + [ + create_queen_margaret_and_king_arthur_family, + create_flora_and_bill_family, + create_victoire_and_ted_family, + create_percy_and_audrey_family, + create_ronald_and_helen_family, + create_malfoy_and_rose_family, + create_ginerva_and_harry_family, + create_darcy_and_james_family, + create_alice_and_albus_family + ] + end + + private + + def find_or_create_person(name, gender) + return @people[name] if @people.key?(name) + + person = Person.new(name, gender) + @people[name] = person + end + + def find_or_create_male(name) + find_or_create_person(name, Gender::MALE) + end + + def find_or_create_female(name) + find_or_create_person(name, Gender::FEMALE) + end + + def create_queen_margaret_and_king_arthur_family + queen_margaret = find_or_create_female('Queen Margaret') + king_arthur = find_or_create_male('King Arthur') + + @relationship_manager.link_spouses(queen_margaret, king_arthur) + + bill = find_or_create_male('Bill') + charlie = find_or_create_male('Charlie') + percy = find_or_create_male('Percy') + ronald = find_or_create_male('Ronald') + ginerva = find_or_create_female('Ginerva') + + Family.new(queen_margaret, king_arthur, [bill, charlie, percy, ronald, ginerva]) + end + + def create_flora_and_bill_family + bill = find_or_create_male('Bill') + flora = find_or_create_female('Flora') + + @relationship_manager.link_spouses(flora, bill) + + victoire = find_or_create_female('Victoire') + dominique = find_or_create_female('Dominique') + louis = find_or_create_male('Louis') + + Family.new(flora, bill, [victoire, dominique, louis]) + end + + def create_victoire_and_ted_family + victoire = find_or_create_female('Victoire') + ted = find_or_create_male('Ted') + + @relationship_manager.link_spouses(victoire, ted) + + remus = find_or_create_male('Remus') + + Family.new(victoire, ted, [remus]) + end + + def create_percy_and_audrey_family + percy = find_or_create_male('Percy') + audrey = find_or_create_female('Audrey') + + @relationship_manager.link_spouses(audrey, percy) + + molly = find_or_create_female('Molly') + lucy = find_or_create_female('Lucy') + + Family.new(audrey, percy, [molly, lucy]) + end + + def create_ronald_and_helen_family + ronald = find_or_create_male('Ronald') + helen = find_or_create_female('Helen') + + @relationship_manager.link_spouses(helen, ronald) + + rose = find_or_create_female('Rose') + hugo = find_or_create_male('Hugo') + + Family.new(helen, ronald, [rose, hugo]) + end + + def create_malfoy_and_rose_family + malfoy = find_or_create_male('Malfoy') + rose = find_or_create_female('Rose') + + @relationship_manager.link_spouses(rose, malfoy) + + draco = find_or_create_male('Draco') + aster = find_or_create_female('Aster') + + Family.new(rose, malfoy, [draco, aster]) + end + + def create_ginerva_and_harry_family + ginerva = find_or_create_female('Ginerva') + harry = find_or_create_male('Harry') + + @relationship_manager.link_spouses(ginerva, harry) + + james = find_or_create_male('James') + albus = find_or_create_male('Albus') + lily = find_or_create_female('Lily') + + Family.new(ginerva, harry, [james, albus, lily]) + end + + def create_darcy_and_james_family + darcy = find_or_create_female('Darcy') + james = find_or_create_male('James') + + @relationship_manager.link_spouses(darcy, james) + + william = find_or_create_male('William') + + Family.new(darcy, james, [william]) + end + + def create_alice_and_albus_family + alice = find_or_create_female('Alice') + albus = find_or_create_male('Albus') + + @relationship_manager.link_spouses(alice, albus) + + ron = find_or_create_male('Ron') + ginny = find_or_create_female('Ginny') + + Family.new(alice, albus, [ron, ginny]) + end +end diff --git a/lib/family_tree.rb b/lib/family_tree.rb index 934d32e..ebb2955 100644 --- a/lib/family_tree.rb +++ b/lib/family_tree.rb @@ -1,15 +1,79 @@ # frozen_string_literal: true +require 'singleton' + +require_relative 'person' +require_relative 'family_factory' + class FamilyTree + include Singleton + + attr_accessor :families, :people + def initialize + @families = FamilyFactory.new.create_families @people = [] end + def add_family(family) + @families << family unless @families.include?(family) + end + def add_child(*params) puts "Adding Child with params: #{params.join(', ')}" end - def query_hierarchy(*params) - puts "Querying Hierarcy with params: #{params.join(', ')}" + def get_relationship(name, relationship) + family = find_family(name) + + return [] if family.nil? + + if child_of_family?(family, name) + case relationship.downcase + when 'mother' + mother = find_mother(family) + return mother.is_a?(NilPerson) ? [] : [mother] + when 'father' + father = find_father(family) + return father.is_a?(NilPerson) ? [] : [father] + when 'siblings' + return find_siblings(family, name) + else + return [] + end + end + + [] + end + + def find_family(name) + families.each do |family| + family.children.each do |child| + return family if child.name.casecmp(name).zero? + end + end + nil + end + + def find_mother(family) + mother = family.mother + mother || NilPerson.new + end + + def find_father(family) + father = family.father + father || NilPerson.new + end + + def find_siblings(family, name) + family.children.reject { |child| child.name.casecmp(name).zero? } + end + + def child_of_family?(family, name) + return false unless family.is_a?(Family) + + family.children.any? do |child| + child.name.casecmp(name).zero? + end end end diff --git a/lib/family_tree_manager.rb b/lib/family_tree_manager.rb deleted file mode 100644 index 56ab992..0000000 --- a/lib/family_tree_manager.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'singleton' - -require_relative 'family_tree' - -class FamilyTreeManager - include Singleton - - def initialize - @family_tree = FamilyTree.new - end - - def add_child(*params) - @family_tree.add_child(*params) - end - - def query_hierarchy(*params) - @family_tree.query_hierarchy(*params) - end -end diff --git a/lib/gender.rb b/lib/gender.rb new file mode 100644 index 0000000..9bd3b06 --- /dev/null +++ b/lib/gender.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class Gender + MALE = 'male' + FEMALE = 'female' + + def self.all + [MALE, FEMALE] + end + + def self.valid?(gender) + return false unless gender.is_a?(String) + + all.include?(gender.downcase) + end +end diff --git a/lib/person.rb b/lib/person.rb new file mode 100644 index 0000000..a40efbe --- /dev/null +++ b/lib/person.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +class Person + attr_accessor :name, :gender, :spouse + + def initialize(name, gender, spouse = NilPerson.new) + @name = name + @gender = gender + @spouse = spouse + end + + def ==(other) + other.is_a?(Person) && name == other.name && gender == other.gender && spouse == other.spouse + end + + def eql?(other) + self == other + end + + def hash + [name, gender, spouse].hash + end + + def to_s + "#{name} (#{gender})" + end +end + +class NilPerson + def name + nil + end + + def father + self + end + + def mother + self + end + + def gender + nil + end + + def ==(other) + other.is_a?(NilPerson) + end + + def eql?(other) + self == other + end + + def to_s + '' + end +end diff --git a/lib/relationship_manager.rb b/lib/relationship_manager.rb new file mode 100644 index 0000000..4b6a4a2 --- /dev/null +++ b/lib/relationship_manager.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'singleton' + +class RelationshipManager + include Singleton + def link_spouses(person1, person2) + # Check if either person is already linked to someone else + if person1.spouse != NilPerson.new && person1.spouse != person2 + raise "Cannot link #{person1.name} and #{person2.name}: #{person1.name} is already linked to #{person1.spouse.name}." + end + + if person2.spouse != NilPerson.new && person2.spouse != person1 + raise "Cannot link #{person1.name} and #{person2.name}: #{person2.name} is already linked to #{person2.spouse.name}." + end + + # Link the spouses + person1.spouse = person2 + person2.spouse = person1 unless person2.is_a?(NilPerson) + end +end |
