传递给 #or 的关联关系必须在结构上兼容。不兼容的值:[:references]。

45

我有两个查询,需要它们之间使用or,也就是我想要返回第一个查询或者第二个查询中的结果。

第一个查询是一个简单的where(),可以获取所有可用的项目。

@items = @items.where(available: true)

第二步包括使用join()方法并获取当前用户的物品。

@items =
  @items
  .joins(:orders)
  .where(orders: { user_id: current_user.id})

我尝试将它们与Rails的or()方法以不同形式组合,包括:

@items =
  @items
  .joins(:orders)
  .where(orders: { user_id: current_user.id})
  .or(
    @items
    .joins(:orders)
    .where(available: true)
  )

但我一直遇到这个错误,不确定如何解决。

Relation passed to #or must be structurally compatible. Incompatible values: [:references]
6个回答

35

在Github上有一个已知的问题,点击此处查看。

根据这条评论,您可能需要覆盖structurally_incompatible_values_for_or以解决该问题:

def structurally_incompatible_values_for_or(other)
  Relation::SINGLE_VALUE_METHODS.reject { |m| send("#{m}_value") == other.send("#{m}_value") } +
    (Relation::MULTI_VALUE_METHODS - [:eager_load, :references, :extending]).reject { |m| send("#{m}_values") == other.send("#{m}_values") } +
    (Relation::CLAUSE_METHODS - [:having, :where]).reject { |m| send("#{m}_clause") == other.send("#{m}_clause") }
end

同时,总是有一个选项可以使用SQL:

@items
  .joins(:orders)
  .where("orders.user_id = ? OR items.available = true", current_user.id)

5
选择使用 SQL 查询代替。感谢提供信息。 - frostbite
1
@Andrey应该参考我的答案,而不是编辑你的答案并添加相同的内容。无论如何,对于第一个解决方案表示+1。 - Rajdeep Singh
@RSB 我本来打算从 SQL 部分开始,但是在谷歌上搜索错误信息后发现了一个 GitHub 问题。尽管如此,我还是决定加入我的初始想法,因为这是最少的代码让它工作。 - Andrey Deineko
@AndreyDeineko 在Rails应用程序中应该在哪里添加structurally_incompatible_values_for_or方法? - Vieenay Siingh

24

巧妙解决方法:在 .or 之后进行所有的 .joins 操作。这样就可以将有问题的 .joins 隐藏起来,不会被检查器发现。即将原始问题中的代码转换为...

@items =
  @items
  .where(orders: { user_id: current_user.id})
  .or(
    @items
    .where(available: true)
  )
  .joins(:orders) # sneaky, but works! 

通常来说,以下这两行都会失败

A.joins(:b).where(bs: b_query).or(A.where(query))  # error!  
A.where(query).or(A.joins(:b).where(bs: b_query))  # error!  

但是,如果按照以下方式重新排列,您就可以避开检查器:

A.where(query).or(A.where(bs: b_query)).joins(:b)  # works  

这可行是因为所有的检查都发生在.or()方法内部。它非常不知道下游结果中的花哨操作。

当然,一个缺点是它读起来不太好。


1
这为我解决了问题。这确实应该是答案。 - hernan43
1
对我来说也是这样。谢谢!连接和包含必须在“or”之后添加。如果这样可以工作,有人能解释一下为什么Rails不允许我们在“or”之前添加吗? - LiKaZ
1
@LiKaZ 对于 joins,我认为没有什么好的理由。实际上,在 Rails 6.1.3 之后,我认为你可以在 .or 之前放置 .joins,而且我知道你可以在 .or 之前放置 .includes。至于为什么首先存在这个问题......Rails 必须限制某些操作,如 .limit.distinct,因为在 .or 之前允许这些操作将无法翻译成 SQL。 (使用不同的 .limit 组合两个子句会意味着什么?)所有情况的更长说明在此处:https://github.com/rails/rails/issues/24055#issuecomment-793274937 - nar8789
@nar8789 感谢您的解释。我很快就会将我的Rails 6.0应用程序升级到6.1.3! - LiKaZ
1
很有趣的是,如果能了解你的答案所生成的 SQL 语句是什么样子的就更好了,因为重要的不只是它是否能正常运行,更重要的是它执行的是否正确。 - Nuno Costa
@NunoCosta 当然没问题!这是我刚在控制台上运行的(部分编辑过的)测试:puts A.where(id: 1).or(A.where(bs: { id: 1 })).joins(:b).to_sql,输出结果为 SELECT "as".* FROM "as" INNER JOIN "bs" ON "bs"."id" = "as"."b_id" WHERE ("as"."id" = 1 OR "bs"."id" = 1)。在我看来,这是一条很好的 SQL 语句。这也揭示了我原始示例中复数形式的错误。我现在会进行更新。 - nar8789

22

您可以以这种传统方式编写查询,以避免出错

@items = @items.joins(:orders).where("items.available = ? OR orders.user_id = ?", true, current_user.id)

希望这可以帮到你!


0

我遇到了同样的问题,但是代码定义在不同的地方,直接更改非常困难。

# I can't change "p"
p = Post.where('1 = 1').distinct # this could also be a join

我需要在其中添加一个or语句

p.or(Post.where('2 = 2'))

以下代码不会引发错误,因为它具有像初始关系一样的distinct
p.or(Post.where('2 = 2').distinct)

它的问题在于只有在您知道关系时才能正常工作。它可能具有joindistinct,也可能没有。

无论关系如何,这都起作用:

p.or(p.unscope(:where).where('2 = 2'))
=> SELECT DISTINCT `posts`.* FROM `posts` WHERE ((1 = 1) OR (2 = 2))

0

当您尝试合并两个相同类型的多活动记录时,其中一个具有连接值或包含值,或者在您的情况下具有参考值,而另一个没有,则会发生此问题。因此,我们需要匹配它们之间的值,并且我找到了一种通用的方法来做到这一点,而不需要事先知道实际值。

items_1 = @items.joins(:orders)
                .where(orders: { user_id: current_user.id})

items_2 = @items.where(available: true)
                .joins(items_1.joins_values)
                .includes(items_1.includes_values)
                .references(items_1.references_values)

@items = items_1.or(items_2)

-1

解决它!

  def exec_or_statement(q1, q2)
    klass = q1.klass
    key = klass.primary_key

    query_wrapper_1 = {}
    query_wrapper_1[key] = q1

    query_wrapper_2 = {}
    query_wrapper_2[key] = q2

    klass.where(query_wrapper_1).or(klass.where(query_wrapper_2))
  end

  query_1 = @items.where(available: true)

  query_2 =
    @items
    .joins(:orders)
    .where(orders: { user_id: current_user.id})
  
  exec_or_statement(query_1, query_2)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接