Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CanCan and polymorphic associations (Eager Loading error)

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)

like image 449
mattangriffel Avatar asked Sep 19 '13 14:09

mattangriffel


People also ask

Why am I getting eagerloadpolymorphicerror in ActiveRecord?

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.

Should I use preload for polymorphic associations?

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/…

What is the difference between eager loading and lazy loading?

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).


1 Answers

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.

like image 129
Josh Kovach Avatar answered Sep 18 '22 01:09

Josh Kovach