ryanb/cancan — pull request #1002 by jackryon (2014-04-11 17:33:05+00:00)
Title: Issue with accessible_by and ActiveRecord SQL JOIN behavior

I have a bit of roundabout logic for permitting a user to access a given image tagged "private" in my application.

The rules are:
- any user can access any image where { is_private: false }
- images where { is_private: true } are accessible to a user if she has been invited (via an invitation model) to any private gallery that contains the image. A Gallery has_many Invitations, and image has_and_belongs_to_many Galleries.

The 2 "can" statements from ability.rb that are relevant here are:
```
can :read, Image, is_private: false
can :read, Image, { is_private: true, galleries: { gallery_invitations: { invitee_id: user.id } } }
```

When calling accessible_by on a @user.images.accessible_by(current_ability), the call to @model_class.where(conditions).joins(joins) from cancan/lib/cancan/model_adapters/active_record_adapter.rb line 105 created SQL like this (note that there are some "type in" calls to accommodate polymorphic relationships, as well as other scopes being applies [acts_as_paranoid for one]):

```
"SELECT \"images\".* FROM \"images\" INNER JOIN \"image_galleries\" ON \"image_galleries\".\"image_id\" = \"images\".\"id\" INNER JOIN \"galleries\" ON \"galleries\".\"id\" = \"image_galleries\".\"gallery_id\" INNER JOIN \"invitations\" ON \"invitations\".\"invitable_id\" = \"galleries\".\"id\" AND \"invitations\".\"type\" IN ('Invitation') AND \"invitations\".\"invitable_type\" = 'Gallery' WHERE \"images\".\"user_id\" = 2 AND (upload_completed = 't') AND (\"images\".\"deleted_at\" IS NULL) AND ((\"images\".\"is_private\" = 't' AND \"invitations\".\"invitee_id\" = 3) OR (\"images\".\"is_private\" = 'f'))"
```

This query doesn't work, however. We need to use an outer left join to return the images for which there are no "join" models, or we only get the models from the second can rule, and not the first. I'm 99% sure that this works in its place:

```
@model_class.where(conditions).includes(joins)
```

Because this only gets called in the context of accessible_by, I don't think there would be any other unintended side effects.