From 438a0773dc850d3fa690881ea0b022bc27435e1e Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Wed, 6 Sep 2017 16:29:52 +0200 Subject: [PATCH] Add a concern to build hierarchies of groups --- app/models/concerns/group_hierarchy.rb | 27 +++++++++ app/models/group.rb | 1 + app/models/project.rb | 1 + spec/models/concerns/group_hierarchy_spec.rb | 61 ++++++++++++++++++++ 4 files changed, 90 insertions(+) create mode 100644 app/models/concerns/group_hierarchy.rb create mode 100644 spec/models/concerns/group_hierarchy_spec.rb diff --git a/app/models/concerns/group_hierarchy.rb b/app/models/concerns/group_hierarchy.rb new file mode 100644 index 00000000000..03864023771 --- /dev/null +++ b/app/models/concerns/group_hierarchy.rb @@ -0,0 +1,27 @@ +module GroupHierarchy + def hierarchy(hierarchy_base = nil) + @hierarchy ||= tree_for_child(self, self, hierarchy_base) + end + + def parent + if self.is_a?(Project) + namespace + else + super + end + end + + def tree_for_child(child, tree, hierarchy_base) + if child.parent.nil? && hierarchy_base.present? + raise ArgumentError.new('specified base is not part of the tree') + end + + if child.parent != hierarchy_base + tree_for_child(child.parent, + { child.parent => tree }, + hierarchy_base) + else + tree + end + end +end diff --git a/app/models/group.rb b/app/models/group.rb index e746e4a12c9..848e422e067 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -6,6 +6,7 @@ class Group < Namespace include Avatarable include Referable include SelectForProjectAuthorization + include GroupHierarchy has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent alias_method :members, :group_members diff --git a/app/models/project.rb b/app/models/project.rb index 59b5a5b3cd7..c221055f8b4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -17,6 +17,7 @@ class Project < ActiveRecord::Base include ProjectFeaturesCompatibility include SelectForProjectAuthorization include Routable + include GroupHierarchy extend Gitlab::ConfigHelper extend Gitlab::CurrentSettings diff --git a/spec/models/concerns/group_hierarchy_spec.rb b/spec/models/concerns/group_hierarchy_spec.rb new file mode 100644 index 00000000000..14ac910c90d --- /dev/null +++ b/spec/models/concerns/group_hierarchy_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +describe GroupHierarchy, :nested_groups do + let(:parent) { create(:group) } + let(:subgroup) { create(:group, parent: parent) } + let(:subsub_group) { create(:group, parent: subgroup) } + + context 'for a group' do + describe '#hierarchy' do + it 'builds a hierarchy for a group' do + expected_hierarchy = { parent => { subgroup => subsub_group } } + + expect(subsub_group.hierarchy).to eq(expected_hierarchy) + end + + it 'builds a hierarchy upto a specified parent' do + expected_hierarchy = { subgroup => subsub_group } + + expect(subsub_group.hierarchy(parent)).to eq(expected_hierarchy) + end + + it 'raises an error if specifying a base that is not part of the tree' do + expect { subsub_group.hierarchy(double) }.to raise_error('specified base is not part of the tree') + end + end + + describe '#parent' do + it 'returns the correct parent' do + expect(subsub_group.parent).to eq(subgroup) + end + end + end + + context 'for a project' do + let(:project) { create(:project, namespace: subsub_group) } + + describe '#hierarchy' do + it 'builds a hierarchy for a group' do + expected_hierarchy = { parent => { subgroup => { subsub_group => project } } } + + expect(project.hierarchy).to eq(expected_hierarchy) + end + + it 'builds a hierarchy upto a specified parent' do + expected_hierarchy = { subsub_group => project } + + expect(project.hierarchy(subgroup)).to eq(expected_hierarchy) + end + + it 'raises an error if specifying a base that is not part of the tree' do + expect { project.hierarchy(double) }.to raise_error('specified base is not part of the tree') + end + end + + describe '#parent' do + it 'returns the correct parent' do + expect(project.parent).to eq(subsub_group) + end + end + end +end