squeel icon indicating copy to clipboard operation
squeel copied to clipboard

Polymorphic join query returns wrong result

Open dariusf opened this issue 10 years ago • 0 comments

A bit of context:

Our system tracks users' achievements. These may have other achievements as conditions (to track prerequisites). A resource which has_many conditions is called a conditional.

There are several different types of condition and conditional, so the associations are polymorphic in both directions (using active_record-acts_as). This is managed via the conditions table.

# Schema
create_table "achievements", force: :cascade do |t|
  t.string "title"
end

create_table "conditions", force: :cascade do |t|
  t.integer "actable_id"
  t.string  "actable_type"
  t.integer "conditional_id"
  t.string  "conditional_type"
end

create_table "achievement_conditions", force: :cascade do |t|
  t.integer "achievement_id"
end
# Models
class Achievement < ActiveRecord::Base
  has_many :conditions, -> { includes :actable },
           class_name: Condition.name, as: :conditional, dependent: :destroy
end

class Condition < ActiveRecord::Base
  actable
  belongs_to :conditional, polymorphic: true
end

class AchievementCondition < ActiveRecord::Base
  acts_as :condition, class_name: Condition.name
  belongs_to :achievement, class_name: Achievement.name

  default_scope { includes(:achievement) }
end

With this setup, if achievement 2 has achievement 1 as a condition, Achievement.find(2).conditions.first.actable.achievement would return achievement 1.

The bug happens with the following query, which we use to find all achievemnt conditions of a given achievement (2 in this case):

AchievementCondition.joins { condition.conditional(Achievement) }.where { condition.conditional.id == 2 }.map(&:achievement).first

It should return achievement 1, but returns achievement 2. The following fixes the problem, however reloading every object is prohibitively expensive:

a = AchievementCondition.joins { condition.conditional(Achievement) }.where { condition.conditional.id == 2 }.first
a.reload.achievement # correct result

Would anyone have insight into why this happens and if it can be fixed?

There's a minimal Rails project here with the models and schema set up. The problem is also described in Coursemology/coursemology2#443.

Apologies for the exposition. This is an obscure case and I wasn't sure how to frame it more briefly, or come up with a smaller example.

dariusf avatar Jul 26 '15 10:07 dariusf