Rails通过连接表过滤器预加载查询

时间:2015-01-27 16:32:13

标签: sql ruby-on-rails optimization eager-loading

我正在查询一堆由人们写的和收到的信件。我通过连接表将字母表与人员表相关联,该连接表使用人物类型标识符(0 =创建者,1 =收件人)确定该人是否是相关字母的创建者或收件人。

我的问题:我在一个视图中为用户一次显示50个字母,其中包含可以找到该字母的日期,创建者,收件人和博物馆收藏等信息。页面加载速度非常慢,因为尽管我已经弄清楚如何包括"包括"集合,以便不在显示的每个字母上运行查询,我无法为创建者和收件人执行此操作。如果我只是显示"人们"我也可以做一个包含,但由于我按照他们的角色过滤了人,因此rails在视图中为每个字母运行了几个查询,这需要大约6000毫秒。如果没有创建者和收件人,视图将在大约250毫秒内加载。

以下是我的模型的基础知识:

class ArchiveObject < ActiveRecord::Base
    has_many :archive_object_people
    has_many :people, :through => :archive_object_people

class ArchiveObjectPerson < ActiveRecord::Base
    belongs_to :archive_object
    belongs_to :person

class Person < ActiveRecord::Base
    has_many :archive_object_people
    has_many :archive_objects, :through => :archive_object_people

我还在模型中创建了一些范围和方法来帮助我。 在archive_object.rb中

# These can be used to distinguish recipients from creators of letters
# Use case:  ArchiveObject.first.creators
def recipients
  people.where("archive_object_people.persontype = '1'")
end
def creators
  people.where("archive_object_people.persontype = '0'")
end

scope :recipients, lambda {
  joins(:people).where("archive_object_people.persontype = '1'")
}
scope :creators, lambda {
  joins(:people).where("archive_object_people.persontype = '0'")
}

我想以我所包含的相同方式预加载创作者和收件人,比如说,字母的位置,以便我可以遍历每个字母并显示letter.title,letter.creators,letter.collection等等。运行几十个查询。例如:ArchiveObject.includes(:collections,:creators)

到目前为止我尝试过的事情。所有这些都会在获取收件人/创建者时运行另一个查询,尽管他们在查看泛型&#34;人员时可能无法运行另一个查询&#34;:

ArchiveObject.joins(:people).where("archive_object_people.persontype = '1'").preload(:people)

ArchiveObject.includes(:people, :archive_object_people).first.recipients

ArchiveObject.includes(:recipients)  => raises an exception

我也开始阅读有关重新定义has_many并使用条件仅拉动创建者或收件人的内容,但实际上我需要两个关联,在这种情况下:has_many创建者和has_many收件人。我不知道如何设置它,但如果有人有任何想法,那将是rad。

感谢您提供给我的任何帮助或建议!我很想知道这一点,以便我有一个超级快速和高效的网站。 :)

1 个答案:

答案 0 :(得分:1)

好。我花了几个小时但我想出来了!

我删除了所有自定义实用程序方法并重写了一些has_many类型的东西。

class ArchiveObject < ActiveRecord::Base
  has_many :archive_object_people
  has_many :people, :through => :archive_object_people

  has_many :creators, -> { where "archive_object_people.persontype = '0'" },
           :through => :archive_object_people
           :source => :person
  has_many :recipients, -> { where "archive_object_people.persontype = '1'" },
           :through => :archive_object_people,
           :source => :person

使用模型中的以下内容,您可以执行以下操作:

test = ArchiveObject.includes(:people, :creators, :recipients)
test.first.people  # => list of all people on first object
test.first.creators # => list of all people who are creators on first object
test.first.recipients # => all people who are recipients

我之前能够使用我的自定义方法获取该信息,但现在可以预先加载它们,以便它可以预先运行大量查询,然后在为每个字母提取信息时不需要再次重新运行。< / p>

我希望这有助于其他人。请注意:has_many描述中的:source是:person,not:people,这引起了我一些暂时的痛苦。