I'm trying to define a user's ability to access something based on a column on an associated model (so something like can :read, Step, 'steppable' => {published: true}
), the problem is that it's a polymorphic association so it can't find the steppable table because it doesn't exist.
I've got steps, and each step has a steppable (either a lecture, a quiz, or some other action). I need an activerecord query that will work. I've tried:
Step.includes(:steppable).where('steppable' => {published: true})
and
Step.joins(:steppable).where('steppable' => {published: true})
But both result in ActiveRecord::EagerLoadPolymorphicError: Can not eagerly load the polymorphic association :steppable
Models look like this:
class Step < ActiveRecord::Base
...
belongs_to :steppable, polymorphic: true, dependent: :destroy
...
end
and
class Lecture
...
has_one :step, as: :steppable, dependent: :destroy
...
end
Note: I'd like to be agnostic regarding the associated model, and in order for it to work for fetching records with CanCan, it has to be done using database columns (see github.com/ryanb/cancan/wiki/defining-abilities)
In some circumstances this can be a very serious data leak because a user may have permission to access review but not the Shop returned by review.shop. If you get an ActiveRecord::EagerLoadPolymorphicError, it's because includes decided to call eager_load when polymorphic associations are only supported by preload.
So always use preload for polymorphic associations. There is one caveat for this: you cannot query the polymorphic assocition in where clauses (which makes sense, since the polymorphic association represents multiple tables.) I see that's the one method that's not documented in the guides: guides.rubyonrails.org/…
In case you don’t know, Eager Loading means the data is read from the database when the model is instantiated. On the other hand, Lazy Loading means the data is read from the database when required (or the model is accessed). The concept of eager/lazy loading is not exclusive to databases (or ORMs).
You should be able to do this:
can :read, Step, steppable_type: 'Lecture', steppable_id: Lecture.published.pluck(:id)
can :read, Step, steppable_type: 'OtherThing', steppable_id: OtherThing.published.pluck(:id)
You have to do it for each Steppable
class, but it gets around the eager loading polymorphic associations problem. To dry this up a bit:
[Lecture, OtherThing].each do |klass|
can :read, Step, steppable_type: klass.to_s, steppable_id: klass.published.pluck(:id)
end
In this case, as long as each steppable
class has a scope published
, you just add any steppable
class into that array, even if published
is defined differently in each class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With