diff --git a/app/models/group.rb b/app/models/group.rb index 4e12aed..316295e 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -7,5 +7,7 @@ class Group < ApplicationRecord has_many :children, class_name: 'Group', foreign_key: 'parent_id' belongs_to :parent, class_name: 'Group', optional: true + scope :roots, -> { where(parent_id: nil) } + has_many :guests end diff --git a/app/services/affinity_groups_hierarchy.rb b/app/services/affinity_groups_hierarchy.rb index 7c87ed9..e20de97 100644 --- a/app/services/affinity_groups_hierarchy.rb +++ b/app/services/affinity_groups_hierarchy.rb @@ -6,24 +6,40 @@ class AffinityGroupsHierarchy < Array def initialize super @references = {} + + Group.roots.each do |group| + self << group.id + + hydrate(group) + end end - def find(name) - @references[name] + def find(id) + @references[id] end - def <<(name) - new_node = Tree::TreeNode.new(name) - super(new_node).tap { @references[name] = new_node } + def <<(id) + new_node = Tree::TreeNode.new(id) + super(new_node).tap { @references[id] = new_node } end - def register_child(parent_name, child_name) - @references[parent_name] << Tree::TreeNode.new(child_name).tap { |child_node| @references[child_name] = child_node } + def register_child(parent_id, child_id) + @references[parent_id] << Tree::TreeNode.new(child_id).tap { |child_node| @references[child_id] = child_node } end - def distance(name_a, name_b) - return nil if @references[name_a].nil? || @references[name_b].nil? + def distance(id_a, id_b) + return nil if @references[id_a].nil? || @references[id_b].nil? - @references[name_a].distance_to_common_ancestor(@references[name_b]) + @references[id_a].distance_to_common_ancestor(@references[id_b]) + end + + private + + def hydrate(group) + group.children.each do |child| + register_child(group.id, child.id) + + hydrate(child) + end end end diff --git a/app/services/tables/discomfort_calculator.rb b/app/services/tables/discomfort_calculator.rb index d6907e8..09b5356 100644 --- a/app/services/tables/discomfort_calculator.rb +++ b/app/services/tables/discomfort_calculator.rb @@ -14,7 +14,7 @@ module Tables private def cohesion_penalty - table.map { |guest| guest.affinity_group_list.first }.tally.to_a.combination(2).sum do |(a, count_a), (b, count_b)| + table.map(&:group_id).tally.to_a.combination(2).sum do |(a, count_a), (b, count_b)| distance = AffinityGroupsHierarchy.instance.distance(a, b) next count_a * count_b if distance.nil? diff --git a/config/initializers/affinity_groups.rb b/config/initializers/affinity_groups.rb deleted file mode 100644 index 1415e8d..0000000 --- a/config/initializers/affinity_groups.rb +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (C) 2024 Manuel Bustillo - -require_relative '../../app/services/affinity_groups_hierarchy' - -hierarchy = AffinityGroupsHierarchy.instance - -hierarchy << 'guests_a' -hierarchy << 'guests_b' -hierarchy << 'common_guests' - -hierarchy.register_child('guests_a', 'family_a') -hierarchy.register_child('family_a', 'close_family_a') -hierarchy.register_child('family_a', 'cousins_a') -hierarchy.register_child('family_a', 'relatives_a') - -hierarchy.register_child('guests_a', 'work_a') -hierarchy.register_child('work_a', 'besties_work_a') - -hierarchy.register_child('guests_a', 'friends_a') -hierarchy.register_child('friends_a', 'college_friends_a') -hierarchy.register_child('friends_a', 'high_school_friends_a') -hierarchy.register_child('friends_a', 'childhood_friends_a') - -hierarchy.register_child('guests_a', 'sports_a') -hierarchy.register_child('sports_a', 'basket_team_a') -hierarchy.register_child('sports_a', 'football_team_a') - -hierarchy.register_child('guests_b', 'family_b') -hierarchy.register_child('family_b', 'close_family_b') -hierarchy.register_child('family_b', 'cousins_b') -hierarchy.register_child('family_b', 'relatives_b') - -hierarchy.register_child('guests_b', 'work_b') -hierarchy.register_child('work_b', 'besties_work_b') - -hierarchy.register_child('guests_b', 'friends_b') -hierarchy.register_child('friends_b', 'college_friends_b') -hierarchy.register_child('friends_b', 'high_school_friends_b') -hierarchy.register_child('friends_b', 'childhood_friends_b') - -hierarchy.register_child('common_guests', 'dance_club') \ No newline at end of file diff --git a/spec/services/tables/discomfort_calculator_spec.rb b/spec/services/tables/discomfort_calculator_spec.rb index c2426ba..0e22602 100644 --- a/spec/services/tables/discomfort_calculator_spec.rb +++ b/spec/services/tables/discomfort_calculator_spec.rb @@ -5,6 +5,11 @@ module Tables RSpec.describe DiscomfortCalculator do let(:calculator) { described_class.new(table) } + let(:family) { create(:group, name: 'family') } + let(:friends) { create(:group, name: 'friends') } + let(:work) { create(:group, name: 'work') } + let(:school) { create(:group, name: 'school') } + describe '#cohesion_penalty' do before do # Overridden in each test except trivial cases @@ -14,16 +19,16 @@ module Tables allow(AffinityGroupsHierarchy.instance).to receive(:distance).with(group, group).and_return(0) end - allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('family', 'friends').and_return(nil) - allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('friends', 'work').and_return(1) - allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('family', 'work').and_return(2) - allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('family', 'school').and_return(3) - allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('friends', 'school').and_return(4) - allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('work', 'school').and_return(5) + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with(family.id, friends.id).and_return(nil) + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with(friends.id, work.id).and_return(1) + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with(family.id, work.id).and_return(2) + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with(family.id, school.id).and_return(3) + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with(friends.id, school.id).and_return(4) + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with(work.id, school.id).and_return(5) end context 'when the table contains just two guests' do context 'when they belong to the same group' do - let(:table) { create_list(:guest, 2, affinity_group_list: ['family']) } + let(:table) { create_list(:guest, 2, group: family) } it { expect(calculator.send(:cohesion_penalty)).to eq(0) } end @@ -31,8 +36,8 @@ module Tables context 'when they belong to completely unrelated groups' do let(:table) do [ - create(:guest, affinity_group_list: ['family']), - create(:guest, affinity_group_list: ['friends']) + create(:guest, group: family), + create(:guest, group: friends) ] end it { expect(calculator.send(:cohesion_penalty)).to eq(1) } @@ -41,8 +46,8 @@ module Tables context 'when they belong to groups at a distance of 1' do let(:table) do [ - create(:guest, affinity_group_list: ['friends']), - create(:guest, affinity_group_list: ['work']) + create(:guest, group: friends), + create(:guest, group: work) ] end @@ -52,8 +57,8 @@ module Tables context 'when they belong to groups at a distance of 2' do let(:table) do [ - create(:guest, affinity_group_list: ['family']), - create(:guest, affinity_group_list: ['work']) + create(:guest, group: family), + create(:guest, group: work) ] end @@ -63,8 +68,8 @@ module Tables context 'when they belong to groups at a distance of 3' do let(:table) do [ - create(:guest, affinity_group_list: ['family']), - create(:guest, affinity_group_list: ['school']) + create(:guest, group: family), + create(:guest, group: school) ] end @@ -75,9 +80,9 @@ module Tables context 'when the table contains three guests' do let(:table) do [ - create(:guest, affinity_group_list: ['family']), - create(:guest, affinity_group_list: ['friends']), - create(:guest, affinity_group_list: ['work']) + create(:guest, group: family), + create(:guest, group: friends), + create(:guest, group: work) ] end @@ -89,10 +94,10 @@ module Tables context 'when the table contains four guests of different groups' do let(:table) do [ - create(:guest, affinity_group_list: ['family']), - create(:guest, affinity_group_list: ['friends']), - create(:guest, affinity_group_list: ['work']), - create(:guest, affinity_group_list: ['school']) + create(:guest, group: family), + create(:guest, group: friends), + create(:guest, group: work), + create(:guest, group: school) ] end @@ -105,8 +110,8 @@ module Tables context 'when the table contains four guests of two evenly split groups' do let(:table) do [ - create_list(:guest, 2, affinity_group_list: ['family']), - create_list(:guest, 2, affinity_group_list: ['friends']) + create_list(:guest, 2, group: family), + create_list(:guest, 2, group: friends) ].flatten end @@ -118,8 +123,8 @@ module Tables context 'when the table contains six guests of two unevenly split groups' do let(:table) do [ - create_list(:guest, 2, affinity_group_list: ['family']), - create_list(:guest, 4, affinity_group_list: ['friends']) + create_list(:guest, 2, group: family), + create_list(:guest, 4, group: friends) ].flatten end @@ -131,9 +136,9 @@ module Tables context 'when the table contains six guests of three evenly split groups' do let(:table) do [ - create_list(:guest, 2, affinity_group_list: ['family']), - create_list(:guest, 2, affinity_group_list: ['friends']), - create_list(:guest, 2, affinity_group_list: ['work']) + create_list(:guest, 2, group: family), + create_list(:guest, 2, group: friends), + create_list(:guest, 2, group: work) ].flatten end @@ -145,9 +150,9 @@ module Tables context 'when the table contains six guests of three unevenly split groups' do let(:table) do [ - create_list(:guest, 3, affinity_group_list: ['family']), - create_list(:guest, 2, affinity_group_list: ['friends']), - create_list(:guest, 1, affinity_group_list: ['work']) + create_list(:guest, 3, group: family), + create_list(:guest, 2, group: friends), + create_list(:guest, 1, group: work) ].flatten end