Skip to content
Snippets Groups Projects
Unverified Commit 73bf9413 authored by Yorick Peterse's avatar Yorick Peterse
Browse files

Refactor Project.with_feature_available_for_user

This method used to use a UNION, which would lead to it performing the
same query twice; producing less than ideal performance. Further, in
certain cases ActiveRecord could get confused and mess up the variable
bindings, though it's not clear how/why exactly this happens.

Fortunately we can work around all of this by building some of the WHERE
conditions manually, allowing us to use a simple OR statement to get all
the data we want without any of the above problems.
parent d2934722
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -269,17 +269,29 @@ class Project < ActiveRecord::Base
# project features may be "disabled", "internal" or "enabled". If "internal",
# they are only available to team members. This scope returns projects where
# the feature is either enabled, or internal with permission for the user.
#
# This method uses an optimised version of `with_feature_access_level` for
# logged in users to more efficiently get private projects with the given
# feature.
def self.with_feature_available_for_user(feature, user)
return with_feature_enabled(feature) if user.try(:admin?)
visible = [nil, ProjectFeature::ENABLED]
 
unconditional = with_feature_access_level(feature, [nil, ProjectFeature::ENABLED])
return unconditional if user.nil?
if user&.admin?
with_feature_enabled(feature)
elsif user
column = ProjectFeature.quoted_access_level_column(feature)
 
conditional = with_feature_access_level(feature, ProjectFeature::PRIVATE)
authorized = user.authorized_projects.merge(conditional.reorder(nil))
authorized = user.project_authorizations.select(1).
where('project_authorizations.project_id = projects.id')
 
union = Gitlab::SQL::Union.new([unconditional.select(:id), authorized.select(:id)])
where(arel_table[:id].in(Arel::Nodes::SqlLiteral.new(union.to_sql)))
with_project_feature.
where("#{column} IN (?) OR (#{column} = ? AND EXISTS (?))",
visible,
ProjectFeature::PRIVATE,
authorized)
else
with_feature_access_level(feature, visible)
end
end
 
scope :active, -> { joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') }
Loading
Loading
Loading
Loading
@@ -27,6 +27,13 @@ class ProjectFeature < ActiveRecord::Base
 
"#{feature}_access_level".to_sym
end
def quoted_access_level_column(feature)
attribute = connection.quote_column_name(access_level_attribute(feature))
table = connection.quote_table_name(table_name)
"#{table}.#{attribute}"
end
end
 
# Default scopes force us to unscope here since a service may need to check
Loading
Loading
Loading
Loading
@@ -4,6 +4,18 @@ describe ProjectFeature do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
 
describe '.quoted_access_level_column' do
it 'returns the table name and quoted column name for a feature' do
expected = if Gitlab::Database.postgresql?
'"project_features"."issues_access_level"'
else
'`project_features`.`issues_access_level`'
end
expect(described_class.quoted_access_level_column(:issues)).to eq(expected)
end
end
describe '#feature_available?' do
let(:features) { %w(issues wiki builds merge_requests snippets repository) }
 
Loading
Loading
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