1
# encoding: utf-8
2
#--
3
#   Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
4
#   Copyright (C) 2008 Johan Sørensen <johan@johansorensen.com>
5
#   Copyright (C) 2008 David A. Cuadrado <krawek@gmail.com>
6
#   Copyright (C) 2008 Tor Arne Vestbø <tavestbo@trolltech.com>
7
#
8
#   This program is free software: you can redistribute it and/or modify
9
#   it under the terms of the GNU Affero General Public License as published by
10
#   the Free Software Foundation, either version 3 of the License, or
11
#   (at your option) any later version.
12
#
13
#   This program is distributed in the hope that it will be useful,
14
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
15
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
#   GNU Affero General Public License for more details.
17
#
18
#   You should have received a copy of the GNU Affero General Public License
19
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
#++
21
22
class Comment < ActiveRecord::Base
23
  include Gitorious::Search
24
  
25
  belongs_to :user
26
  belongs_to :target, :polymorphic => true
27
  belongs_to :project
28
  has_many   :events, :as => :target, :dependent => :destroy
29
  after_create :notify_target_if_supported
30
  after_create :update_state_in_target
31
  serialize :state_change, Array
32
33
  is_indexed do |s|
34
    s.index :body
35
    s.index "user#login", :as => :commented_by
36
  end
37
38
  attr_protected :user_id
39
40
  validates_presence_of :user_id, :target, :project_id
41
  validates_presence_of :body, :if =>  Proc.new {|mr| mr.body_required?}
42
43
  named_scope :with_shas, proc{|*shas|
44
    {:conditions => { :sha1 => shas.flatten }, :include => :user}
45
  }
46
47
  NOTIFICATION_TARGETS = [ MergeRequest, MergeRequestVersion ]
48
49
  def deliver_notification_to(another_user)
50
    message_body = "#{user.title} commented:\n\n#{body}"
51
    if [MergeRequest, MergeRequestVersion].include?(target.class)
52
      if state_change
53
        message_body << "\n\nThe status of your merge request"
54
        message_body << " is now #{state_changed_to}"
55
      end
56
      subject_class_name = "merge request"
57
    else
58
      subject_class_name = target.class.human_name.downcase
59
    end
60
    message = Message.new({
61
      :sender => self.user,
62
      :recipient => another_user,
63
      :subject => "#{user.title} commented on your #{subject_class_name}",
64
      :body => message_body,
65
      :notifiable => self.target,
66
    })
67
    message.save
68
  end
69
70
  def state=(new_state)
71
    return if new_state.blank?
72
    result = []
73
    if applies_to_merge_request?
74
      return if target.status_tag.to_s == new_state
75
      result << (target.status_tag.nil? ? nil : target.status_tag.name)
76
    end
77
    result << new_state
78
    self.state_change = result
79
  end
80
81
  def state_changed_to
82
    state_change.to_a.last
83
  end
84
85
  def state_changed_from
86
    state_change.to_a.size > 1 ? state_change.first : nil
87
  end
88
89
  def body_required?
90
    if applies_to_merge_request?
91
      return state_change.blank?
92
    else
93
      return true
94
    end
95
  end
96
97
  # +lines_str+ is a representation of the first and last line-number
98
  # tuple (as generated by Diff::Unified::Generator) and the lines the
99
  # comment span, in the follow format:
100
  # first_tuple:last_tuple+line_span
101
  def lines=(lines_str)
102
    start, rest = lines_str.split(":")
103
    raise "invalid lines format" if rest.blank?
104
    last, amount = rest.split("+")
105
    if start.blank? || last.blank? || amount.blank?
106
      raise "invalid lines format"
107
    end
108
    self.first_line_number = start
109
    self.last_line_number = last
110
    self.number_of_lines = amount
111
  end
112
113
  def lines
114
    "#{self.first_line_number}:#{self.last_line_number}+#{self.number_of_lines}"
115
  end
116
117
  def sha_range
118
    first, last = sha1.split("-")
119
    first..(last||first)
120
  end
121
122
  def applies_to_line_numbers?
123
    return !first_line_number.blank?
124
  end
125
126
  def applies_to_merge_request?
127
    MergeRequest === target
128
  end
129
130
  def editable_by?(a_user)
131
    creator?(a_user) && recently_created?
132
  end
133
134
  def creator?(a_user)
135
    a_user == user
136
  end
137
138
  def recently_created?
139
    created_at > 10.minutes.ago
140
  end
141
142
  protected
143
    def notify_target_if_supported
144
      if target && NOTIFICATION_TARGETS.include?(target.class)
145
        if self.target === MergeRequestVersion
146
          target_user = target.merge_request.user
147
        else
148
          target_user = target.user
149
        end
150
        return if target_user == user
151
        deliver_notification_to(target_user)
152
      end
153
    end
154
155
    def update_state_in_target
156
      if applies_to_merge_request? and state_change
157
        target.with_user(user) do
158
          if target.resolvable_by?(user)
159
            target.status_tag=(state_changed_to)
160
            target.create_status_change_event(body)
161
          end
162
        end
163
      end
164
    end
165
166
end