Skip to content

Commit

Permalink
Allow querying nested unloaded associations using a database query.
Browse files Browse the repository at this point in the history
  • Loading branch information
lowjoel committed Apr 28, 2016
1 parent e2ce4a0 commit 92a4ddd
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 16 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## master

### bug fixes

- Allow querying nested unloaded associations using a database query.
[@lowjoel](https://github.com/lowjoel)

## 0.1.3

### enhancements
Expand Down
44 changes: 28 additions & 16 deletions lib/cancancan/squeel/squeel_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,14 @@ def self.match_relation?(subject, name)
def self.matches_relation?(subject, name, value)
relation = subject.public_send(name)
klass = subject.class.reflect_on_association(name).klass
join_list = nil

relation.where do
expression, = CanCanCan::Squeel::ExpressionBuilder.build(self, klass, :==, value)
scope = relation.where do
expression, join_list = CanCanCan::Squeel::ExpressionBuilder.build(self, klass, :==, value)
expression
end.any?
end

add_joins_to_scope(scope, join_list, :inner).any?
end
private_class_method :matches_relation?

Expand All @@ -65,6 +68,25 @@ def database_records
relation.distinct
end

# Builds a relation, outer joined on the provided associations.
#
# @param [ActiveRecord::Relation] scope The current scope to add the joins to.
# @param [Array<Array<Symbol>>] joins The set of associations to outer join with.
# @param [Symbol] join_type The type of join; defaults to outer joins.
# @return [ActiveRecord::Relation] The built relation.
def self.add_joins_to_scope(scope, joins, join_type = :outer)
joins.reduce(scope) do |result, join|
result.joins do
join.reduce(self) do |relation, association|
relation = relation.__send__(association)
relation = relation.__send__(join_type) unless join_type == :inner
relation
end
end
end
end
private_class_method :add_joins_to_scope

private

# Builds a relation that expresses the set of provided rules.
Expand All @@ -82,19 +104,9 @@ def relation
add_joins_to_scope(scope, join_list)
end

# Builds a relation, outer joined on the provided associations.
#
# @param [ActiveRecord::Relation] scope The current scope to add the joins to.
# @param [Array<Array<Symbol>>] joins The set of associations to outer join with.
# @return [ActiveRecord::Relation] The built relation.
def add_joins_to_scope(scope, joins)
joins.reduce(scope) do |result, join|
result.joins do
join.reduce(self) do |relation, association|
relation.__send__(association).outer
end
end
end
# @see CanCanCan::Squeel::SqueelAdapter.add_joins_to_scope
def add_joins_to_scope(*args)
self.class.send(:add_joins_to_scope, *args)
end

# This builds Squeel expression for each rule, and combines the expression with those to the left
Expand Down
11 changes: 11 additions & 0 deletions spec/cancancan/squeel_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -203,5 +203,16 @@
ability.can?(:read, parent)
expect(parent.children).not_to be_loaded
end

context 'when unloaded associations include joins' do
it 'adds the necessary joins' do
parent = Parent.create!
ability.can(:read, Parent, children: { parent: { id: 2 }})
expect(parent.children).to receive(:where).and_call_original

expect(ability.can?(:read, parent)).to eq(false)
expect(parent.children).not_to be_loaded
end
end
end
end

0 comments on commit 92a4ddd

Please sign in to comment.