Rails: найти все ресурсы, еще не подключенные через таблицу соединений - PullRequest
0 голосов
/ 22 января 2020

Я пытаюсь переписать действия, которые подключают и отключают продукты к проектам и от них. В настоящее время в моем представлении 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 the просмотрите теперь сборки, но результат запроса неверен.

@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 из их.

* 103 5 *

В моем запросе должны отображаться товары с идентификаторами: 4, 5, 6, 7, 8, 9, 10, 11, 13, 14.

В настоящее время отображаются товары 3, 4, 5, 6, 7, 8, 9, 10, 12, 13.

Продукт 14 в настоящее время отсутствует в таблице соединений, 3 и 12 уже подключены, а 11 не подключены.

Это как если бы результаты запроса сдвинули одно значение идентификатора влево.

В идеале я хотел бы найти все записи из таблицы продуктов, которые еще не подключены в таблице соединений к данному проекту.

1 Ответ

1 голос
/ 22 января 2020

Вы установили ассоциацию как has_and_belongs_to_many, но HABTM без заголовка, поэтому нет смысла иметь модель объединения.

Вместо этого вы хотите настроить ассоциацию как has_many through: и использовать обычное имя таблицы.

Начните с переименования таблицы объединения в project_products и убедитесь, что в ней есть столбец идентификатора.

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
...