我正在编写一个控制器索引方法,它返回一个ActiveRecord Contact
对象的排序数组。我需要能够按属性或实例方法的输出对对象进行排序。例如,我需要能够按contact.email
和contact.photos_uploaded
进行排序,order
是一种返回联系人照片数量的实例方法。
我不能使用ActiveRecord的本机reorder
或array#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 />
答案 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