Skip to content

Commit

Permalink
Added the new strategy: double_exist_subquery
Browse files Browse the repository at this point in the history
  • Loading branch information
kaspernj committed May 13, 2021
1 parent 98205cb commit 58c7204
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 6 deletions.
2 changes: 1 addition & 1 deletion lib/cancan/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module CanCan
def self.valid_accessible_by_strategies
strategies = [:left_join]
strategies << :subquery unless does_not_support_subquery_strategy?
strategies.push(:double_exist_subquery, :subquery) unless does_not_support_subquery_strategy?
strategies
end

Expand Down
50 changes: 45 additions & 5 deletions lib/cancan/model_adapters/active_record_5_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,57 @@ def self.matches_condition?(subject, name, value)

private

delegate :connection, :quoted_primary_key, to: :@model_class
delegate :quote_table_name, to: :connection

def build_joins_relation(relation, *where_conditions)
case CanCan.accessible_by_strategy
when :subquery
inner = @model_class.unscoped do
@model_class.left_joins(joins).where(*where_conditions)
end
@model_class.where(@model_class.primary_key => inner)

build_joins_relation_subquery(where_conditions)
when :left_join
relation.left_joins(joins).distinct
when :double_exist_subquery
build_joins_relation_double_exist_subquery(where_conditions)
end
end

def build_joins_relation_subquery(where_conditions)
inner = @model_class.unscoped do
@model_class.left_joins(joins).where(*where_conditions)
end
@model_class.where(@model_class.primary_key => inner)
end

def build_joins_relation_double_exist_subquery(where_conditions)
@model_class.where("EXISTS (#{double_exists_query_sql(where_conditions)})")
end

def double_exists_inner_query(where_conditions)
@model_class
.unscoped
.select('1')
.left_joins(joins)
.where(*where_conditions)
.where(
"#{quoted_table_name}.#{quoted_primary_key} = " \
"#{quoted_aliased_table_name}.#{quoted_primary_key}"
)
end

def double_exists_query_sql(where_conditions)
'SELECT 1 ' \
"FROM #{quoted_table_name} AS #{quoted_aliased_table_name} " \
'WHERE ' \
"#{quoted_aliased_table_name}.#{quoted_primary_key} = #{quoted_table_name}.#{quoted_primary_key} AND " \
"EXISTS (#{double_exists_inner_query(where_conditions).to_sql})"
end

def quoted_aliased_table_name
@quoted_aliased_table_name ||= quote_table_name('aliased_table')
end

def quoted_table_name
@quoted_table_name ||= quote_table_name(@model_class.table_name)
end

def sanitize_sql(conditions)
Expand Down
31 changes: 31 additions & 0 deletions spec/cancan/model_adapters/has_and_belongs_to_many_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,37 @@ class House < ActiveRecord::Base
end

unless CanCan::ModelAdapters::ActiveRecordAdapter.version_lower?('5.0.0')
describe 'fetching of records - double_exist_subquery strategy' do
before do
CanCan.accessible_by_strategy = :double_exist_subquery
end

it 'it retreives the records correctly' do
houses = House.accessible_by(ability)
expect(houses).to match_array [@house2, @house1]
end

if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
it 'generates the correct query' do
expect(ability.model_adapter(House, :read))
.to generate_sql("SELECT \"houses\".*
FROM \"houses\"
WHERE (EXISTS (SELECT 1
FROM \"houses\" AS \"aliased_table\"
WHERE
\"aliased_table\".\"id\" = \"houses\".\"id\" AND
EXISTS (SELECT 1
FROM \"houses\"
LEFT OUTER JOIN \"houses_people\" ON \"houses_people\".\"house_id\" = \"houses\".\"id\"
LEFT OUTER JOIN \"people\" ON \"people\".\"id\" = \"houses_people\".\"person_id\"
WHERE
\"people\".\"id\" = #{@person1.id} AND
(\"houses\".\"id\" = \"aliased_table\".\"id\"))))
")
end
end
end

describe 'fetching of records - subquery strategy' do
before do
CanCan.accessible_by_strategy = :subquery
Expand Down

0 comments on commit 58c7204

Please sign in to comment.