I'm using Rails 2.3.14 here, but it's possible problem I'm having is not confined to this particular version. This is all association and eager loading functionality that is still around in Rails 3, and has been around well before 2.3.14. I'm in the process of upgrading from rails 2.3.8, where I was not having the problem described below.
The code below is a mock-up based on a much more complex production system. The class/module scheme I'm outlining is set up like this for a reason. I'm actually including a few more details in this mock-up than necessary to demonstrate the problem, hopefully make the overall structure of the system more clear.
Suppose I have several domain objects that can be "driven", including vehicles (cars/trucks) and golf balls. For each of these things, I have an ActiveRecord class:
class Vehicle < ActiveRecord::Base
end
class Car < Vehicle
include Driveable
end
class Truck < Vehicle
include Driveable
end
class GolfBall < ActiveRecord::Base
include Driveable
end
First note that GolfBall
is a top-level model class and the corresponding database table is golf_balls
. On the other hand, Car
and Truck
are sub-classes of Vehicle
. The database for vehicles is set up with STI, so Cars
and Trucks
both correspond to the vehicles
table (with a type
column differentiator).
Secondly, note that I'm including a Drivable
module on all the bottom-level domain objects (Car
, Truck
, GolfBall
), and it looks like this (in the actual system this module does a lot more too, including settings things up based on the specific including domain object):
module Driven
def self.included(base)
base.class_eval do
has_one :driver, :as => :driveable, :class_name => "#{self.name}Driver", :dependent => :destroy
end
end
end
So each of these things can have a Driver
, and it's using a :class_name
based on the including class name (e.g. including class Car
results in a has_one
with :class_name => "CarDriver"
), because each of these referenced classes (CarDriver
, etc...) contains specific business logic that is necessary for the association's use.
There is a top-level Driver
class which sets up the polymorphic association, and then a similar subclass hierarchy as above for domain object drivers:
class Driver < ActiveRecord::Base
belongs_to :driveable, :polymorphic => true
end
class VehicleDriver < Driver
end
class CarDriver < VehicleDriver
end
class TruckDriver < VehicleDriver
end
class GolfBallDriver < Driver
end
This is based on a single database table drivers
, using STI for all subclasses.
With this system in place, I create a new Car
(stored in @car
below) and associate it with a newly-created CarDriver
like this (it's split up into these particular sequential steps in this mock-up to mirror the way the actual system works):
@car = Car.create
CarDriver.create(:driveable => @car)
This created database row in the vehicles
table like this:
id type ...
-----------------
1 Car ...
And a row in the drivers
table like this:
id driveable_id driveable_type type ...
--------------------------------------------------------
1 1 Vehicle CarDriver ...
Vehicle
is the driveable_type
as opposed to Car
because vehicles are STI. So far so good. Now I open up a rails console and execute a simple command to get a Car
instance:
>> @car = Car.find(:last)
=> #<Car id: 1, type: "Car", ...>
According to the log, here is the query that was executed:
Car Load (1.0ms)
SELECT * FROM `vehicles`
WHERE ( `vehicles`.`type` = 'Car' )
ORDER BY vehicles.id DESC
LIMIT 1
Then I get the CarDriver
:
>> @car.driver
=> #<CarDriver id: 1, driveable_id: 1, driveable_type: "Vehicle", type: "CarDriver", ...>
This caused this query to be execute.
CarDriver Load (0.7ms)
SELECT * FROM `drivers`
WHERE (`drivers`.driveable_id = 1 AND `drivers`.driveable_type = 'Vehicle') AND (`drivers`.`type` = 'CarDriver' )
LIMIT 1
If I try to use eager loading, however, I get different results. From a fresh console session, I run:
>> @car = Car.find(:last, :include => :driveable)
=> #<Car id: 1, type: "Car", ...>
>> @car.driver
=> nil
This results in nil
for the driver. Checking the logs, the first statement execute the following queries (regular query and eager loading query):
Car Load (1.0ms)
SELECT * FROM `vehicles`
WHERE ( `vehicles`.`type` = 'Car' )
ORDER BY vehicles.id DESC
LIMIT 1
CarDriver Load (0.8ms)
SELECT * FROM `drivers`
WHERE (`drivers`.driveable_id = 1 AND `drivers`.driveable_type = 'Car') AND (`drivers`.`type` = 'CarDriver' )
LIMIT 1
As you can see, in the eager loading case, the Car
query is identical to the above, but the CarDriver
query is different. It's mostly the same, except that for drivers.driveable
type it is looking for Car
where it shouldbe looking for the STI base class name, Vehicle
, as it does in the non-eager loading case.
Any idea how to fix this?