Remy Wang
Remy Wang

Reputation: 783

Rails - association issue for complex models

I have 3 tables.

product
product_attribute_mappings
product_attribute_values

Here are some rows of each table.

products

id  |name
1058|shoes

product_attribute_mappings

id  | product_id | product_attribute_id
438 | 1058       | 9

product_attribute_values

id   | product_attribute_mapping_id | value
2001 | 438                          | 18 oz
2002 | 438                          | 19 oz

As you can see here,

product.id = product_attribute_mappings.product_id
product_attribute_values.product_attribute_mapping_id = product_attribute_mappings.id

I want to get all product attributes values like

product.product_attribute_values # ["18 oz", "19 oz"]

But I am not sure how I can make models with associations to get as I want.

Does anyone have any idea?

Upvotes: 0

Views: 34

Answers (2)

max
max

Reputation: 101831

What you have is a really strange backwards variant of the Entity Attribute Value (EAV) pattern. It would make more sense if you had the normalized attribute definitions (eg volume, weight, number of doodads, etc) on one table and the entitiy, attribute and value on one table.

class Product < ApplicationRecord
  has_many :product_attributes
  has_many :product_attribute_types, through: :product_attributes
  # eager loading scope
  def self.eager_load_attributes
    eager_load(product_attributes: :product_attribute_types)
  end
end

# This is the normalization table that stores the definition of an attribute
# rails g model ProductAttribute name:string unit:string
class ProductAttributeType< ApplicationRecord
  has_many :product_attributes
  has_many :product_attribute_types, through: :product_attributes
end

# This is the actual table that defines the attributes
# rails g model ProductAttribute product:belongs_to product_attribute_type:belongs_to value:jsonb
class ProductAttribute < ApplicationRecord
  belongs_to :product # the entity
  belongs_to :product_attribute_type # the attribute
  # just shortcuts
  delegates :name, to: :product_attribute_type
  delegates :unit, to: :product_attribute_type
end

This uses a JSON column as the value to alieviate one of the classical issues with EAV which is that you have to cast everything into a single (usually string) type column. JSON can store numbers (not terribly well), strings, arrays and objects.

This lets you iterate through the products and attributes with:

# eager loading avoids a n+1 query
@products = Product.eager_load_attributes.all

@products.each do |product|
   product.product_attributes.each do |attr|
     puts "#{attr.name}: #{attr.value}{attr.unit}"
   end
end

Upvotes: 1

Thuy Nguyen
Thuy Nguyen

Reputation: 362

You can change your association name from product_attribute_values to product_attribute_values_association, then define product_attribute_values as instance methods

Upvotes: 0

Related Questions