2

여기에서 레일즈 2.3.14를 사용하고 있습니다 만,이 특정 버전에만 국한되지는 않습니다. 이것은 레일즈 3에서 여전히 사용되고있는 2.3.14 이전의 모든 연관성과 열망하는 로딩 기능입니다. 레일즈 2.3.8에서 업그레이드하는 중입니다. 여기서 아래에 설명 된 문제가 없었습니다.Rails STI 모델 (다형성 배열 포함), 열거 형로드 쿼리가 잘못된 클래스 유형을 참조합니다.

아래의 코드는 훨씬 복잡한 생산 시스템을 기반으로 한 모형입니다. 내가 요약하고있는 클래스/모듈 구성표가 이유 때문에 이렇게 설정되었습니다. 사실상 문제를 설명하는 데 필요한 것보다 좀 더 자세한 내용을 포함하고 있습니다. 시스템의 전반적인 구조를 더 분명하게 만들 수 있습니다.

차량 (자동차/트럭) 및 골프 공을 포함하여 "구동"할 수있는 여러 도메인 개체가 있다고 가정합니다. GolfBall 최상위 모델 클래스와 해당 데이터베이스 테이블이 golf_balls되는 것을

class Vehicle < ActiveRecord::Base 
end 

class Car < Vehicle 
    include Driveable 
end 

class Truck < Vehicle 
    include Driveable 
end 

class GolfBall < ActiveRecord::Base 
    include Driveable 
end 

첫째 주 : 이러한 것들의 각각에 대해, 나는 액티브 클래스가 있습니다. 한편, CarTruck은 서브 클래스가 Vehicle입니다. 차량용 데이터베이스는 STI로 설정되므로 CarsTrucks은 모두 vehicles 테이블 (type 열 차별 자 포함)에 해당합니다.

둘째

, 나는 모든 최하위 레벨 도메인 객체 (Car, Truck, GolfBall)에 Drivable 모듈을 포함하고있어주의, 그리고 실제 시스템에서 (이 같은 않는 많은도 포함이 모듈을 찾습니다 도메인 개체를 포함하여 특정에 따라 설정 일까지) :

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 

그래서 이런 것들의 각각은 Driver을 가질 수 있으며, 예를 들면 has_one와의 Car 결과 클래스를 포함합니다 (포함 클래스 이름을 기준으로 :class_name를 사용하고 :class_name => "CarDriver") 참조 된 클래스 (CarDriver, 등 ...)는 협회의 사용에 필요한 특정 비즈니스 로직을 포함합니다.

다음 최상위 Driver 다형성 연관을 설정 클래스 및 도메인 객체 드라이버 위와 유사한 서브 클래스 계층이있다 : 사용이 단일 데이터베이스 테이블 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 

은 모든 서브 클래스에 대한 STI.대신에이 시스템을

, 내가 만드는 새로운 Car (아래 @car에 저장)과 같은 CarDriver (이 방식을 반영하기 위해이 모형에서 이러한 특정 순차적 단계로 분할 새로 생성과 연결 실제 시스템 작동) :

@car = Car.create 
CarDriver.create(:driveable => @car) 

이 같은 vehicles 테이블이 생성 된 데이터베이스 행 :

id type ... 
----------------- 
1 Car ... 

그리고 drivers 테이블의 행과 같은 이 : 차량 STI 때문에 Car에 반대

id driveable_id driveable_type type   ... 
-------------------------------------------------------- 
1 1    Vehicle   CarDriver ... 

Vehicledriveable_type입니다. 여태까지는 그런대로 잘됐다. 지금은 레일 콘솔을 열고 Car 예를 얻을 수있는 간단한 명령을 실행

Car Load (1.0ms) 
SELECT * FROM `vehicles` 
WHERE (`vehicles`.`type` = 'Car') 
ORDER BY vehicles.id DESC 
LIMIT 1 

그런 다음 나는 CarDriver를 얻을 : 로그에 따르면

>> @car = Car.find(:last) 
=> #<Car id: 1, type: "Car", ...> 

을 여기에 실행 된 쿼리입니다 :

>> @car.driver 
=> #<CarDriver id: 1, driveable_id: 1, driveable_type: "Vehicle", type: "CarDriver", ...> 

이로 인해이 쿼리가 실행되었습니다.

CarDriver Load (0.7ms) 
SELECT * FROM `drivers` 
WHERE (`drivers`.driveable_id = 1 AND `drivers`.driveable_type = 'Vehicle') AND (`drivers`.`type` = 'CarDriver') 
LIMIT 1 

그러나 열망하는로드를 사용하려고하면 다른 결과가 발생합니다. 새로운 콘솔 세션에서, 나는 실행이 드라이버에 대한 nil 결과

>> @car = Car.find(:last, :include => :driveable) 
=> #<Car id: 1, type: "Car", ...> 
>> @car.driver 
=> nil 

. 로그를 확인, 첫 번째 문은 다음과 같은 쿼리 (일반 쿼리와 열망 로딩 쿼리) 실행 : 당신이 볼 수 있듯이

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 

의 열망 로딩 경우, Car 쿼리가 위의 동일을, 그러나 CarDriver 검색어가 다릅니다. drivers.driveable 유형의 경우 Car을 찾고있는 경우를 제외하고는 거의 동일합니다. 비 열정적 인로드 케이스에서와 같이 STI 기본 클래스 이름 인 Vehicle을 찾아야합니다.

이 문제를 해결하는 방법은 무엇입니까?

답변

2

몇 시간 동안 레일스 소스 코드를 읽은 후에, 나는 이것을 알아 냈다고 확신한다. (물론 나는 뭔가를 잘못 읽었을 가능성이있다.) 그것은 Rails 2.3.x에서 소개되었고 Rails 2.3.x의 버그 인 것 같습니다. ,

Fix eager loading of polymorphic has_one associations nested

그러나 : 그것은이 2.3 버그 리포트 레일을 시작 :

Query built wrong for preloading associations nested under polymorphic belongs_to

그리고 실제로이 유효한 버그가 있었다, 그리고 그것은 레일 2.3.9 커밋이 고정 된 이 커밋은 실수로 STI 클래스에서 중첩되지 않은 다형 적 열거 형로드를 파기했으며이 시나리오에 대한 테스트가 없으므로 자동화 된 테스트에서이를 잡아 내지 못했습니다.관련 DIFF는 여기에 있습니다 :이 DIFF에서

360 -   conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} #{in_or_equals_for_ids(ids)} and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.sti_name}'" 

360 +   parent_type = if reflection.active_record.abstract_class? 
361 +   self.base_class.sti_name 
362 +   else 
363 +   reflection.active_record.sti_name 
364 +   end 
365 + 
366 +   conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} #{in_or_equals_for_ids(ids)} and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{parent_type}'" 

당신이 그 전에 2.3.9에 열망로드 다형성 연관들에 대해 항상 (다형성 유형 열 임) parent_typeself.base_class.sti_name을 사용 볼 수 있습니다. 이 질문에 대한 예를 따르면 부모 유형 열이 driveable_type이고 Car.base_classVehicle이므로 정확히 일치해야 driveable_type = 'Vehicle'이됩니다.

그러나 위 커밋부터 시작하여 association-holder가 abstract_class 인 경우에만 올바르게 커밋됩니다. 이것은 곧은 버그 인 것처럼 보입니다. association-holder가 추상 또는 sti 하위 클래스 인 경우 parent_typeself.base_class.sti_name으로 설정해야합니다.

다행히도이 문제를 해결할 수있는 간단한 방법이 있습니다. 어디서나이 문서를 언급하지는 않지만 다형성 belongs_to 어소시에이션을 보유하고있는 sti 서브 클래스에 abstract_class = true을 설정하면이 문제를 해결할 수있는 것 같습니다. 질문의 예에서, 그것은 다음과 같습니다

class Car < Vehicle 
    abstract_class = true 
    include Driveable 
end 

class Truck < Vehicle 
    abstract_class = true 
    include Driveable 
end 

abstract_class의 레일 소스의 전체 텍스트 검색을 수행, 그것은 의도하지 않은/원하지 않는 결과를 초래할 것 아닌 것 같아 설정 및에 진짜 응용 프로그램으로 초기 테스트, 그것은 문제를 해결 한 것 같습니다.

1

죄송합니다.이 질문을 답으로 게시 해 주셔서 감사합니다. 그러나 어떤 이유로 든 평판이 좋으면 의견을 추가 할 수 있으며 다른 방법으로 연락 할 수 없습니다.

제 질문은 버그로 확인되었으며, 수정 된 적이 있습니까? 내가 묻는 이유는 비슷한 문제가있는 것 같습니다.

class Wheel < ActiveRecord::Base 
    belongs_to :vehicle 
end 

# Let's say it has a string attribute called serial_number 
class Vehicle < ActiveRecord::Base 
    has_many :wheels 
end 

class Car < Vehicle 
end 

class Truck < Vehicle 
end 

# etc 

# Without abstract_class = true this query is very slow (up to 10x slower) 
# With abstract_class = true it's suddenly very fast 
Wheel.include(:vehicle).sort_by("vehicles.serial_number ASC") 

이는 위에 설명 된 버그에 관련된 보이는가 : 은 기본적으로 내 모델은 다음과 같이 보입니다? 나에게 그것은 자동차 협회를 예고하는 데 실패 할 것 같은데 솔직히 말해서 Rails/ActiveRecord를 충분히 알지 못한다.

가 지금은 차량에 사실이 문제지만 abstract_class 추가를 = 해결할 수 있습니다 (I이 사용 레일 3.2.8을 테스트 한), 그러나 어떻게 응용 프로그램의 나머지 부분에 영향을 미칠 것인가? abstract_class = true를 둘러 보는 것은 STI를 사용하지 않고 각 서브 클래스가 자신의 테이블을 가져야 함을 나타내지 만, 테스트 할 때 그런 문제는 발생하지 않았다.

+1

예, 이는 동일한 버그로 인한 것 같습니다. 내가 아는 한,이 버그는 결코 고쳐지지 않았고, 심지어는 공식적으로보고되지도 않았으므로, 여전히 3.2.8에있을 수 있습니다. 이 버그로 인해 정확하게 쿼리가 수행하는 sti 데이터가 미리로드되지 않고 속도 차이가 설명됩니다. Rails 2.3.14에서 sti 클래스의 "abstract_class = true"문제를 보지 못했고 문제없이 몇 달 동안 해당 솔루션을 구현 한 프로덕션 앱을 실행했습니다. Rails 3.2.8에서 변경된 사항이 있으면 말할 수 없습니다. –