summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorick Peterse <yorickpeterse@gmail.com>2016-06-08 17:03:51 +0200
committerYorick Peterse <yorickpeterse@gmail.com>2016-06-10 15:33:05 +0200
commit24920bc52a5658dd1d16d38ba3dc46f92dfe7675 (patch)
treee1bb3003b7df81dec147e831334011e5f41cd1c2
parentcea3cf177c68bb1fa9326d4e88631b7737ae8a98 (diff)
downloadgitlab-ce-finding-multiple-projects-by-paths.tar.gz
Add Project.where_paths_infinding-multiple-projects-by-paths
This method can be used to find multiple projects for multiple paths. For example, take this snippet: Project.where_paths_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee}) This will return an ActiveRecord::Relation containing the GitLab CE and GitLab EE projects. This method takes care of matching rows both case-sensitively and case-insensitively where needed. Project.find_with_namespace in turn has been modified to use Project.where_paths_in without nuking any scoping (instead it uses reorder(nil)). This means that any default scopes (e.g. those used for "pending_delete" stay intact). The method Project.where_paths_in was added so the various Markdown filters can use a single query to grab all the projects referenced in a set of documents, something Project.find_with_namespace didn't allow.
-rw-r--r--app/models/project.rb71
-rw-r--r--spec/models/project_spec.rb33
2 files changed, 93 insertions, 11 deletions
diff --git a/app/models/project.rb b/app/models/project.rb
index f47ef8a81de..7bee7550d6c 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -253,20 +253,69 @@ class Project < ActiveRecord::Base
non_archived.where(table[:name].matches(pattern))
end
- def find_with_namespace(id)
- namespace_path, project_path = id.split('/', 2)
+ # Finds a single project for the given path.
+ #
+ # path - The full project path (including namespace path).
+ #
+ # Returns a Project, or nil if no project could be found.
+ def find_with_namespace(path)
+ where_paths_in([path]).reorder(nil).take
+ end
- return nil if !namespace_path || !project_path
+ # Builds a relation to find multiple projects by their full paths.
+ #
+ # Each path must be in the following format:
+ #
+ # namespace_path/project_path
+ #
+ # For example:
+ #
+ # gitlab-org/gitlab-ce
+ #
+ # Usage:
+ #
+ # Project.where_paths_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee})
+ #
+ # This would return the projects with the full paths matching the values
+ # given.
+ #
+ # paths - An Array of full paths (namespace path + project path) for which
+ # to find the projects.
+ #
+ # Returns an ActiveRecord::Relation.
+ def where_paths_in(paths)
+ wheres = []
+ cast_lower = Gitlab::Database.postgresql?
+
+ paths.each do |path|
+ namespace_path, project_path = path.split('/', 2)
+
+ next unless namespace_path && project_path
+
+ namespace_path = connection.quote(namespace_path)
+ project_path = connection.quote(project_path)
+
+ where = "(namespaces.path = #{namespace_path}
+ AND projects.path = #{project_path})"
+
+ if cast_lower
+ where = "(
+ #{where}
+ OR (
+ LOWER(namespaces.path) = LOWER(#{namespace_path})
+ AND LOWER(projects.path) = LOWER(#{project_path})
+ )
+ )"
+ end
- # Use of unscoped ensures we're not secretly adding any ORDER BYs, which
- # have a negative impact on performance (and aren't needed for this
- # query).
- projects = unscoped.
- joins(:namespace).
- iwhere('namespaces.path' => namespace_path)
+ wheres << where
+ end
- projects.find_by('projects.path' => project_path) ||
- projects.iwhere('projects.path' => project_path).take
+ if wheres.empty?
+ none
+ else
+ joins(:namespace).where(wheres.join(' OR '))
+ end
end
def visibility_levels
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 553556ed326..f687e3171a7 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -859,4 +859,37 @@ describe Project, models: true do
it { is_expected.to be_falsey }
end
end
+
+ describe '.where_paths_in' do
+ context 'without any paths' do
+ it 'returns an empty relation' do
+ expect(Project.where_paths_in([])).to eq([])
+ end
+ end
+
+ context 'without any valid paths' do
+ it 'returns an empty relation' do
+ expect(Project.where_paths_in(%w[foo])).to eq([])
+ end
+ end
+
+ context 'with valid paths' do
+ let!(:project1) { create(:project) }
+ let!(:project2) { create(:project) }
+
+ it 'returns the projects matching the paths' do
+ projects = Project.where_paths_in([project1.path_with_namespace,
+ project2.path_with_namespace])
+
+ expect(projects).to contain_exactly(project1, project2)
+ end
+
+ it 'returns projects regardless of the casing of paths' do
+ projects = Project.where_paths_in([project1.path_with_namespace.upcase,
+ project2.path_with_namespace.upcase])
+
+ expect(projects).to contain_exactly(project1, project2)
+ end
+ end
+ end
end