模型 has_many AND has_many :through 合二为一?

发布于 2024-12-22 11:40:32 字数 1053 浏览 1 评论 0原文

我有一组类别,属于一个类别集。 这些类别本身可以混合类别(自我指涉)和问题。 模型及其关系被定义并被定义。可视化如下:

class CategorySet < ActiveRecord::Base
  has_many :categories
end

class Category < ActiveRecord::Base
  belongs_to :category_set

  belongs_to :parent_category, :class_name => "Category"
  has_many :categories, :class_name => "Category", :foreign_key => "parent_category_id", dependent: :destroy

  has_many :questions, dependent: :destroy
end

class Question < ActiveRecord::Base
  belongs_to :category
end

Abbrev to CS, C and Q:

CS
  |- C
     |- Q
     |
     |- C
     |
     |- C
     |  |- Q
     |
     |- C
        |- Q
        |- Q

我希望能够询问 CategorySet.find(1).questions 并返回树中的所有问题,无论位置如何。我能想到的唯一方法是使用大量基于函数的请求,并且可能会对 sql 语句造成过度杀伤(请参阅下面的示例)。

调用CategorySet.find(1).categories仅查找类别集的直接后代类别。此外,Category.find(id).questions 仅返回该类别的问题。

我尝试过覆盖类别上的 .questions 方法,但这似乎不太符合 Rails 关系风格,必须有更好的方法来做到这一点? Alo 这意味着我无法执行 CategorySet.includes(:questions).all 样式语法,这大大减少了数据库服务器上的负载

I have a set of categories, belonging to a category set.
These categories can themselves have a mix of categories (self-referential) and also questions.
The models and their relationships are defined & visualized as follows:

class CategorySet < ActiveRecord::Base
  has_many :categories
end

class Category < ActiveRecord::Base
  belongs_to :category_set

  belongs_to :parent_category, :class_name => "Category"
  has_many :categories, :class_name => "Category", :foreign_key => "parent_category_id", dependent: :destroy

  has_many :questions, dependent: :destroy
end

class Question < ActiveRecord::Base
  belongs_to :category
end

Abbrev to CS, C and Q:

CS
  |- C
     |- Q
     |
     |- C
     |
     |- C
     |  |- Q
     |
     |- C
        |- Q
        |- Q

I would like to be able to ask CategorySet.find(1).questions and return all questions in the tree regardless of position. The only ways I can think of use lots of function-based requests and will probably be overkill on sql statements (see below for an example).

Calling CategorySet.find(1).categories finds only the direct descendant categories of the category set. Also, Category.find(id).questions returns only the questions for that category.

I have tried overwriting the .questions method on categories, but that doesn't seem very rails relationship-esque and there must be a better way of doing this? Alo it means I can't do the CategorySet.includes(:questions).all style syntax which greatly reduces the load on the database server

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

街角迷惘 2024-12-29 11:40:32

方法 1

使用awesome_nested_set

class CategorySet < ActiveRecord::Base
  has_many :categories
  def questions
    categories.map do |c|   
      c.self_and_descendants.include(:questions).map(&:questions)
    end.flatten
  end
end

class Category < ActiveRecord::Base
  awesome_nested_set
  belongs_to :category_set
  has_many :questions
end

class Question < ActiveRecord::Base
  belongs_to :category
end

参考awesome_nested_set 文档,用于获取 gem 所需的附加列的列表。

方法2

方法1将所有问题加载到内存中,并且不支持基于数据库的分页。

如果避免为 CategorySet 维护单独的表,则可以获得更好的性能,因为每个类别都可以包含其他类别。

class Category < ActiveRecord::Base
  awesome_nested_set
  has_many :questions
  # add a boolean column called category_set

  def questions
    join_sql = self_and_descendants.select(:id).to_sql
    Question.joins("JOIN (#{join_sql}) x ON x.id = questions.id")  
  end
end

class Question < ActiveRecord::Base
  belongs_to :category
  validates :category_id, :presence => true, :category_set => true
end

# lib/category_set_validator.rb
class CategorySetValidator < ActiveModel::EachValidator  
  def validate_each(object, attribute, value)  
    if record.category.present? and record.category.category_set?
      record.errors[attribute] << (options[:message] || 
                    "is pointing to a category set") 
    end
  end 
end  

现在您可以获取有关类别设置的问题

cs.questions.limit(10).offset(0)
cs.questions.paginate(:page => params[:page])  # if you are using will_paginate 

Approach 1

Use awesome_nested_set for this

class CategorySet < ActiveRecord::Base
  has_many :categories
  def questions
    categories.map do |c|   
      c.self_and_descendants.include(:questions).map(&:questions)
    end.flatten
  end
end

class Category < ActiveRecord::Base
  awesome_nested_set
  belongs_to :category_set
  has_many :questions
end

class Question < ActiveRecord::Base
  belongs_to :category
end

Refer to the awesome_nested_set documentation to get the list of additional columns required by the gem.

Approach 2

The approach 1 loads all the questions in to the memory and it does not support DB based pagination.

You can get better performance if you avoid maintaining a separate table for CategorySet as each category can contain other categories.

class Category < ActiveRecord::Base
  awesome_nested_set
  has_many :questions
  # add a boolean column called category_set

  def questions
    join_sql = self_and_descendants.select(:id).to_sql
    Question.joins("JOIN (#{join_sql}) x ON x.id = questions.id")  
  end
end

class Question < ActiveRecord::Base
  belongs_to :category
  validates :category_id, :presence => true, :category_set => true
end

# lib/category_set_validator.rb
class CategorySetValidator < ActiveModel::EachValidator  
  def validate_each(object, attribute, value)  
    if record.category.present? and record.category.category_set?
      record.errors[attribute] << (options[:message] || 
                    "is pointing to a category set") 
    end
  end 
end  

Now you can get questions for a category set as

cs.questions.limit(10).offset(0)
cs.questions.paginate(:page => params[:page])  # if you are using will_paginate 
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文