Skip to content
Snippets Groups Projects
Verified Commit 2989192d authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets
Browse files

Store group and project full name and full path in routes table

parent bbb7fbcd
No related branches found
No related tags found
No related merge requests found
Showing
with 191 additions and 98 deletions
class Admin::DashboardController < Admin::ApplicationController
def index
@projects = Project.limit(10)
@projects = Project.with_route.limit(10)
@users = User.limit(10)
@groups = Group.limit(10)
@groups = Group.with_route.limit(10)
end
end
Loading
Loading
@@ -2,7 +2,7 @@ class Admin::GroupsController < Admin::ApplicationController
before_action :group, only: [:edit, :update, :destroy, :project_update, :members_update]
 
def index
@groups = Group.with_statistics
@groups = Group.with_statistics.with_route
@groups = @groups.sort(@sort = params[:sort])
@groups = @groups.search(params[:name]) if params[:name].present?
@groups = @groups.page(params[:page])
Loading
Loading
class Dashboard::GroupsController < Dashboard::ApplicationController
def index
@group_members = current_user.group_members.includes(:source).page(params[:page])
@group_members = current_user.group_members.includes(source: :route).page(params[:page])
end
end
Loading
Loading
@@ -2,7 +2,7 @@ class GroupsFinder < UnionFinder
def execute(current_user = nil)
segments = all_groups(current_user)
 
find_union(segments, Group).order_id_desc
find_union(segments, Group).with_route.order_id_desc
end
 
private
Loading
Loading
Loading
Loading
@@ -3,7 +3,7 @@ class ProjectsFinder < UnionFinder
segments = all_projects(current_user)
segments.map! { |s| s.where(id: project_ids_relation) } if project_ids_relation
 
find_union(segments, Project)
find_union(segments, Project).with_route
end
 
private
Loading
Loading
# Store object full path in separate table for easy lookup and uniq validation
# Object must have path db field and respond to full_path and full_path_changed? methods.
# Object must have name and path db fields and respond to parent and parent_changed? methods.
module Routable
extend ActiveSupport::Concern
 
Loading
Loading
@@ -9,7 +9,13 @@ module Routable
validates_associated :route
validates :route, presence: true
 
before_validation :update_route_path, if: :full_path_changed?
scope :with_route, -> { includes(:route) }
before_validation do
if full_path_changed? || full_name_changed?
prepare_route
end
end
end
 
class_methods do
Loading
Loading
@@ -77,10 +83,62 @@ module Routable
end
end
 
def full_name
if route && route.name.present?
@full_name ||= route.name
else
update_route if persisted?
build_full_name
end
end
def full_path
if route && route.path.present?
@full_path ||= route.path
else
update_route if persisted?
build_full_path
end
end
private
 
def update_route_path
def full_name_changed?
name_changed? || parent_changed?
end
def full_path_changed?
path_changed? || parent_changed?
end
def build_full_name
if parent && name
parent.human_name + ' / ' + name
else
name
end
end
def build_full_path
if parent && path
parent.full_path + '/' + path
else
path
end
end
def update_route
prepare_route
route.save
end
def prepare_route
route || build_route(source: self)
route.path = full_path
route.path = build_full_path
route.name = build_full_name
@full_path = nil
@full_name = nil
end
end
Loading
Loading
@@ -170,27 +170,10 @@ class Namespace < ActiveRecord::Base
Gitlab.config.lfs.enabled
end
 
def full_path
if parent
parent.full_path + '/' + path
else
path
end
end
def shared_runners_enabled?
projects.with_shared_runners.any?
end
 
def full_name
@full_name ||=
if parent
parent.full_name + ' / ' + name
else
name
end
end
# Scopes the model on ancestors of the record
def ancestors
if parent_id
Loading
Loading
@@ -217,6 +200,10 @@ class Namespace < ActiveRecord::Base
[owner_id]
end
 
def parent_changed?
parent_id_changed?
end
private
 
def repository_storage_paths
Loading
Loading
@@ -255,10 +242,6 @@ class Namespace < ActiveRecord::Base
find_each(&:refresh_members_authorized_projects)
end
 
def full_path_changed?
path_changed? || parent_id_changed?
end
def remove_exports!
Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete))
end
Loading
Loading
Loading
Loading
@@ -228,7 +228,12 @@ class Project < ActiveRecord::Base
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
scope :with_statistics, -> { includes(:statistics) }
scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
scope :inside_path, ->(path) { joins(:route).where('routes.path LIKE ?', "#{path}/%") }
scope :inside_path, ->(path) do
# We need routes alias rs for JOIN so it does not conflict with
# includes(:route) which we use in ProjectsFinder.
joins("INNER JOIN routes rs ON rs.source_id = projects.id AND rs.source_type = 'Project'").
where('rs.path LIKE ?', "#{path}/%")
end
 
# "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) {
Loading
Loading
@@ -810,26 +815,6 @@ class Project < ActiveRecord::Base
end
end
 
def name_with_namespace
@name_with_namespace ||= begin
if namespace
namespace.human_name + ' / ' + name
else
name
end
end
end
alias_method :human_name, :name_with_namespace
def full_path
if namespace && path
namespace.full_path + '/' + path
else
path
end
end
alias_method :path_with_namespace, :full_path
def execute_hooks(data, hooks_scope = :push_hooks)
hooks.send(hooks_scope).each do |hook|
hook.async_execute(data, hooks_scope.to_s)
Loading
Loading
@@ -1328,6 +1313,18 @@ class Project < ActiveRecord::Base
map.public_path_for_source_path(path)
end
 
def parent
namespace
end
def parent_changed?
namespace_id_changed?
end
alias_method :name_with_namespace, :full_name
alias_method :human_name, :full_name
alias_method :path_with_namespace, :full_path
private
 
def cross_namespace_reference?(from)
Loading
Loading
@@ -1366,10 +1363,6 @@ class Project < ActiveRecord::Base
raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS
end
 
def full_path_changed?
path_changed? || namespace_id_changed?
end
def update_project_statistics
stats = statistics || build_statistics
stats.update(namespace_id: namespace_id)
Loading
Loading
Loading
Loading
@@ -8,16 +8,22 @@ class Route < ActiveRecord::Base
presence: true,
uniqueness: { case_sensitive: false }
 
after_update :rename_descendants, if: :path_changed?
after_update :rename_descendants
 
def rename_descendants
# We update each row separately because MySQL does not have regexp_replace.
# rubocop:disable Rails/FindEach
Route.where('path LIKE ?', "#{path_was}/%").each do |route|
# Note that update column skips validation and callbacks.
# We need this to avoid recursive call of rename_descendants method
route.update_column(:path, route.path.sub(path_was, path))
if path_changed? || name_changed?
descendants = Route.where('path LIKE ?', "#{path_was}/%")
descendants.each do |route|
attributes = {
path: route.path.sub(path_was, path),
name: route.name.sub(name_was, name)
}
# Note that update_columns skips validation and callbacks.
# We need this to avoid recursive call of rename_descendants method
route.update_columns(attributes)
end
end
# rubocop:enable Rails/FindEach
end
end
---
title: Store group and project full name and full path in routes table
merge_request: 8979
author:
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddNameToRoute < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :routes, :name, :string
end
end
Loading
Loading
@@ -1037,6 +1037,7 @@ ActiveRecord::Schema.define(version: 20170206101030) do
t.string "path", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "name"
end
 
add_index "routes", ["path"], name: "index_routes_on_path", unique: true, using: :btree
Loading
Loading
Loading
Loading
@@ -14,12 +14,14 @@ describe Group, 'Routable' do
describe 'Callbacks' do
it 'creates route record on create' do
expect(group.route.path).to eq(group.path)
expect(group.route.name).to eq(group.name)
end
 
it 'updates route record on path change' do
group.update_attributes(path: 'wow')
group.update_attributes(path: 'wow', name: 'much')
 
expect(group.route.path).to eq('wow')
expect(group.route.name).to eq('much')
end
 
it 'ensure route path uniqueness across different objects' do
Loading
Loading
@@ -78,4 +80,34 @@ describe Group, 'Routable' do
 
it { is_expected.to eq([nested_group]) }
end
describe '#full_path' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
it { expect(group.full_path).to eq(group.path) }
it { expect(nested_group.full_path).to eq("#{group.path}/#{nested_group.path}") }
end
describe '#full_name' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
it { expect(group.full_name).to eq(group.name) }
it { expect(nested_group.full_name).to eq("#{group.name} / #{nested_group.name}") }
end
end
describe Project, 'Routable' do
describe '#full_path' do
let(:project) { build_stubbed(:empty_project) }
it { expect(project.full_path).to eq "#{project.namespace.path}/#{project.path}" }
end
describe '#full_name' do
let(:project) { build_stubbed(:empty_project) }
it { expect(project.full_name).to eq "#{project.namespace.human_name} / #{project.name}" }
end
end
Loading
Loading
@@ -175,22 +175,6 @@ describe Namespace, models: true do
end
end
 
describe '#full_path' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
it { expect(group.full_path).to eq(group.path) }
it { expect(nested_group.full_path).to eq("#{group.path}/#{nested_group.path}") }
end
describe '#full_name' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
it { expect(group.full_name).to eq(group.name) }
it { expect(nested_group.full_name).to eq("#{group.name} / #{nested_group.name}") }
end
describe '#ancestors' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
Loading
Loading
Loading
Loading
@@ -275,13 +275,6 @@ describe Project, models: true do
it { is_expected.to delegate_method(:add_master).to(:team) }
end
 
describe '#name_with_namespace' do
let(:project) { build_stubbed(:empty_project) }
it { expect(project.name_with_namespace).to eq "#{project.namespace.human_name} / #{project.name}" }
it { expect(project.human_name).to eq project.name_with_namespace }
end
describe '#to_reference' do
let(:owner) { create(:user, name: 'Gitlab') }
let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) }
Loading
Loading
@@ -1840,6 +1833,20 @@ describe Project, models: true do
end
end
 
describe '#parent' do
let(:project) { create(:empty_project) }
it { expect(project.parent).to eq(project.namespace) }
end
describe '#parent_changed?' do
let(:project) { create(:empty_project) }
before { project.namespace_id = 7 }
it { expect(project.parent_changed?).to be_truthy }
end
def enable_lfs
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
end
Loading
Loading
require 'spec_helper'
 
describe Route, models: true do
let!(:group) { create(:group, path: 'gitlab') }
let!(:group) { create(:group, path: 'gitlab', name: 'gitlab') }
let!(:route) { group.route }
 
describe 'relationships' do
Loading
Loading
@@ -15,17 +15,30 @@ describe Route, models: true do
end
 
describe '#rename_descendants' do
let!(:nested_group) { create(:group, path: "test", parent: group) }
let!(:deep_nested_group) { create(:group, path: "foo", parent: nested_group) }
let!(:similar_group) { create(:group, path: 'gitlab-org') }
let!(:nested_group) { create(:group, path: 'test', name: 'test', parent: group) }
let!(:deep_nested_group) { create(:group, path: 'foo', name: 'foo', parent: nested_group) }
let!(:similar_group) { create(:group, path: 'gitlab-org', name: 'gitlab-org') }
 
before { route.update_attributes(path: 'bar') }
context 'path update' do
before { route.update_attributes(path: 'bar') }
 
it "updates children routes with new path" do
expect(described_class.exists?(path: 'bar')).to be_truthy
expect(described_class.exists?(path: 'bar/test')).to be_truthy
expect(described_class.exists?(path: 'bar/test/foo')).to be_truthy
expect(described_class.exists?(path: 'gitlab-org')).to be_truthy
it "updates children routes with new path" do
expect(described_class.exists?(path: 'bar')).to be_truthy
expect(described_class.exists?(path: 'bar/test')).to be_truthy
expect(described_class.exists?(path: 'bar/test/foo')).to be_truthy
expect(described_class.exists?(path: 'gitlab-org')).to be_truthy
end
end
context 'name update' do
before { route.update_attributes(name: 'bar') }
it "updates children routes with new path" do
expect(described_class.exists?(name: 'bar')).to be_truthy
expect(described_class.exists?(name: 'bar / test')).to be_truthy
expect(described_class.exists?(name: 'bar / test / foo')).to be_truthy
expect(described_class.exists?(name: 'gitlab-org')).to be_truthy
end
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment