简体   繁体   中英

Rails & Postgres: Join tables on an array

Conceptually what I am trying to do is to be able to say: product a is available in 5 colors, which will be an array of color ids. These color ids are the keys that link to a colors table that holds color related data such as hex representations, color images, and so on. Finally, I want to join the colors table with the products table so that I can pull over color related data.

In my current setup, I have two tables: products and colors . In my products table I have a column ( color_ids ) that holds an array of integers. This array holds the ids of the colors in the colors table. To join the two in Rails, I created a custom SQL string that is in the Product class eg:

class Product < ApplicationRecord
  has_many :colors

  def self.custom_query   
    "SELECT * FROM products JOIN colors on colors.id = ANY(products.color_ids)
    WHERE products.name = 'Some Product'"
  end

end

I tried using associations ( includes(:colors) ) but that doesn't seem to work since the primary id is an array of ids.

Is there a more elegant / Rails way to achieve what I am trying to do?

Use simple has_and_belongs_to_many association. Don't store your reference ids in an array, just because PostgreSQL kind of allows you to do so, it's not how relations should be implemented in relational databases.

# new migration
create_table :colors_products do |t|
  t.references :color, foreign_key: true
  t.references :product, foreign_key: true
end
add_index :colors_products, [:color_id, :product_id], unique: true


class Product < ApplicationRecord
  has_and_belongs_to_many :colors
end

class Color < ApplicationRecord
  has_and_belongs_to_many :products
end

All of the ActiveRecord methods will work then.

Why shouldn't you have relations made with arrays (unless you really know what you're doing):

  1. You cannot use Foreign keys. You will be able to have a color_id in your array that is no longer in the database.
  2. If you delete a color, your database cannot take care of automatically clearing its id for all products.
  3. ActiveRecord (and most ORM) will simply not work and you will need huge amount of workarounds.

You can simply do

class Product < ApplicationRecord
  def colors   
    Color.where(id: color_ids)
  end
end

class Color < ApplicationRecord
  def products   
    Product.where('? = Any (category_ids)', id)
  end
end

You could create a view named like:

CREATE VIEW product_colors(id, product_id, name, hex, image) AS

SELECT
  p.id as product_id,
  c.name,
  c.hex,
  c.image
FROM
  (
  SELECT
      id,
      unnest(color_ids) as color_id
    FROM
      products
  ) as p
  INNER JOIN colors c
    ON c.id = p.color_id;

And then add model ProductColor with

def readonly?
 true
end

And you can treat this view as a child model. Products.includes(:product_colors)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM