【问题标题】:Preloading of chain of complex/indirect ActiveRecord functions/'associations'复杂/间接 ActiveRecord 函数/“关联”链的预加载
【发布时间】:2016-09-30 20:23:40
【问题描述】:

我正在为Discourse 开发一个插件,这意味着我可以使用class_eval 修改类,但我无法更改数据库架构。要存储有关主题模型的额外数据,我可以使用为此目的提供的 TopicCustomField 执行连接。

我能够存储和检索我需要的所有数据,但是当一次加载多个主题时,数据库性能低效,因为我的间接数据为每个主题单独加载一次。如果为每个主题一次加载这些数据会更好,就像使用预加载或包含时一样。

例如,每个 Topic 都有一个 topic_guid 和一组 parent_guid(存储在带有破折号的单个字符串中,因为顺序很重要)。这些 parent_guids 指向其他主题的 topic_guids 以及其他组的名称。

我希望能够写出类似的东西:

has_many :topic_custom_fields has_many :parent_guids, -> { where(name: 'parent_guids').pluck(:value).first }, :through => :topic_custom_fields has_many:parent_groups,class_name:'组',primary_key::parent_guids,foreign_key::name

但是这个 :through 抱怨无法在 TopicCustomField 中找到关联“:parent_guids”,并且 primary_key 实际上不会采用关联而不是 DB 列。

我也尝试了以下方法,但 :through 子句无法将函数用作关联。

has_many :topic_custom_fields do
    def parent_guids
        parent_guids_str = where(name: PARENT_GUIDS_FIELD_NAME).pluck(:value).first
        return [] unless parent_guids_str
        parent_guids_str.split('-').delete_if { |s| s.length == 0 }
    end
    def parent_groups
        Group.where(name: parent_guids)
    end
end

has_many :parent_guids, :through => :topic_custom_fields
has_many :parent_groups, :through => :topic_custom_fields

使用 Rails 4.2.7.1

【问题讨论】:

标签: ruby-on-rails ruby activerecord


【解决方案1】:

其实rails associations的through参数就是设置一个模型的多对多关联,传递“通过”其他模型:

http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association

所以你不能这样做

has_many :parent_guids, :through => :topic_custom_fields

因为ParentGuid 不是与TopicCustomFields 相关的模型。此外,将一个块传递给has_many 只是为了扩展与新方法的关联,因为rails 已经为您提供了这些方法,例如topic_custom_fields.createtopic_custom_fields.build 等。

为什么不在Topic 类的第二个示例中定义块内的方法来检索组?有没有你想要的东西,只用方法是不可能的?

更新

好吧,我认为在这种情况下不可能实现同样的性能提升,因为组 id 仍然必须从 topic_custom_fields 处理,并且通过 JOIN 实现了性能提升。也许preloadwherereferences 的复杂组合可以解决问题,但我不知道这是否可能。

您可以尝试最小化 db 调用,也许在查询组之前收集所有 parent_guids。

【讨论】:

  • 我实际上已经完成了这项工作,但它导致性能低于标准,因为这些方法会针对每个主题单独评估。对于关联,我可以使用 topic.preload(:topic_custom_fields) 加载所有相关的自定义字段数据,一次查询所有主题。我希望同样能够做topics.preload(:parent_guids),preload(:parent_groups)等......我知道它不能像这样工作,所以我想找到一个可以导致的替代方案在相同的改进性能!
【解决方案2】:

我希望有一个更优雅的解决方案,但这是我为了有效地预加载我的数据所做的。这应该很容易扩展到其他应用程序。

我修改了 Relation 的 exec_queries,它调用了其他的预加载函数。

ActiveRecord::Relation.class_eval do
    attr_accessor :preload_funcs

    old_exec_queries = self.instance_method(:exec_queries)
    define_method(:exec_queries) do |&block|
        records = old_exec_queries.bind(self).call(&block)
        if preload_funcs
            preload_funcs.each do |func|
                func.call(self, records)
            end
        end
        records
    end
end

对于主题,我补充说:

has_many :topic_custom_fields
attr_accessor :parent_groups

def parent_guids
    parent_guids_str = topic_custom_fields.select { |a| a.name == PARENT_GUIDS_FIELD_NAME }.first
    return [] unless parent_guids_str
    parent_guids_str.value.split('-').delete_if { |s| s.length == 0 }
end

然后为了预加载 parent_groups,我这样做了:

def preload_parent_groups(topics)
    topics.preload_funcs ||= []
    topics.preload_funcs <<= Proc.new do |association, records|
        parent_guidss = association.map {|t| t.parent_guids}.flatten
        parent_groupss = Group.where(name: parent_guidss).to_a

        records.each do |t|
            t.parent_groups = t.parent_guids.map {|guid| parent_groupss.select {|group| group.name == guid }.first}
        end
    end
    topics
end

最后,我将预加载器添加到我的关系查询中:

result = result.preload(:topic_custom_fields)
result = preload_parent_groups(result)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多