has_many 中 :unique => true/Distinct 选项的问题,通过关联/命名范围(Rails)

Posted

技术标签:

【中文标题】has_many 中 :unique => true/Distinct 选项的问题,通过关联/命名范围(Rails)【英文标题】:Problems with :uniq => true/Distinct option in a has_many_through association w/ named scope (Rails) 【发布时间】:2010-03-14 12:55:19 【问题描述】:

查看问题底部的更新。

我不得不对我的应用程序进行一些调整以添加新功能,而我的更改似乎破坏了以前完美运行的 :uniq 选项。

设置如下:#User.rb has_many :products, :through => :seasons, :uniq => true has_many :varieties, :through => :seasons, :uniq => true has_many :季节

#product.rb has_many :四季 has_many :users, :through => :seasons, :uniq => true has_many :品种

#season.rb 属于_to:产品 属于_to :variety 属于_to :user named_scope :by_product_name, :joins => :product, :order => 'products.name'

#variety.rb 属于_to:产品 has_many :四季 has_many :users, :through => :seasons, :uniq => true

首先,我想向您展示当前正在破坏的视图的先前版本,以便我们有一个基线进行比较。下面的视图是拉起属于用户的产品和品种。在以下两个版本中,我为用户分配了相同的产品/品种,因此日志将查看完全相同的用例。

#user/show

<% @user.products.each do |product| %>  
  <%= link_to product.name, product %>
    <% @user.varieties.find_all_by_product_id(product.id).each do |variety| %>
      <%=h variety.name.capitalize %></p>
<% end %>
<% end %>  

这行得通。它只显示一种产品,然后显示每种产品的品种。在下面的日志中,产品 ID 1 有 3 个关联品种。产品 ID 43 没有。

这是上面代码的日志输出:

Product Load (11.3ms)   SELECT DISTINCT `products`.* FROM `products` INNER JOIN `seasons` ON `products`.id = `seasons`.product_id WHERE ((`seasons`.user_id = 1)) ORDER BY name, products.name  

Product Columns (1.8ms)   SHOW FIELDS FROM `products`  
Variety Columns (1.9ms)   SHOW FIELDS FROM `varieties`  
Variety Load (0.7ms)   SELECT DISTINCT `varieties`.* FROM `varieties` INNER JOIN `seasons` ON `varieties`.id = `seasons`.variety_id WHERE (`varieties`.`product_id` = 1) AND ((`seasons`.user_id = 1)) ORDER BY name  
Variety Load (0.5ms)   SELECT DISTINCT `varieties`.* FROM `varieties` INNER JOIN `seasons` ON `varieties`.id = `seasons`.variety_id WHERE (`varieties`.`product_id` = 43) AND ((`seasons`.user_id = 1)) ORDER BY name

好的,以上所有内容都是以前的版本,运行良好。在新版本中,我在名为seasons 的连接表中添加了一些列,并制作了一组查询这些列的自定义方法。因此,我对您在上面看到的视图代码进行了以下更改,以便我可以访问 seasons 模型上的这些方法:

<% @user.seasons.by_product_name.each do |season| %>  
  <%= link_to season.product.name, season.product %>  
    #Note: I couldn't get this loop to work at all, so I settled for the following:
    #<% @user.varieties.find_all_by_product_id(product.id).each do |variety| %>
    <%=h season.variety.name.capitalize %>  
  <%end%>
<%end%>  

这是日志输出:

SQL (0.9ms)   SELECT count(DISTINCT "products".id) AS count_products_id FROM "products" INNER JOIN "seasons" ON "products".id = "seasons".product_id WHERE (("seasons".user_id = 1))  
Season Load (1.8ms)   SELECT "seasons".* FROM "seasons" INNER JOIN "products" ON "products".id = "seasons".product_id WHERE ("seasons".user_id = 1) AND ("seasons".user_id = 1) ORDER BY products.name  
Product Load (0.7ms)   SELECT * FROM "products" WHERE ("products"."id" = 43) ORDER BY products.name  
CACHE (0.0ms)   SELECT "seasons".* FROM "seasons" INNER JOIN "products" ON "products".id = "seasons".product_id WHERE ("seasons".user_id = 1) AND ("seasons".user_id = 1) ORDER BY products.name  
Product Load (0.4ms)   SELECT * FROM "products" WHERE ("products"."id" = 1) ORDER BY products.name  
Variety Load (0.4ms)   SELECT * FROM "varieties" WHERE ("varieties"."id" = 2) ORDER BY name  
CACHE (0.0ms)   SELECT * FROM "products" WHERE ("products"."id" = 1) ORDER BY products.name  
Variety Load (0.4ms)   SELECT * FROM "varieties" WHERE ("varieties"."id" = 8) ORDER BY name  
CACHE (0.0ms)   SELECT * FROM "products" WHERE ("products"."id" = 1) ORDER BY products.name  
Variety Load (0.4ms)   SELECT * FROM "varieties" WHERE ("varieties"."id" = 7) ORDER BY name  
CACHE (0.0ms)   SELECT * FROM "products" WHERE ("products"."id" = 43) ORDER BY products.name  
CACHE (0.0ms)   SELECT count(DISTINCT "products".id) AS count_products_id FROM "products" INNER JOIN "seasons" ON "products".id = "seasons".product_id WHERE (("seasons".user_id = 1))  
CACHE (0.0ms)   SELECT "seasons".* FROM "seasons" INNER JOIN "products" ON "products".id = "seasons".product_id WHERE ("seasons".user_id = 1) AND ("seasons".user_id = 1) ORDER BY products.name  
CACHE (0.0ms)   SELECT * FROM "products" WHERE ("products"."id" = 1) ORDER BY products.name  
CACHE (0.0ms)   SELECT * FROM "products" WHERE ("products"."id" = 1) ORDER BY products.name  
CACHE (0.0ms)   SELECT * FROM "varieties" WHERE ("varieties"."id" = 8) ORDER BY name

我有两个问题: (1) :uniq 选项不适用于products。同一产品的三个不同版本显示在页面上。 (2) :uniq 选项不适用于varieties。我还没有对此设置验证,如果用户两次输入相同的品种,它确实会出现在页面上。在以前的工作版本中,情况并非如此。

我需要的结果是任何给定 ID 仅显示一个产品,并且与该 ID 关联的所有品种与此类唯一产品一起显示。

让我印象深刻的一件事是最近的日志输出中的 sql 调用。它将“计数”添加到不同的调用中。我不确定它为什么这样做,或者它是否可能表明存在问题。我发现这张未解决的灯塔票似乎可能相关,但我不确定它是否是同一个问题:https://rails.lighthouseapp.com/projects/8994/tickets/2189-count-breaks-sqlite-has_many-through-association-collection-with-named-scope

更新

我认为问题在于,named_scope 每个季节都被调用一次。 named_scope 中需要有一些东西可以按季节 ID 缩小返回的产品范围。

现在发生的事情是:

用户 = 给我用户 季节 = 获取用户的季节(例如,用户有 3 个季节) 产品 = 给我产品 产品 += 给我产品 产品 += 给我产品

给我每个产品

所以发生的不是 uniq 被破坏,而是命名范围上没有分隔符。 (我认为)。

我尝试了以下方法,但它抛出了这个异常:散列的奇数列表

named_scope :by_product_name, lambda  |seasons|  season_ids = seasons.map  |season| season.id ; :joins => :product, :conditions =>  :seasons  :id => season_id    :order => 'products.name'    

想法?

更新 #2

好的,现在我在想也许它根本不是命名范围。

#user/show,我只是改了循环绕过命名作用域:

<% @user.seasons.each do |season| %>  
  <%= link_to season.product.name, season.product %>  
    #Note: I couldn't get this loop to work at all, so I settled for the following:
    #<% @user.varieties.find_all_by_product_id(product.id).each do |variety| %>
    <%=h season.variety.name.capitalize %>  
  <%end%>
<%end%>    

上面没有使用命名范围,但我仍然得到相同的结果。换句话说,我仍然看到每个产品的所有实例,而不仅仅是一个。

上面创建第一个循环的代码与我在此问题顶部列出的原始代码相同。不同之处在于这段代码循环通过seasons 到达products,而我的原始代码循环通过products。这种差异是问题所在,但我不知道如何解决它。

另外,我在最初的问题中提到我也无法让品种循环正常工作。您可以直接在上面的代码中看到注释的行。当循环遍历季节而不是产品时,当 Rails 遇到那个品种循环时,它会抛出一个名称错误:

undefined local variable or method `product'  

似乎这可能是同一问题的另一个症状?

还有其他想法吗?

【问题讨论】:

【参考方案1】:

我认为问题在于 lambda 的格式。我显然无法运行 SQL,但以下 lambda 确实创建了一个适当的哈希:

lambda  |seasons| season_ids = seasons.map  |season| season.id ;  :joins => :product, :conditions =>  :seasons =>  :id => season_ids  , :order => 'products.name'  

ID 为 1 和 2 的两个季节的调用的输出是:

:joins=>:product, :conditions=>:seasons=>:id=>[1, 2], :order=>"products.name"

【讨论】:

运行此程序时出现 No Method 错误...当您没有预料到它时,您有一个 nil 对象!您可能期望有一个 Array 的实例。评估 nil.map 时发生错误

以上是关于has_many 中 :unique => true/Distinct 选项的问题,通过关联/命名范围(Rails)的主要内容,如果未能解决你的问题,请参考以下文章

Rails 4中has_many中的静态范围

Rails:为啥“has_many ...,:通过=> ...”关联导致“NameError:未初始化的常量...”

接受_nested_attributes_for with has_many => :through Options

has_many :通过可以构建多个实例的nested_form

如何覆盖:在 has_many 中定义的顺序

Rails中如何通过对象同时保存多个has_many?