Как мне найти .max значения атрибута среди группы различных моделей?

все: Я также открыт для простого рефакторинга того, что я считаю довольно повторяющимся, но чтобы дать общее представление о том, как это работает ...

У меня есть для каждого контакта кампания, в которой есть множество моделей трех типов: электронная почта, звонок и письмо.

Когда электронное письмо (звонок или письмо) было выполнено для определенного контакта, у меня есть Contact_Email (_or_Call_or_Letter), который принадлежит как контакту, так и модели (Email_or_Call_or_Letter).

Например, каждая пара Contact_Email имеет атрибут: date_sent. То же самое происходит с каждым Contact_Call и Contact_Letter.

Как найти самые свежие из них?

Вот код, который я написал, который может найти последнюю электронную почту и мои находки, повторно набрав аналогичный код для Call и Letter, но затем застрял на том, как сделать .max для всех из них:

  def last_email(contact)
    #get campaign the contact belongs to
    @campaign = Campaign.find_by_id(contact.campaign_id)

    @last_email = ContactEmail.find(:last, 
                        :conditions => "contact_id = #{contact.id}",
                        :order => "date_sent DESC")

    @last_call = ContactCall.find(:last, 
                        :conditions => "contact_id = #{contact.id}",
                        :order => "date_sent DESC")

    @last_letter = ContactLetter.find(:last, 
                        :conditions => "contact_id = #{contact.id}",
                        :order => "date_sent DESC")

    # how do I get the latest of all of these to display?

    @email_template = Email.find_by_id(@last_email.email_id)

    if @last_email.nil?
      return "no email sent"
    else
      return @last_email.date_sent.to_s(:long) + link_to('email was sent', @email_template)
    end
  end

Вопрос 1. С тем, что у меня есть, как я могу эффективно найти @last_event, учитывая, что я могу найти последнее электронное письмо, последний звонок и последнее письмо для каждого контакта?

Вопрос 2. Как я могу удалить повторяющийся код, который мне приходится писать для каждой модели?


person Satchel    schedule 15.05.2010    source источник
comment
Я считаю, что вам действительно стоит подумать об использовании наследования одной таблицы. Это сделает вашу жизнь намного проще:]   -  person Ju Nogueira    schedule 16.05.2010


Ответы (2)


Есть ли у вас has_many настройки ассоциаций в Contact, относящиеся к другим моделям? Что-то типа:

class Contact < ActiveRecord::Base
  has_many :contact_emails
  has_many :contact_calls
  has_many :contact_letters
end

Если это так, вы можете создать last_event метод на модели Contact:

def latest_event
  [contact_emails, contact_calls, contact_letters].map do |assoc|
    assoc.first(:order => 'date_sent DESC')
  end.compact.sort_by { |e| e.date_sent }.last
end

Обработка nil

При использовании метода latest_event вы получите nil, если нет связанных записей. Есть несколько способов обойти это. Во-первых, сначала проверьте, нет ли nil, примерно так:

contact.latest_event && contact.latest_event.date_sent

В поздних версиях Rails / Ruby вы также можете использовать Object#try, который будет вызывать метод, если он существует:

contact.latest_event.try(:date_sent)

Я предпочитаю не использовать это, поскольку он не проверяет nil, а только если объект может ответить на метод. Это вызвало некоторые интересные ошибки, если вы ожидаете nil, если объект nil, но вызываете метод, на который nil сам отвечает.

Наконец, в простом случае я предпочитаю использовать гем andand, который предоставляет Object#andand. Это значительно сокращает безопасный случай, описанный выше, и избавляет от многократного вызова latest_event:

contact.latest_event.andand.date_sent

date_sent, nil и вы.

Для вашего примера использования вызова to_s(:long) вы можете использовать && или andand:

contact.latest_event.andand.date_sent.andand.to_s(:long)

or

contact.latest_event && contact.latest_event.date_sent.to_s(:long)

Первый вариант безопаснее, если date_sent сам может быть nil. Без использования andand это можно было бы записать как:

contact.latest_event &&
  contact.latest_event.date_sent &&
  contact.latest_event.date_sent.to_s(:long)

что, на мой взгляд, довольно сложно и громоздко. Я бы порекомендовал изучить andand

person Jason Weathered    schedule 16.05.2010
comment
это будет смотреть только на те contact_emails, contact_calls и contact_letters, которые были фактически созданы, верно? В таком случае не будет экземпляра класса nil, верно? Это то, что я получаю с другими подходами. Это интересно, забыл о методе .map ... но тоже так и не понял ... проверяю! - person Satchel; 16.05.2010
comment
Итак, я мог бы просто вызвать Contact.latest_event, чтобы получить фактическое событие? Значит, атрибуты могут применяться к Contact.latest_event.date_sent? - person Satchel; 16.05.2010
comment
Привет, хорошо, у меня может быть ситуация, когда может быть НЕТ contact_emails, contact_calls, contact_letters, поэтому date_sent применяется к классу Nil ... как я могу исправить это? Спасибо! - person Satchel; 17.05.2010
comment
Привет, Анджела. Я добавил еще одну секунду к своему ответу, в котором рассказывается, как обращаться с nil. - person Jason Weathered; 17.05.2010

На вопрос 1:

Просто сделать

@last_event = [@last_letter, @last_email, @last_call].sort_by{|m| m.date_sent}.first

На вопрос 2:

Ну это поинтереснее. Это зависит от того, как именно выглядят ваши модели, но вы можете рассмотреть возможность наследования одной таблицы для этого типа сценария.

person Jakub Hampl    schedule 15.05.2010
comment
что произойдет, если один из этих экземпляров (например, @last_letter) не имеет значения, потому что ни одно письмо не было отправлено? - person Satchel; 16.05.2010