【问题标题】:Rails: find all resources not already connected through join tableRails:通过连接表查找所有尚未连接的资源
【发布时间】:2020-01-22 10:07:39
【问题描述】:

我正在尝试重写将产品与项目连接和断开的操作。目前我的select_to_project 视图显示所有产品,但我希望它只显示尚未连接到给定项目的产品。

产品和项目通过连接表连接

class Product < ActiveRecord::Base
  has_and_belongs_to_many :projects, :join_table => "projects_products"  
end

class Project < ActiveRecord::Base
  has_and_belongs_to_many :products, :join_table => "projects_products" 
end

class ProjectsProduct < ActiveRecord::Base
  attr_accessible :project_id, :product_id

  belongs_to :project
  belongs_to :product
end

在我目前拥有的产品控制器中:

def select_to_project
  @project = Project.find(params[:id])
  @products = Product.find(:all)
end

def select_from_project
  @project = Project.find(params[:id])
end

显然select_to_project 视图当前显示所有可能的产品,即使是那些已经通过连接表连接的产品。

我认为应该将select_to_project 操作更改为如下内容:

def select_to_project
  @project = Project.find(params[:id])
  @products = Product.joins(:projects => :products).where('products_projects_join.product_id IS NOT ?', @product)
end

但我目前在尝试加载相对视图时遇到 MySQL 错误:

Mysql::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '1)' at line 1: SELECT `products`.* FROM `products` INNER JOIN `projects_products` ON `projects_products`.`product_id` = `products`.`id` INNER JOIN `projects` ON `projects`.`id` = `projects_products`.`project_id` INNER JOIN `projects_products` `products_projects_join` ON `products_projects_join`.`project_id` = `projects`.`id` INNER JOIN `products` `products_projects` ON `products_projects`.`id` = `products_projects_join`.`product_id` WHERE (products_projects_join.project_id IS NOT 1)

如何让这个查询在 Rails 3 中工作?

非常感谢您。

更新

感谢@Sebastian Palma,现在可以构建视图,但查询结果不正确。

@products = Product.joins(:projects => :products).where('products_projects_join.project_id != ?', @project.id).uniq.order(:id, :order => 'id ASC')

这会产生以下查询:

SELECT DISTINCT 'products'.* FROM 'products' INNER JOIN 'projects_products' ON 'projects_products'.'product_id' = 'products'.'id' INNER JOIN 'projects' ON 'projects'.'id' = 'projects_products'.'project_id' INNER JOIN 'projects_products' 'products_projects_join' ON 'products_projects_join'.'project_id' = 'projects'.'id' INNER JOIN 'products' 'products_projects' ON 'products_projects'.'id' = 'products_projects_join'.'product_id' WHERE (products_projects_join.project_id != 2) ORDER BY id, '--- \n:order: id ASC\n'

我有 14 个产品条目,项目 2 已经连接到其中的 4 个。

projects_products_id / project_id / product_id
3 2 1
4 2 2
5 2 3
6 2 12

我的查询应该显示 ID 为:4、5、6、7、8、9、10、11、13、14 的产品。

目前显示产品 3、4、5、6、7、8、9、10、12、13。

连接表上当前不存在产品 14,3 和 12 已连接,而 11 未连接。

好像查询结果向左滑动了一个id值。

理想情况下,我想从产品表中找到尚未在连接表上连接到给定项目的所有条目。

【问题讨论】:

  • 你想用IS NOT实现什么?试试where('products_projects_join.product_id != ?', @product.id)
  • @SebastianPalma 谢谢我一直认为IS NOT == !=。现在,使用@products = Product.joins(:projects =&gt; :products).where('products_projects_join.project_id != ?', @project.id).uniq.order(:id, :order =&gt; 'id ASC') 渲染视图,但查询结果不正确。我将在我的问题中添加更多信息。
  • @SebastianPalma 我添加了一些数据。结果似乎总是id-1。就像 products[0] 是第一个条目一样。你知道为什么会这样吗?我怎样才能做到这一点?

标签: mysql ruby-on-rails ruby-on-rails-3 activerecord rails-activerecord


【解决方案1】:

您已将关联设置为 has_and_belongs_to_many,但 HABTM 是无头的,因此实际上也没有连接模型。

相反,您希望将关联设置为 has_many through: 并为表使用常规名称。

首先将连接表重命名为 project_products,并确保它有一个 id 列。

class Product < ActiveRecord::Base
  has_many :project_products 
  has_many :projects, through: project_products 
end

class Project < ActiveRecord::Base
  has_many :project_products 
  has_many :products, through: project_products 
end

class ProjectProduct < ActiveRecord::Base
  attr_accessible :project_id, :product_id
  belongs_to :project
  belongs_to :product
end

这将让您通过project_products 协会加入。

最简单的方法是执行子查询。在现代版本的 Rails 中,你会这样做:

Product.where.not(
  id: project.products
)

这会创建以下查询:

SELECT "products".* FROM "products" 
WHERE "products"."id" NOT IN (
  SELECT "products"."id" FROM "products" 
  INNER JOIN "project_products" ON "products"."id" = "project_products"."product_id" 
  WHERE "project_products"."project_id" = $1
) LIMIT $2

但是 where.not 是在 Rails 4.0 中引入的。作为一种解决方法,您可以在 Rails 3 中使用字符串。

class Product < ApplicationRecord
  has_many :project_products
  has_many :projects, through: :project_products

  def self.not_assigned_to_project(project)
    Product.where("
      products.id NOT IN (#{project.products.select(:id).to_sql})
    ")
  end
end

【讨论】:

  • 我没有安装 Rails 3,所以这段代码只是在 Rails 6 中测试过。你的里程可能会有所不同。
  • 非常感谢。我怀疑我必须在控制器中使用@products = Product.not_assigned_to_project(@project)?根据您的意见,我得到:Mysql::Error: Column 'id' in field list is ambiguous: SELECT 'products'.* FROM 'products' WHERE (products.id NOT IN (SELECT id FROM 'products' INNER JOIN 'project_products' ON 'products'.'id' = 'project_products'.'product_id' WHERE 'project_products'.'project_id' = 2))
  • HABTM 和has_many through: 之间的区别在于后者使用了一个模型来连接,而且HABTM 使用了一个愚蠢的表命名方案,这违背了rails 中的所有其他约定。 HABTM 实际上非常无用,因为您无法查询连接表或向连接表添加任何其他数据。 flatironschool.com/blog/…
  • 你设置它的方式看起来很傻。您拥有这两种方法的所有缺点,却没有 has_many through: 的优点。
  • 类名选项需要是类“作者”的实际名称。但是如果你问一个新问题会更好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-06
  • 2015-02-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多