Как я должен написать ActiveRecord при наличии нескольких ассоциаций?

#ruby-on-rails-3 #activerecord #associations

#ruby-on-rails-3 #activerecord #ассоциации

Вопрос:

Модели такие:

 class Contract < ActiveRecord::Base  
  belongs_to :buyer, :class_name => 'Customer', :foreign_key => 'buyer_customer_id' 
  belongs_to :user, :class_name => 'Customer', :foreign_key => 'user_customer_id'
  belongs_to :currency
end  

class Customer < ActiveRecord::Base  
  has_many :as_buyer_in_contracts, :class_name => 'Contract', :foreign_key => 'buyer_customer_id'  
  has_many :as_user_in_contracts, :class_name => 'Contract',:foreign_key => 'user_customer_id'  
end

class Currency < ActiveRecord::Base
  has_many :contracts
end
  

И ниже приведены данные:

 Contract
 ---- ------------------- ------------------ ------------- 
| id | buyer_customer_id | user_customer_id | currency_id |
 ---- ------------------- ------------------ ------------- 
|  1 |         1         |        3         |      3      |
|  2 |         2         |        2         |      2      |
|  3 |         2         |        1         |      2      |
|    |                   |                  |             |


Customer
 ---- ------------------- 
| id |       name        |
 ---- ------------------- 
|  1 |    Terry Brown    |
|  2 |    Tom Green      |
|  3 |    Kate White     |
|    |                   |

Currency
 ---- ------------------- 
| id |       name        |
 ---- ------------------- 
|  1 |        EUR        |
|  2 |        USD        |
|  3 |        JPY        |
|    |                   |
  

И теперь я хочу найти все контракты, которые были подписаны с клиентом по имени «Терри», например:

 Contract.where("customers.name like '%Terry%'").includes(:buyer,:user)
#I want 1 and 3, but it can only get 1
Contract.where("customers.name like '%Terry%'").includes(:user, :buyer)
#If I write "user" before "buyer", then I can only get 3
  

Кто-то сказал мне, что это может работать так:

 Contract.join(:customer).where("customers.name like '%terry%'").includes(:user,:buyer)
#It works fine.
  

Я попробовал, и это работает. Но далее, когда модель контракта принадлежит другой модели, такой как currency_id, описанный выше метод больше не может работать.

 Contract.join(:customer).where("customers.name like '%terry%'").includes(:currency, :user, :buyer)
#>>Mysql2::Error: Unknown column 'customers_contracts.id' in 'field list': ...
  

Ответ №1:

Это потому, что вы не должны использовать объединения в сочетании с включениями. Это недостаточно подчеркнуто (и в rails нет предупреждения), но

выбрать, объединить, сгруппировать, иметь и т. Д. НЕ РАБОТАЙТЕ С включениями!

Вы можете получить результаты, но только случайно. И скорее всего, он сломается раньше, чем позже.

Кажется, есть также некоторая несогласованность с включениями…

Если вам нужно использовать обычное внешнее соединение с activerecord> = 3.0 (как в данном случае), используйте отличный камень squeel.Это действительно дает силу Arel.

Попробуйте (с установленным squeel) :

 Contract.joins{buyer.outer}.joins{user.outer}.where("name like '%terry%'")
  

Готовое соединение выполняет только внутреннее соединение, что исключает непересекающуюся таблицу, что делает вашу цель здесь невозможной: покупатель и пользователь могут быть взаимоисключающими…

Комментарии:

1. НЕ РАБОТАЕТ? Я использовал их вместе в других случаях, вы имеете в виду, что он может сломаться в любой момент?

2. Это хороший кандидат на корень проблемы, но ваш ответ вначале немного расплывчатый (select и т. Д. Не Работают с включениями, несоответствие). Не могли бы вы добавить более четкое объяснение этих моментов и / или указать на авторитетное объяснение?

3. все еще в отпуске… попытаюсь немного подкрепить свой ответ, когда вернусь.

Ответ №2:

вы пробовали использовать joins :buyer или :user вместо :customer ?

в вашей контрактной модели нет атрибута / отношения:customer

 Contract.join(:buyer).where("customers.name like '%terry%'").includes(:currency, :user, :buyer)
  

я предполагаю, что это будет эквивалентно чему-то вроде

 Contract.join("INNER JOIN customers ON customers.id = contracts.buyer_customer_id").where("customers.name like '%terry%'").includes(:currency, :user, :buyer)
  

проверьте свой файл журнала, чтобы точно узнать, какой sql генерируется в каждом случае — log / development.log

Комментарии:

1. извините, я забыл добавить отношение модели… И файл журнала составляет около 40 или 50 строк, потому что мое фактическое состояние очень сложное. Я был полностью смущен ими.