2016-09-30 79 views
0

我正在为Discourse的插件工作,这意味着我可以使用class_eval修改类,但是我无法更改数据库模式。要存储有关Topic模型的额外数据,我可以使用为此提供的TopicCustomField执行连接。预加载复杂链接/间接ActiveRecord函数/'协会'

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

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

我希望能写这样的:

 
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: 'Group', primary_key: :parent_guids, foreign_key: :name 

但这:通过抱怨不能够找到一个关联“:parent_guids”在TopicCustomField和primary_key不会采取实际的关联,而不是一个数据库列。

我也尝试了以下方法,但是: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

+0

看起来像一般战略自定义预加载是在这里:https://mrbrdo.wordpress.com/2013/09/25/manually-preloading-associations-in-rails-using-custom-scopessql/ – Akh

回答

0

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

我修改了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) 
0

其实,导轨协会through参数设置与模型的许多-to-many关联,传递 “到” 其他型号:

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

所以你不能做

has_many :parent_guids, :through => :topic_custom_fields 

因为ParentGuid不是与TopicCustomFields相关的型号。此外,传递给has_many块,只有需要使用新的方法为那些铁轨延伸的关联已经提供了你,就像topic_custom_fields.createtopic_custom_fields.build

你为什么不以你的第二个例子定义块内的方法在Topic类中检索组?有没有你想要的东西,只有使用这些方法才有可能?

更新

好了,我不认为这是可能实现在这种情况下,同样的改进性能,因为群ID仍然必须从topic_custom_fields处理,并达到更高的性能,通过连接。也许复杂的组合preload,wherereferences可以做到这一点,但我不知道是否有可能。

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

+0

我实际上已经工作,但它导致子性能,因为这些方法会针对每个主题单独进行评估。对于关联,我可以执行topics.preload(:topic_custom_fields),它可以一次性为所有主题加载所有相关的自定义字段数据。我想同样能够做topics.preload(:parent_guids),preload(:parent_groups)等...我知道,通过不这样工作,所以我想找到一个替代方案,可以导致在同样的改进表现! – Akh