views:

199

answers:

1

I have two models Ticket and TicketComment, the TicketComment is a child of Ticket.

ticket.rb

class Ticket < ActiveRecord::Base
  has_many :ticket_comments, :dependent => :destroy, :order => 'created_at DESC'

  # allow the ticket comments to be created from within a ticket form
  accepts_nested_attributes_for :ticket_comments, :reject_if => proc { |attributes| attributes['comment'].blank? }
end

ticket_comment.rb

class TicketComment < ActiveRecord::Base
  belongs_to :ticket

  validates_presence_of :comment
end

What I want to do is mimic the functionality in Trac, where if a user makes a change to the ticket, and/or adds a comment, an email is sent to the people assigned to the ticket.

I want to use an after_update or after_save callback, so that I know the information was all saved before I send out emails.

How can I detect changes to the model (ticket.changes) as well as whether a new comment was created or not (ticket.comments) and send this update (x changes to y, user added comment 'text') in ONE email in a callback method?

+2  A: 

Hi, you could use the ActiveRecord::Dirty module, which allows you to track unsaved changes.

E.g.

t1 = Ticket.first
t1.some_attribute = some_new_value
t1.changed? => true
t1.some_attribute_changed? => true
t1.some_attribute_was => old_value 

So inside a before_update of before_create you should those (you can only check before the save!).

A very nice place to gather all these methods is in a Observer-class TicketObserver, so you can seperate your "observer"-code from your actual model.

E.g.

class TicketObserver < ActiveRecord::Observer
  def before_update
    .. do some checking here ..
  end
end

to enable the observer-class, you need to add this in your environment.rb:

config.active_record.observers = :ticket_observer

This should get you started :)

What concerns the linked comments. If you do this:

new_comment = ticket.ticket_comments.build
new_comment.new_record? => true
ticket.comments.changed => true

So that would be exactly what you would need. Does that not work for you? Note again: you need to check this before saving, of course :)

I imagine that you have to collect the data that has changed in a before_create or before_update, and in an after_update/create actually send the mail (because then you are sure it succeeded).

Apparently it still is not clear. I will make it a bit more explicit. I would recommend using the TicketObserver class. But if you want to use the callback, it would be like this:

class Ticked

  before_save :check_state
  after_save :send_mail_if_needed

  def check_state
    @logmsg=""
    if ticket_comments.changed
      # find the comment
      ticket_comments.each do |c| 
        @logmsg << "comment changed" if c.changed?
        @logmsg << "comment added" if c.new_record? 
      end
    end
  end

end
def send_mail_if_needed
  if @logmsg.size > 0
    ..send mail..
  end
end
nathanvda
@nathanvda I already have the dirty code working, I understand that part. The difficult part here is determining if a TicketComment was created, which is a child object. If one was included I need to add its contents.. Unfortunately children like that dont appear in the 'changes' array, as its not a change (not dirty)
Rabbott
Hi, i extended my answer accordingly. Does it make more sense now?
nathanvda
Yes, I understand all of that.. I understand Dirty. Dirty doesnt help me though. When a form is submitted, in the controller I have a hash (params[:ticket]), a new ticket_comment was created, and is provided within that hash. The ticket object is saved. the ticket_comment is saved. Now, in the callback, I have SELF, how do i know that a comment was added, and how do i get to that comment?
Rabbott
Well, you have SELF. self.ticket_comments.changed will be true if something inside the collection changed; IF you check before saving. After a succesfull update you can then send the e-mail. I updated the answer to show this.
nathanvda