如何从Rails中的ActiveRecords数组中提取“表格”?

7
我有几个类似的模型(ContactEmail,ContactLetter,ContactPostalcard)。
例如,在ContactEmail中的每个实例(记录)表示已向特定联系人发送了特定的电子邮件模板。
因此,每个实例(例如ContactEmail)都属于一个联系人。
因此,在ContactEmail模型中,存在一个属性ContactEmail.contact_id。
每个联系人都有一个虚拟属性company_name。
因此,当我写下面的代码时,我将会得到在特定条件下(在本例中是时间)发送的所有电子邮件:
@sent_emails = ContactEmail.find(:all, :conditions => "conditions here")

因此,@sent_emails.size会告诉我发送的所有电子邮件的总数。 我的挑战:如何在不同的模型之间按唯一公司提取更多细节? 我想要的输出应该类似于以下内容:
FROM 8/1/10 TO 8/10/10 (where the dates are dynamic)

                Calls       Letter      Postalcards
Company 1         4           2             4
Company 2        10           4             6
Company 3         2           3             4

公司3有2个通话,这意味着在两个日期之间发送日期落在其中,并且关联的联系人属于公司3的ContactCalls记录有两条。
公司1-3没有提前设置。需要从ContactCalls、ContactLetters和ContactPostalcards池中抽取。
挑战在于我不知道这些公司是什么。它们是每个不同记录的联系人部分的属性。因此,在某些情况下,可能会出现Company2没有信件的情况。
谢谢您的任何指导! :)
如何在给定的ContactEmail模型记录中找到一个公司?
ContactEmail.contact.company.name

这将返回特定联系人电子邮件对应的公司。
4个回答

2
这应该可以很好地完成任务:
    list = Contact.find :all,
      :select => "companies.name company_name, COUNT(contact_emails.id) email_count, COUNT(contact_letters.id) letter_count, COUNT(contact_postalcards.id) postalcard_count",
      :joins => [
        "LEFT JOIN companies ON company.id = contact.company_id",
        "LEFT JOIN contact_emails ON contact_emails.contact_id = contacts.id",
        "LEFT JOIN contact_letters ON contact_letters.contact_id = contacts.id",
        "LEFT JOIN contact_postalcards ON contact_postalcards.contact_id = contacts.id"
      ],
      :group => "companies.id"

      list.each do |item|
        p item.company_name, item.email_count, item.letter_count, item.postalcard_count
      end

无法找到 company_name,因为它不是一个列 - 请参阅我所说的注释...必须通过 Contact.Company.Name 访问。 - Satchel
1
LEFT JOIN 和 LEFT OUTER JOIN 是一样的。 - aceofspades
根据问题的性质,我认为也许数据库架构设计时没有考虑需要支持哪些功能。我不确定为什么您想要一个虚拟属性,比如公司名称,而没有在数据库中进行映射。 - aceofspades
嗯...我该怎么改呢?原因是联系人属于公司,但公司拥有多个联系人。这就是它的设置方式...你有什么建议吗? - Satchel
有趣的是,列出属性非常好用:{"postalcard_count"=>3, "email_count"=>3, "letter_count"=>0, "company_name"=>"Typhoon Corporation"},但为什么访问item.company_name时会得到奇怪的输出? - Satchel
显示剩余24条评论

0
如果你真的想获得所有联系人以及所有这些关系计数的联系方式,使用counter_cache可能是个好主意。
在这个网页http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html上可以找到counter_cache。
它的作用是自动保持每个联系人关系的计数的反规范化跟踪,因此您不必查询所有表的信息并按联系人分组,这可能会导致性能问题。
您需要做的是将“emails_count”、“letters_count”和“postcards_count”列添加到您的“contacts”表中,最初更新正确的计数,并声明has_many关系如下:
class Contact < ActiveRecord::Base
  has_many :emails, :class_name => 'ContactEmail', :counter_cache => 'emails_count'
  # so on for other columns
end

有了这些设置,你所需要做的就是查询你的联系人,并检查他们的计数器缓存以渲染你的表格。每当一个项目被创建或销毁时,Rails都会自动更新它的数字。

如果我想引用ContactEmail模型中的其他信息,我不需要这样做,对吗?计数器缓存只是一个聚合数字? - Satchel
是的,没错。你仍然可以在每个联系人上访问他们的contact_emails,但需要进行额外的内部查询。 如果您有关于要在聚合列上显示哪种信息的详细信息,我们可能能够为您提供更多帮助。PS. 除非您想同时将所有4个表的内容加载到内存中,否则可能值得考虑一下这个问题。对于动态增长的表格,通常应避免这种想法。 - user155104
同意。那么,让我想想,我的问题现在显示了我想展示的内容,所以我认为计数器缓存可以工作...除了我需要能够按日期进行过滤之外。那个数字,因此,根据定义会动态变化。对吧? - Satchel
已经使用示例数据填充了相同的输出...但是总体结构仍然相同(按公司分组的ContactEmail记录总数(这些可能是针对同一联系人的)....谢谢! - Satchel

0
哦,所以我一开始误解了你的问题。我想我终于明白你的意思了。
的确,实现你想要的(即从所有这些表中提取所有数据)唯一好的方法是在查询联系人时发出包含命令:
@contacts = Contact.all :include => [:contact_emails, :contact_letters, :contact_postal_cards]

这将生成4个查询,选择这4个表中的所有数据并相应地填充您的联系人,因此您可以按照自己喜欢的方式浏览联系对象的关系以填充您的表格。

最后,您提到您的表格将按公司分组而不是联系人,因此我假设多个联系人可能具有相同的“company_name”。如果是这种情况,您可以像这样通过“company_name”对您的集合进行分组

@companies = @contacts.group_by &:company_name

这将创建一个像这样的哈希:
{'Company 1' => [contact_1, contact_3], 'Company 2' => [contact_4]}

有了这些,您可以使用类似以下代码生成您的表格:

<% @companies.each do |company_name, contacts| %>
    Company: <%= company_name %>
    <% contacts.each do |contact| %>
       Contact: <%= contact.name %>
          Emails : <%= contact.contact_emails.map(&:email).split(',') %>
          etc..
    <% end %>
<% end %>

嗨,谢谢——抱歉我有点迷失……如何使用.count方法?我一直在尝试使用statistics gem,它可以给我一个特定时间范围内ContactEmails的计数。关于与哈希表合作的好主意……ContactEmail属于Contact。上面提到的Contact是指特定的模型吗? - Satchel
“Contact” 是指特定的模型吗?- 是的,就像您使用“ContactEmail.all: conditions => {:contact_id => 1}”获取第一个联系人的所有电子邮件一样,“Contact.all”将返回数组中的所有联系人。然后,您可以使用“contacts.first.contact_emails”从中获取所有电子邮件。 - user155104
哦,我明白了...所以我可以用contact.contact_emails.count吗?实际上,你应该使用“contact.contact_emails.size”,因为当你第一次调用“Contact.all:include =>:contact_emails”时,你已经加载了所有的数据。这是我假设你需要更多的计数(根据你对我的其他答案的回复)。如果你真的只想要计数,那么我的另一个答案就是你想要的(计数器缓存)。 - user155104
我明白了...挑战在于能够按公司而不是特定联系人进行聚合。我可以使用ContactEmail.Contact.Company.Name来生成与任何特定ContactEmail相关联的公司名称...这有帮助吗? - Satchel
我原以为你说的“每个联系人都有一个虚拟属性company_name”是指通过“contact.company_name”而不是像你提到的“Contact.Company.Name”来获取公司名称。无论哪种方式,按公司聚合它们的方法是通过我上面提到的:@companies = @contacts.group_by(&:company_name)。这根本不应该是什么难题,你只需要通过“contacts”而不是“contact_emails”进行聚合即可。你不明白还是为什么对你不起作用? - user155104
显示剩余3条评论

0
在我看来,试图在一个查询中提取这些数据(我想你是想这样做)有点过度。我会为每种类型发出一个带有:group => company_name选项的查询。然后,我会使用Ruby代码合并结果,以便拥有一些数据结构,可以轻松显示(可能是哈希的哈希,例如{'Company 1' => {'Calls' => 5, 'Letter' => 0, 'Postalcards' => 3'}...})。

":group =>"选项是什么作用?这会给我组内的总数吗(我猜类似于SQL group,对吗?) - Satchel
当company_name是Contact的属性时,我该如何按company_name搜索,而每个ContactEmail都“属于”Contact?我能为每个ContactEmail、ContactLetter创建一个虚拟属性,也叫company_name吗? - Satchel
我该如何跨越所有模型(ContactEmail、ContactLetter等)进行查询? - Satchel
:group => company_name 在 SQL 中与其他答案中所提到的 :group => 'company_id' 是相同的。正如我所说,用一条查询完成这个任务有些过度设计,但你可以在 Fullware 的答案中看到相关代码。 - szeryf
我只想找到最简单的方法来做这件事... 顺便提一下,company_name是联系人中的虚拟属性,它具有belongs_to关系... - Satchel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接