ActiveRecord对象数组上的Ruby Array#sort_by似乎很慢

时间:2016-03-14 19:10:16

标签: ruby-on-rails ruby performance sorting activerecord

我正在编写一个控制器索引方法,它返回一个ActiveRecord Contact对象的排序数组。我需要能够按属性或实例方法的输出对对象进行排序。例如,我需要能够按contact.emailcontact.photos_uploaded进行排序,order是一种返回联系人照片数量的实例方法。

我不能使用ActiveRecord的本机reorderarray#sort_by方法,因为它只适用于数据库中列的属性。我从阅读中知道,对于复杂的对象,array#sort通常比contacts = company.contacts.order(last_name: :asc) if params[:order].present? && params[:order_by].present? && (Contact::READ_ONLY_METHOD.include?(params[:order_by].to_sym) || Contact::ATTRIBUTES.include?(params[:order_by].to_sym)) contacts = contacts.sort_by do |contact| if params[:order_by] == 'engagement' contact.engagement.to_i else contact.method(params[:order_by].to_sym).call end end contacts.reverse! if params[:order] == 'desc' end 快得多。

我的问题是,如何在我的控制器方法中提高这段代码的性能?目前的代码

sort_by

这里的根本问题(我认为)是我在contacts上呼叫ActiveRecord::Relation,这是Contact,其中可能有几百个联系人。最后,我在将结果返回给客户端之前对结果进行分页,但是在对它们进行分页之前需要对它们进行排序。当我使用200个联系人运行上面的代码块时,执行平均需要900毫秒,如果用户有数千个联系人,这可能是生产环境中的问题。

这是我的if模型,显示了一些相关的方法。我为engagement设置特殊class Contact < ActiveRecord::Base has_many :invites has_many :responses, through: :invites has_many :photos has_many :requests belongs_to :company ATTRIBUTES = self.attribute_names.map(&:to_sym) READ_ONLY_METHOD = [:engagement, :stories_requested, :stories_submitted, :stories_published] def engagement invites = self.invites.present? ? self.invites.count : 1 responses = self.responses.present? ? self.responses.count : 0 engagement = ((responses.to_f / invites).round(2) * 100).to_i.to_s + '%' end def stories_requested self.invites.count end def stories_submitted self.responses.count end def stories_published self.responses.where(published: true).count end end 子句的原因是因为该方法返回一个需要转换为整数的字符串以进行排序。在我提交任何一个返回整数之前,我可能会重构它。通常,我可能排序的所有方法都返回一个表示相关对象数量的整数(例如,联系人拥有的照片,故事等数量)。还有很多其他的,所以为了简洁,我只是展示了一些。

sort_by

当我运行查询以获取一堆联系人然后将其序列化以获取所有这些方法的值时,200个联系人只需要大约80毫秒。绝大多数放缓似乎发生在contacts区块。

在使用以下代码行迭代@contacts = Hash[contacts.map { |contact| [contact.id, ContactSerializer.new(contact)] }] 以构建自定义数据结构后,控制器方法的输出应如下所示:

{
    contacts: {
        79: {
            id: 79,
            first_name: "Foo",
            last_name: "Bar",
            email: "t@t.co",
            engagement: "0%",
            company_id: 94,
            created_at: " 9:41AM Jan 30, 2016",
            updated_at: "10:57AM Feb 23, 2016",
            published_response_count: 0,
            groups: {
                test: true,
                test23: false,
                Test222: false,
                Last: false
            },
            stories_requested: 1,
            stories_submitted: 0,
            stories_published: 0,
            amplify_requested: 1,
            amplify_completed: 1,
            photos_uploaded: 0,
            invites: [
                {
                    id: 112,
                    email: "t@t.co",
                    status: "Requested",
                    created_at: "Jan 30, 2016, 8:48 PM",
                    date_submitted: null,
                    response: null
                }
            ],
            responses: [ ],
            promotions: [
                {
                    id: 26,
                    company_id: 94,
                    key: "e5cb3bc80b58c29df8a61231d0",
                    updated_at: "Feb 11, 2016, 2:45 PM",
                    read: null,
                    social_media_posts: [ ]
                }
            ]
        }
    }
}

我已经对最后一行代码进行了基准测试,因此我知道它不是减速的主要来源。 More on that here.

<include />

1 个答案:

答案 0 :(得分:1)

if params[:order_by] == 'stories_submitted'
  contact_ids = company.contact_ids
  # count all invites that have the relevant contact ids
  invites=Invite.where(contact_id:contact_ids).group('contact_id').count
  invites_contact_ids = invites.map(&:first)
  # Add contacts with 0 invites
  contact_ids.each{|c| invites.push([c, 0]) unless invites_contact_ids.include?(c)}
  # Sort all invites by id (add .reverse to the end of this for sort DESC)
  contact_id_counts=invites.sort_by{|r| r.last}.map(&:first)      
  # The [0, 10] limits you to the lowest 10 results
  contacts=Contact.where(id: contact_id_counts[0, 10])
  contacts.sort_by!{|c| contact_id_counts.index(c.id)}
end