Polymorphic association _sometimes_ not working #3882

Closed
stayhero opened this Issue Dec 7, 2011 · 15 comments

Comments

Projects
None yet
9 participants

stayhero commented Dec 7, 2011

Today I faced a very weird error using polymorphic associations. In the rails console everything does almost often work, but the unit tests are failing all the time.

I setup a test Rails 3.1.3. app here (using SQLlite, but same error happens with mysql)
https://github.com/stayhero/Polymorphic_Test_Error

To see the problem simply

cd polymorphic_test_error
bundle install
bundle exec rake db:setup
bundle exec rake db:fixtures:load

(but I guess you already know that ;-))

Basically, using the polymorphic association in the console like this works:

Company.find(1).users.size == 2  # returns true

But the test case which does the same fails. I've found out that it fails in the console as well if you use these two calls (basically this is what the test does, in the same order, thus it fails):

Company.find(1).designers.size == 1 # returns true
Company.find(1).users.size == 2  # now returns false, but should be true!

Same result if you run

bundle exec rake test:units

I've tried to simplify my existing database model as much as possible, so I can showcase the error with 5 models. Basically we have a Company, which has one or more departments. Each department has one or more users.

And now via polymorphic association each user can either have a Designer or Developer entity.

schema.rb:

ActiveRecord::Schema.define(:version => 20111207002230) do

  create_table "companies", :force => true do |t|
    t.string   "name"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "departments", :force => true do |t|
    t.string   "title"
    t.integer  "company_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "designers", :force => true do |t|
    t.string   "favorite_color"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "developers", :force => true do |t|
    t.string   "favorite_software"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "users", :force => true do |t|
    t.string   "name"
    t.integer  "typeable_id"
    t.string   "typeable_type"
    t.integer  "department_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end
end

company.rb (model)

class Company < ActiveRecord::Base
  has_many :departments
  has_many :users, through: :departments
  has_many :developers, source_type: 'Developer', source: :typeable  , through: :users
  has_many :designers, source_type: 'Designer', source: :typeable, through: :users
end

department.rb (model)

class Department < ActiveRecord::Base
  has_many :users
  belongs_to :company
end

user.rb (model)

class User < ActiveRecord::Base
  belongs_to :typeable, :polymorphic => true
end

designer.rb (model)

class Designer < ActiveRecord::Base
  has_one :user, :as => :typeable
end

developer.rb (model)

class Developer < ActiveRecord::Base
  has_one :user, :as => :typeable
end

BTW: I wanted to try with latest master, but after changing Gemfile to use rails edge, bundle install told me this:
Could not find gem 'arel (~> 3.0.0.pre) ruby', which is required by gem 'rails (>= 0) ruby', in any of the sources.

stayhero commented Dec 7, 2011

BTW: In the console you can see the difference between the working and not-working call

Company.find(1).users.size

The working call queries the database using this sql

SELECT COUNT(*) FROM "users" 
INNER JOIN "departments" ON "users"."department_id" = "departments"."id" 
WHERE "departments"."company_id" = 1

whereas the not-working query looks like this:

SELECT COUNT(*) FROM "users" 
INNER JOIN "departments" ON "users"."department_id" = "departments"."id" 
WHERE "departments"."company_id" = 1 AND ("users"."typeable_type" = 'Designer')

Obviously it tries to filter only "Designers", but it should get all users...;-O

I have the same issue here. It seems to be an sql caching problem. The caching system does not seem to consider the source_type condition.

The problem looks similar to this old issue : #3271

EDIT : The problem only appears when there are 2 throughs associations (developers through users through departements)

Ugly workaround :

You can duplicate the primary association :

class Company < ActiveRecord::Base
  has_many :departments
  has_many :users, through: :departments
  has_many :_users_developers, through: :departments, :source => :users # workaround (do not use directly)
  has_many :_users_designers, through: :departments, :source => :users # workaround (do not use directly)
  has_many :developers, source_type: 'Developer', source: :typeable  , through: :_users_developers
  has_many :designers, source_type: 'Designer', source: :typeable, through: :_users_designers
end
Contributor

isaacsanders commented May 5, 2012

Is this still an issue?

Contributor

isaacsanders commented May 5, 2012

Is this still an issue?

stayhero commented May 5, 2012

Yes. Upgraded the failing test app at https://github.com/easychris/Polymorphic_Test_Error to use Rails 3.2.3. Still shows the same (erroneous) behavior.

stayhero commented May 5, 2012

FYI Error also happens on Rails master branch:

https://github.com/easychris/Polymorphic_Test_Error/tree/rails-head

gerwitz commented Jun 3, 2012

Thank you @alarribeau for that workaround!

gerwitz commented Jun 3, 2012

I should add a description of the behavior I've seen on 3.2.3. My models look very similar to @EasyChris's test app but I have many more of these relationships.

Queries generated by the "deep" polymorphic relationship (Company.developers and Company.designers) "stack" their type conditionals over time. So if you start console fresh and call Company.developers the generated query will end with

   AND ("users"."typeable_type" = 'Developer')

but a following call to Company.designers will incorrectly build on that last query

   AND ("users"."typeable_type" = 'Developer' AND "users"."typeable_type" = 'Designer')

…et cetera. It's type conditionals all the way down, if we had a Company.diplomats:

   AND ("users"."typeable_type" = 'Developer' AND "users"."typeable_type" = 'Designer' AND "users"."typeable_type" = 'Diplomat')

It doesn't matter, of course, what order the polymorphic types are used in.

gerwitz commented Jun 3, 2012

This seems to be because something is inappropriately reusing ThroughReflection instances because :source_type is not included in a cache key. (:through must be, hence the workaround above.) So ThroughReflection.conditions just keeps accumulating in its through_conditions.

I wish I understood AR well enough to know where ThroughReflection is being re-used, but I have a lot to learn before that's clear.

Im using rails 3.2.8 and i those problems. You can check it there: http://stackoverflow.com/questions/15747247/rails-polymorphic-associations-has-many-condition-error
I will try the workaround (i tried the workaround and it works...good job alarribeau ^^)

Thanks in advance,

tiff commented Apr 16, 2013

jfyi: this problem still exists with Rails 4 (current master)

jfoley commented May 4, 2013

Here is a smaller test case that demonstrates the error in the latest version of ActiveRecord (4.1.0.beta): https://gist.github.com/jfoley/5518882

Contributor

Dagnan commented Jul 31, 2013

Does the last modification fix the problem?

I'm running Rails 3.2.13 and did a lot of hard work to just find where weird behaviours in tests happened. Happy to find this issue :)

is this broken again in rails 4.1.5?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment