在ActiveRecord / arel中使用join()时,是否可以指定备用关联名称?

时间:2014-12-29 03:23:17

标签: ruby-on-rails ruby activerecord arel

我正处于ActiveRecord似乎生成无效查询的问题上。

以下是模型的本质以及控制器如何使用它:

class Licence < ActiveRecord::Base
  belongs_to :organisation
  belongs_to :owner_organisation, :class_name => 'Organisation'
  # omitting other relationships

  def self.search(params = {})
    collection = self
    table = self.arel_table

    # omitting filtering of the collection

    case params[:order]

      # these two work:
      when 'generated_by'
        collection = collection.joins(:organisation).
                                order('organisations.name ASC')
      when 'generated_by_reverse'
        collection = collection.joins(:organisation).
                                order('organisations.name DESC')

      # these two cause the problem:
      when 'owner'
        collection = collection.joins(:owner_organisation).
                                order('owner_organisations.name ASC')
      when 'owner_reverse'
        collection = collection.joins(:owner_organisation).
                                order('owner_organisations.name DESC')
                              # ^- this order() is almost certainly wrong too, but I
                              #    will fix that once I get a table name I can predict...

      # omitting other orderings
    end

    collection
  end

end

class LicencesController < ApplicationController

  def index
    @licences = Licence.search(params).
      includes(:organisation, :user, :owner_organisation, :profile)
  end

end

您会注意到模型加入 owner_organisation,而控制器包含它。这似乎是问题的一部分。

这会导致创建一个奇怪的查询,但我收到错误。

ActionView::Template::Error: SQLite3::SQLException: no such column: owner_organisations_licences.id:

实际上查询并没有包含具有此名称的表:(我删除了查询,因为选择了 lot 列。)

SELECT
  "licences"."id" AS t0_r0,           # omitting rest of the columns for all these
  "organisations"."id" AS t1_r0,
  "users"."id" AS t2_r0,
  "dongles"."id" AS t3_r0,
  "owner_organisations_licences"."id" AS t4_r0,      # this is the problem
  "profiles"."id" AS t5_r0
FROM "licences"
  INNER JOIN "organisations"
    ON "organisations"."id" = "licences"."owner_organisation_id"
  LEFT OUTER JOIN "organisations" "organisations_licences"
    ON "organisations_licences"."id" = "licences"."organisation_id"
  LEFT OUTER JOIN "users"
    ON "users"."id" = "licences"."user_id"
  LEFT OUTER JOIN "dongles"
    ON "dongles"."id" = "licences"."dongle_id"
  LEFT OUTER JOIN "profiles"
    ON "profiles"."id" = "licences"."profile_id"
WHERE "licences"."parent_licence_id" IS NULL
ORDER BY owner_organisations.name ASC
LIMIT 50 OFFSET 0

我可以看到这里有一个命名方案,我的includes(:organisations)在查询中变为organisations_licences。但owner_organisation已与joins(:owner_organisation)一起使用,因此它已作为INNER JOIN使用 - 无论出于何种原因,#joins都没有与#includes相同的惯例owner_organisations_licences,所以它有&#34;错误&#34;名称。但是,无论出于何种原因,当它指定要选择的内容时,它会使用{{1}},因此查询无效。

我想我可以解决这个问题,如果有办法告诉arel什么名字给连接表,但我无法弄清楚如何做到这一点。

有办法吗?

1 个答案:

答案 0 :(得分:1)

您不能指望includes始终在数据库级别执行JOIN。实际上include有两种不同的策略,它可能会使用JOIN,或者它可能会触发第二个查询(仍然比常见的N + 1问题includes尝试解决的速度更快)。您可能对此article感兴趣includes works

问题的答案:不要在同一张桌子上同时使用joinsincludes。在Rails 4中,有一种新方法references可以解决这个问题:

case params[:order]
when 'generated_by'
  collection = collection.includes(:organisation).
                          order('organisations.name ASC').
                          references(:organisations)
when 'generated_by_reverse'
  collection = collection.includes(:organisation).
                          order('organisations.name DESC').
                          references(:organisations)
when 'owner'
  collection = collection.includes(:owner_organisation).
                          order('organisations.name ASC').
                          references(:organisations)
when 'owner_reverse'
  collection = collection.includes(:owner_organisation).
                          order('organisations.name DESC').
                          references(:organisations)
end