简体   繁体   中英

Rails 5 - what is the correct way to access this model structure from a single entity?

I have a Product and Category model structure, however, the attributes that belong to each product are dependent upon which category they are in.

Please note that this is not about STI (single table inheritance) as I am not implementing specific classes that inherit from Product (eg like Book < Product , Bicycle < Product etc)

I have the following model classes (simplified to the pertinent code only):

class Product < ApplicationRecord
  # Relationships
  belongs_to :subcategory
  delegate :category, to: :subcategory, allow_nil: true
  has_many :product_category_attributes
end

class Category < ApplicationRecord
  # Relationships
  has_many :subcategories, dependent: :destroy
  has_many :products, through: :subcategories
  has_many :category_attributes, dependent: :destroy
end

class CategoryAttribute < ApplicationRecord
  # Relationships
  belongs_to :category
  has_many :product_category_attributes, dependent: :destroy
end

class ProductCategoryAttribute < ApplicationRecord
  # Relationships
  belongs_to :product
  belongs_to :category_attribute
end

This works but...bear with me here!

Here is the migrations for the category hierarchy:

class CreateCategories < ActiveRecord::Migration[5.1]
  def change
    create_table :categories, id: :uuid do |t|
      t.string :name

      t.timestamps
    end
  end
end

class CreateCategoryAttributes < ActiveRecord::Migration[5.1]
  def change
    create_table :category_attributes, id: :uuid do |t|
      t.references :category, type: :uuid, foreign_key: true, index: true 
      t.string :name
      t.string :value
      
      t.timestamps
    end
  end
end

class CreateProductCategoryAttributes < ActiveRecord::Migration[5.1]
  def change
    create_table :product_category_attributes, id: :uuid do |t|
      t.references :product, type: :uuid, foreign_key: true
      t.references :category_attribute, type: :uuid, foreign_key: true

      t.timestamps
    end
    add_index :product_category_attributes, [:product_id, :category_attribute_id], unique: true, name: 'index_product_category_attributes_on_fks'
  end
end

Imagine a category with the name 'Rugs'. Rugs have a size, so:

category_attribute.name would be 'size'

and

category_attribute.values would have 3 - 'small', 'medium', 'large'

So in the `category_attribute' table:

| id | name | value

| 1 | size | small

| 2 | size | medium

| 3 | size | large

But an individual Rug product might only be available in 'small'.

So that's the reason for the ProductCategoryAttribute -> I can assign only the attributes that applies to that particular product.

What I don't understand is how can I 'short-cut' the ActiveRecord associations created.

For example, if I load a Product in memory, how can I modify the associations so that I can do:

product.category_attributes or even product.attributes

to return only those applicable to that product.

As an example, I know I can get a single attribute like this:

p.product_category_attributes.first.category_attribute

and that obviously only returns the first one (and is also quite unwieldy).

I believe there is a way to add a method or a scope - I'd like to call it 'attributes' - that could solve my problem, but I am a bit lost on how to proceed.

It seems that attributes is already reserved by ActiveModel?

Attempt 1 Barking up the wrong tree here but worth a try, eh?

Product.rb

  def cat_attributes
    self.product_category_attributes.each do |pca|
      return pca.category_attribute
    end
  end

Returns the product_category_attributes , not the category_attributes

Attempt 2

Product.rb

has_many :category_attributes, through: :product_category_attributes

This returns the data I want, but now how do I alias the name to something shorter?

Attempt 3

Product.rb

has_many :cat_atts, class_name: 'CategoryAttribute', through: :product_category_attributes, source: :category_attributes

Attempt 4

Delete *.*
ReturnTo C#

Thanks.

according to Rails Guide you can go with your attempt 3 version. Like:

# app/models/product.rb

  has_many :product_category_attributes
  has_many :cat_atts, through: :product_category_attributes, source: :category_attribute

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