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

时间:2010-03-14 12:55:20

标签: sql ruby-on-rails activerecord views named-scope

查看问题底部的更新。

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

这是设置:
#User.rb
has_many:products,:through => :season,:uniq =>真正
has_many:variety,:through => :season,:uniq =>真正
has_many:season

#product.rb
has_many:season
has_many:users,:through => :season,:uniq =>真正
has_many:品种

#season.rb
belongs_to:产品
belongs_to:品种
belongs_to:用户
named_scope:by_product_name,:joins => :product,:order => 'products.name'

#variety.rb
belongs_to:产品
has_many:season
has_many:users,:through => :season,:uniq =>真的

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

#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缩小返回的产品。

现在正在发生的事情是:

user =给我用户
四季=给我用户的季节(比如,用户有3个季节)
产品=给我产品
产品+ =给我产品
产品+ =给我产品

给我每个产品

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

我尝试了以下操作,但它抛出了这个异常:Hash的奇数列表

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 个答案:

答案 0 :(得分:0)

我认为问题是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"}