Revision 1297:0a574315af3e .svn/pristine/a7

View differences:

.svn/pristine/a7/a71b998ee631f5527b99e3a0df2d62bbccac91fe.svn-base
1
# Redmine - project management software
2
# Copyright (C) 2006-2012  Jean-Philippe Lang
3
# Copyright (C) 2007  Patrick Aljord patcito@ŋmail.com
4
#
5
# This program is free software; you can redistribute it and/or
6
# modify it under the terms of the GNU General Public License
7
# as published by the Free Software Foundation; either version 2
8
# of the License, or (at your option) any later version.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18

  
19
require 'redmine/scm/adapters/git_adapter'
20

  
21
class Repository::Git < Repository
22
  attr_protected :root_url
23
  validates_presence_of :url
24

  
25
  def self.human_attribute_name(attribute_key_name, *args)
26
    attr_name = attribute_key_name.to_s
27
    if attr_name == "url"
28
      attr_name = "path_to_repository"
29
    end
30
    super(attr_name, *args)
31
  end
32

  
33
  def self.scm_adapter_class
34
    Redmine::Scm::Adapters::GitAdapter
35
  end
36

  
37
  def self.scm_name
38
    'Git'
39
  end
40

  
41
  def report_last_commit
42
    extra_report_last_commit
43
  end
44

  
45
  def extra_report_last_commit
46
    return false if extra_info.nil?
47
    v = extra_info["extra_report_last_commit"]
48
    return false if v.nil?
49
    v.to_s != '0'
50
  end
51

  
52
  def supports_directory_revisions?
53
    true
54
  end
55

  
56
  def supports_revision_graph?
57
    true
58
  end
59

  
60
  def repo_log_encoding
61
    'UTF-8'
62
  end
63

  
64
  # Returns the identifier for the given git changeset
65
  def self.changeset_identifier(changeset)
66
    changeset.scmid
67
  end
68

  
69
  # Returns the readable identifier for the given git changeset
70
  def self.format_changeset_identifier(changeset)
71
    changeset.revision[0, 8]
72
  end
73

  
74
  def branches
75
    scm.branches
76
  end
77

  
78
  def tags
79
    scm.tags
80
  end
81

  
82
  def default_branch
83
    scm.default_branch
84
  rescue Exception => e
85
    logger.error "git: error during get default branch: #{e.message}"
86
    nil
87
  end
88

  
89
  def find_changeset_by_name(name)
90
    if name.present?
91
      changesets.where(:revision => name.to_s).first ||
92
        changesets.where('scmid LIKE ?', "#{name}%").first
93
    end
94
  end
95

  
96
  def entries(path=nil, identifier=nil)
97
    entries = scm.entries(path, identifier, :report_last_commit => extra_report_last_commit)
98
    load_entries_changesets(entries)
99
    entries
100
  end
101

  
102
  # With SCMs that have a sequential commit numbering,
103
  # such as Subversion and Mercurial,
104
  # Redmine is able to be clever and only fetch changesets
105
  # going forward from the most recent one it knows about.
106
  #
107
  # However, Git does not have a sequential commit numbering.
108
  #
109
  # In order to fetch only new adding revisions,
110
  # Redmine needs to save "heads".
111
  #
112
  # In Git and Mercurial, revisions are not in date order.
113
  # Redmine Mercurial fixed issues.
114
  #    * Redmine Takes Too Long On Large Mercurial Repository
115
  #      http://www.redmine.org/issues/3449
116
  #    * Sorting for changesets might go wrong on Mercurial repos
117
  #      http://www.redmine.org/issues/3567
118
  #
119
  # Database revision column is text, so Redmine can not sort by revision.
120
  # Mercurial has revision number, and revision number guarantees revision order.
121
  # Redmine Mercurial model stored revisions ordered by database id to database.
122
  # So, Redmine Mercurial model can use correct ordering revisions.
123
  #
124
  # Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10"
125
  # to get limited revisions from old to new.
126
  # But, Git 1.7.3.4 does not support --reverse with -n or --skip.
127
  #
128
  # The repository can still be fully reloaded by calling #clear_changesets
129
  # before fetching changesets (eg. for offline resync)
130
  def fetch_changesets
131
    scm_brs = branches
132
    return if scm_brs.nil? || scm_brs.empty?
133

  
134
    h1 = extra_info || {}
135
    h  = h1.dup
136
    repo_heads = scm_brs.map{ |br| br.scmid }
137
    h["heads"] ||= []
138
    prev_db_heads = h["heads"].dup
139
    if prev_db_heads.empty?
140
      prev_db_heads += heads_from_branches_hash
141
    end
142
    return if prev_db_heads.sort == repo_heads.sort
143

  
144
    h["db_consistent"]  ||= {}
145
    if changesets.count == 0
146
      h["db_consistent"]["ordering"] = 1
147
      merge_extra_info(h)
148
      self.save
149
    elsif ! h["db_consistent"].has_key?("ordering")
150
      h["db_consistent"]["ordering"] = 0
151
      merge_extra_info(h)
152
      self.save
153
    end
154
    save_revisions(prev_db_heads, repo_heads)
155
  end
156

  
157
  def save_revisions(prev_db_heads, repo_heads)
158
    h = {}
159
    opts = {}
160
    opts[:reverse]  = true
161
    opts[:excludes] = prev_db_heads
162
    opts[:includes] = repo_heads
163

  
164
    revisions = scm.revisions('', nil, nil, opts)
165
    return if revisions.blank?
166

  
167
    # Make the search for existing revisions in the database in a more sufficient manner
168
    #
169
    # Git branch is the reference to the specific revision.
170
    # Git can *delete* remote branch and *re-push* branch.
171
    #
172
    #  $ git push remote :branch
173
    #  $ git push remote branch
174
    #
175
    # After deleting branch, revisions remain in repository until "git gc".
176
    # On git 1.7.2.3, default pruning date is 2 weeks.
177
    # So, "git log --not deleted_branch_head_revision" return code is 0.
178
    #
179
    # After re-pushing branch, "git log" returns revisions which are saved in database.
180
    # So, Redmine needs to scan revisions and database every time.
181
    #
182
    # This is replacing the one-after-one queries.
183
    # Find all revisions, that are in the database, and then remove them from the revision array.
184
    # Then later we won't need any conditions for db existence.
185
    # Query for several revisions at once, and remove them from the revisions array, if they are there.
186
    # Do this in chunks, to avoid eventual memory problems (in case of tens of thousands of commits).
187
    # If there are no revisions (because the original code's algorithm filtered them),
188
    # then this part will be stepped over.
189
    # We make queries, just if there is any revision.
190
    limit = 100
191
    offset = 0
192
    revisions_copy = revisions.clone # revisions will change
193
    while offset < revisions_copy.size
194
      recent_changesets_slice = changesets.find(
195
                                     :all,
196
                                     :conditions => [
197
                                        'scmid IN (?)',
198
                                        revisions_copy.slice(offset, limit).map{|x| x.scmid}
199
                                      ]
200
                                    )
201
      # Subtract revisions that redmine already knows about
202
      recent_revisions = recent_changesets_slice.map{|c| c.scmid}
203
      revisions.reject!{|r| recent_revisions.include?(r.scmid)}
204
      offset += limit
205
    end
206

  
207
    revisions.each do |rev|
208
      transaction do
209
        # There is no search in the db for this revision, because above we ensured,
210
        # that it's not in the db.
211
        save_revision(rev)
212
      end
213
    end
214
    h["heads"] = repo_heads.dup
215
    merge_extra_info(h)
216
    self.save
217
  end
218
  private :save_revisions
219

  
220
  def save_revision(rev)
221
    parents = (rev.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact
222
    changeset = Changeset.create(
223
              :repository   => self,
224
              :revision     => rev.identifier,
225
              :scmid        => rev.scmid,
226
              :committer    => rev.author,
227
              :committed_on => rev.time,
228
              :comments     => rev.message,
229
              :parents      => parents
230
              )
231
    unless changeset.new_record?
232
      rev.paths.each { |change| changeset.create_change(change) }
233
    end
234
    changeset
235
  end
236
  private :save_revision
237

  
238
  def heads_from_branches_hash
239
    h1 = extra_info || {}
240
    h  = h1.dup
241
    h["branches"] ||= {}
242
    h['branches'].map{|br, hs| hs['last_scmid']}
243
  end
244

  
245
  def latest_changesets(path,rev,limit=10)
246
    revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
247
    return [] if revisions.nil? || revisions.empty?
248

  
249
    changesets.find(
250
      :all,
251
      :conditions => [
252
        "scmid IN (?)",
253
        revisions.map!{|c| c.scmid}
254
      ],
255
      :order => 'committed_on DESC'
256
    )
257
  end
258

  
259
  def clear_extra_info_of_changesets
260
    return if extra_info.nil?
261
    v = extra_info["extra_report_last_commit"]
262
    write_attribute(:extra_info, nil)
263
    h = {}
264
    h["extra_report_last_commit"] = v
265
    merge_extra_info(h)
266
    self.save
267
  end
268
  private :clear_extra_info_of_changesets
269
end
.svn/pristine/a7/a721c0d1c7040afbf54df15fe3439fbbed48e0c9.svn-base
1
# Redmine - project management software
2
# Copyright (C) 2006-2012  Jean-Philippe Lang
3
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

  
18
require File.expand_path('../../../test_helper', __FILE__)
19

  
20
class RoutingMyTest < ActionController::IntegrationTest
21
  def test_my
22
    ["get", "post"].each do |method|
23
      assert_routing(
24
          { :method => method, :path => "/my/account" },
25
          { :controller => 'my', :action => 'account' }
26
        )
27
    end
28
    ["get", "post"].each do |method|
29
      assert_routing(
30
          { :method => method, :path => "/my/account/destroy" },
31
          { :controller => 'my', :action => 'destroy' }
32
        )
33
    end
34
    assert_routing(
35
        { :method => 'get', :path => "/my/page" },
36
        { :controller => 'my', :action => 'page' }
37
      )
38
    assert_routing(
39
        { :method => 'get', :path => "/my" },
40
        { :controller => 'my', :action => 'index' }
41
      )
42
    assert_routing(
43
        { :method => 'post', :path => "/my/reset_rss_key" },
44
        { :controller => 'my', :action => 'reset_rss_key' }
45
      )
46
    assert_routing(
47
        { :method => 'post', :path => "/my/reset_api_key" },
48
        { :controller => 'my', :action => 'reset_api_key' }
49
      )
50
    ["get", "post"].each do |method|
51
      assert_routing(
52
          { :method => method, :path => "/my/password" },
53
          { :controller => 'my', :action => 'password' }
54
        )
55
    end
56
    assert_routing(
57
        { :method => 'get', :path => "/my/page_layout" },
58
        { :controller => 'my', :action => 'page_layout' }
59
      )
60
    assert_routing(
61
        { :method => 'post', :path => "/my/add_block" },
62
        { :controller => 'my', :action => 'add_block' }
63
      )
64
    assert_routing(
65
        { :method => 'post', :path => "/my/remove_block" },
66
        { :controller => 'my', :action => 'remove_block' }
67
      )
68
    assert_routing(
69
        { :method => 'post', :path => "/my/order_blocks" },
70
        { :controller => 'my', :action => 'order_blocks' }
71
      )
72
  end
73
end
.svn/pristine/a7/a767dc28cd65059893b07124ffbba8876e4ee4ce.svn-base
1
Return-Path: <jsmith@somenet.foo>
2
Received: from osiris ([127.0.0.1])
3
	by OSIRIS
4
	with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
5
Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
6
From: "John Smith" <jsmith@somenet.foo>
7
To: <redmine@somenet.foo>
8
Subject: New ticket on a given project
9
Date: Sun, 22 Jun 2008 12:28:07 +0200
10
MIME-Version: 1.0
11
Content-Type: text/plain;
12
	format=flowed;
13
	charset="iso-8859-1";
14
	reply-type=original
15
Content-Transfer-Encoding: 7bit
16
X-Priority: 3
17
X-MSMail-Priority: Normal
18
X-Mailer: Microsoft Outlook Express 6.00.2900.2869
19
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
20

  
21
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet 
22
turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus 
23
blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti 
24
sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In 
25
in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras 
26
sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum 
27
id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus 
28
eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique 
29
sed, mauris. Pellentesque habitant morbi tristique senectus et netus et 
30
malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse 
31
platea dictumst.
32

  
33
Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque 
34
sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem. 
35
Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et, 
36
dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed, 
37
massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo 
38
pulvinar dui, a gravida orci mi eget odio. Nunc a lacus.
39

  
40
Project: onlinestore
41
Tracker: Feature request
42
category: Stock management
43
assigned to: miscuser9@foo.bar
44
priority: foo
45
done ratio: x
46
start date: some day 
47
due date: never
.svn/pristine/a7/a7836f4a070a9a0d96511cf7847d79f9fcfd69e4.svn-base
1
# Redmine - project management software
2
# Copyright (C) 2006-2012  Jean-Philippe Lang
3
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

  
18
class MailHandlerController < ActionController::Base
19
  before_filter :check_credential
20

  
21
  # Submits an incoming email to MailHandler
22
  def index
23
    options = params.dup
24
    email = options.delete(:email)
25
    if MailHandler.receive(email, options)
26
      render :nothing => true, :status => :created
27
    else
28
      render :nothing => true, :status => :unprocessable_entity
29
    end
30
  end
31

  
32
  private
33

  
34
  def check_credential
35
    User.current = nil
36
    unless Setting.mail_handler_api_enabled? && params[:key].to_s == Setting.mail_handler_api_key
37
      render :text => 'Access denied. Incoming emails WS is disabled or key is invalid.', :status => 403
38
    end
39
  end
40
end
.svn/pristine/a7/a7add1443588b3af2ef99c91219368b928dd34d0.svn-base
1
# Redmine - project management software
2
# Copyright (C) 2006-2012  Jean-Philippe Lang
3
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

  
18
module Redmine
19
  module Helpers
20
    class TimeReport
21
      attr_reader :criteria, :columns, :from, :to, :hours, :total_hours, :periods
22

  
23
      def initialize(project, issue, criteria, columns, from, to)
24
        @project = project
25
        @issue = issue
26

  
27
        @criteria = criteria || []
28
        @criteria = @criteria.select{|criteria| available_criteria.has_key? criteria}
29
        @criteria.uniq!
30
        @criteria = @criteria[0,3]
31

  
32
        @columns = (columns && %w(year month week day).include?(columns)) ? columns : 'month'
33
        @from = from
34
        @to = to
35

  
36
        run
37
      end
38

  
39
      def available_criteria
40
        @available_criteria || load_available_criteria
41
      end
42

  
43
      private
44

  
45
      def run
46
        unless @criteria.empty?
47
          scope = TimeEntry.visible.spent_between(@from, @to)
48
          if @issue
49
            scope = scope.on_issue(@issue)
50
          elsif @project
51
            scope = scope.on_project(@project, Setting.display_subprojects_issues?)
52
          end
53
          time_columns = %w(tyear tmonth tweek spent_on)
54
          @hours = []
55
          scope.sum(:hours, :include => :issue, :group => @criteria.collect{|criteria| @available_criteria[criteria][:sql]} + time_columns).each do |hash, hours|
56
            h = {'hours' => hours}
57
            (@criteria + time_columns).each_with_index do |name, i|
58
              h[name] = hash[i]
59
            end
60
            @hours << h
61
          end
62
          
63
          @hours.each do |row|
64
            case @columns
65
            when 'year'
66
              row['year'] = row['tyear']
67
            when 'month'
68
              row['month'] = "#{row['tyear']}-#{row['tmonth']}"
69
            when 'week'
70
              row['week'] = "#{row['tyear']}-#{row['tweek']}"
71
            when 'day'
72
              row['day'] = "#{row['spent_on']}"
73
            end
74
          end
75
          
76
          if @from.nil?
77
            min = @hours.collect {|row| row['spent_on']}.min
78
            @from = min ? min.to_date : Date.today
79
          end
80

  
81
          if @to.nil?
82
            max = @hours.collect {|row| row['spent_on']}.max
83
            @to = max ? max.to_date : Date.today
84
          end
85
          
86
          @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f}
87

  
88
          @periods = []
89
          # Date#at_beginning_of_ not supported in Rails 1.2.x
90
          date_from = @from.to_time
91
          # 100 columns max
92
          while date_from <= @to.to_time && @periods.length < 100
93
            case @columns
94
            when 'year'
95
              @periods << "#{date_from.year}"
96
              date_from = (date_from + 1.year).at_beginning_of_year
97
            when 'month'
98
              @periods << "#{date_from.year}-#{date_from.month}"
99
              date_from = (date_from + 1.month).at_beginning_of_month
100
            when 'week'
101
              @periods << "#{date_from.year}-#{date_from.to_date.cweek}"
102
              date_from = (date_from + 7.day).at_beginning_of_week
103
            when 'day'
104
              @periods << "#{date_from.to_date}"
105
              date_from = date_from + 1.day
106
            end
107
          end
108
        end
109
      end
110

  
111
      def load_available_criteria
112
        @available_criteria = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
113
                                              :klass => Project,
114
                                              :label => :label_project},
115
                                 'status' => {:sql => "#{Issue.table_name}.status_id",
116
                                              :klass => IssueStatus,
117
                                              :label => :field_status},
118
                                 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
119
                                              :klass => Version,
120
                                              :label => :label_version},
121
                                 'category' => {:sql => "#{Issue.table_name}.category_id",
122
                                                :klass => IssueCategory,
123
                                                :label => :field_category},
124
                                 'member' => {:sql => "#{TimeEntry.table_name}.user_id",
125
                                             :klass => User,
126
                                             :label => :label_member},
127
                                 'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
128
                                              :klass => Tracker,
129
                                              :label => :label_tracker},
130
                                 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
131
                                               :klass => TimeEntryActivity,
132
                                               :label => :label_activity},
133
                                 'issue' => {:sql => "#{TimeEntry.table_name}.issue_id",
134
                                             :klass => Issue,
135
                                             :label => :label_issue}
136
                               }
137

  
138
        # Add list and boolean custom fields as available criteria
139
        custom_fields = (@project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields)
140
        custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
141
          @available_criteria["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = #{Issue.table_name}.id ORDER BY c.value LIMIT 1)",
142
                                                 :format => cf.field_format,
143
                                                 :label => cf.name}
144
        end if @project
145

  
146
        # Add list and boolean time entry custom fields
147
        TimeEntryCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
148
          @available_criteria["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'TimeEntry' AND c.customized_id = #{TimeEntry.table_name}.id ORDER BY c.value LIMIT 1)",
149
                                                 :format => cf.field_format,
150
                                                 :label => cf.name}
151
        end
152

  
153
        # Add list and boolean time entry activity custom fields
154
        TimeEntryActivityCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
155
          @available_criteria["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Enumeration' AND c.customized_id = #{TimeEntry.table_name}.activity_id ORDER BY c.value LIMIT 1)",
156
                                                 :format => cf.field_format,
157
                                                 :label => cf.name}
158
        end
159

  
160
        @available_criteria
161
      end
162
    end
163
  end
164
end
.svn/pristine/a7/a7cc7a3537df3ded519cd03f022f755c7e4d5803.svn-base
1
class InsertBuiltinRoles < ActiveRecord::Migration
2
  def self.up
3
    Role.reset_column_information
4
    nonmember = Role.new(:name => 'Non member', :position => 0)
5
    nonmember.builtin = Role::BUILTIN_NON_MEMBER
6
    nonmember.save
7

  
8
    anonymous = Role.new(:name => 'Anonymous', :position => 0)
9
    anonymous.builtin = Role::BUILTIN_ANONYMOUS
10
    anonymous.save
11
  end
12

  
13
  def self.down
14
    Role.destroy_all 'builtin <> 0'
15
  end
16
end
.svn/pristine/a7/a7d2f426cb2319278bd482b53b5c849fd8c928c2.svn-base
1
require File.dirname(__FILE__) + '/test_helper'
2

  
3
class Note < ActiveRecord::Base
4
  acts_as_nested_set :scope => [:notable_id, :notable_type]
5
end
6

  
7
class AwesomeNestedSetTest < Test::Unit::TestCase
8

  
9
  class Default < ActiveRecord::Base
10
    acts_as_nested_set
11
    self.table_name = 'categories'
12
  end
13
  class Scoped < ActiveRecord::Base
14
    acts_as_nested_set :scope => :organization
15
    self.table_name = 'categories'
16
  end
17

  
18
  def test_left_column_default
19
    assert_equal 'lft', Default.acts_as_nested_set_options[:left_column]
20
  end
21

  
22
  def test_right_column_default
23
    assert_equal 'rgt', Default.acts_as_nested_set_options[:right_column]
24
  end
25

  
26
  def test_parent_column_default
27
    assert_equal 'parent_id', Default.acts_as_nested_set_options[:parent_column]
28
  end
29

  
30
  def test_scope_default
31
    assert_nil Default.acts_as_nested_set_options[:scope]
32
  end
33
  
34
  def test_left_column_name
35
    assert_equal 'lft', Default.left_column_name
36
    assert_equal 'lft', Default.new.left_column_name
37
  end
38

  
39
  def test_right_column_name
40
    assert_equal 'rgt', Default.right_column_name
41
    assert_equal 'rgt', Default.new.right_column_name
42
  end
43

  
44
  def test_parent_column_name
45
    assert_equal 'parent_id', Default.parent_column_name
46
    assert_equal 'parent_id', Default.new.parent_column_name
47
  end
48
  
49
  def test_quoted_left_column_name
50
    quoted = Default.connection.quote_column_name('lft')
51
    assert_equal quoted, Default.quoted_left_column_name
52
    assert_equal quoted, Default.new.quoted_left_column_name
53
  end
54

  
55
  def test_quoted_right_column_name
56
    quoted = Default.connection.quote_column_name('rgt')
57
    assert_equal quoted, Default.quoted_right_column_name
58
    assert_equal quoted, Default.new.quoted_right_column_name
59
  end
60

  
61
  def test_left_column_protected_from_assignment
62
    assert_raises(ActiveRecord::ActiveRecordError) { Category.new.lft = 1 }
63
  end
64
  
65
  def test_right_column_protected_from_assignment
66
    assert_raises(ActiveRecord::ActiveRecordError) { Category.new.rgt = 1 }
67
  end
68
  
69
  def test_parent_column_protected_from_assignment
70
    assert_raises(ActiveRecord::ActiveRecordError) { Category.new.parent_id = 1 }
71
  end
72
  
73
  def test_colums_protected_on_initialize
74
    c = Category.new(:lft => 1, :rgt => 2, :parent_id => 3)
75
    assert_nil c.lft
76
    assert_nil c.rgt
77
    assert_nil c.parent_id
78
  end
79
  
80
  def test_scoped_appends_id
81
    assert_equal :organization_id, Scoped.acts_as_nested_set_options[:scope]
82
  end
83
  
84
  def test_roots_class_method
85
    assert_equal Category.find_all_by_parent_id(nil), Category.roots
86
  end
87
    
88
  def test_root_class_method
89
    assert_equal categories(:top_level), Category.root
90
  end
91
  
92
  def test_root
93
    assert_equal categories(:top_level), categories(:child_3).root
94
  end
95
  
96
  def test_root?
97
    assert categories(:top_level).root?
98
    assert categories(:top_level_2).root?
99
  end
100
  
101
  def test_leaves_class_method
102
    assert_equal Category.find(:all, :conditions => "#{Category.right_column_name} - #{Category.left_column_name} = 1"), Category.leaves
103
    assert_equal Category.leaves.count, 4
104
    assert (Category.leaves.include? categories(:child_1))
105
    assert (Category.leaves.include? categories(:child_2_1))
106
    assert (Category.leaves.include? categories(:child_3))
107
    assert (Category.leaves.include? categories(:top_level_2))
108
  end
109
  
110
  def test_leaf
111
    assert categories(:child_1).leaf?
112
    assert categories(:child_2_1).leaf?
113
    assert categories(:child_3).leaf?
114
    assert categories(:top_level_2).leaf?
115
    
116
    assert !categories(:top_level).leaf?
117
    assert !categories(:child_2).leaf?
118
  end
119
    
120
  def test_parent
121
    assert_equal categories(:child_2), categories(:child_2_1).parent
122
  end
123
  
124
  def test_self_and_ancestors
125
    child = categories(:child_2_1)
126
    self_and_ancestors = [categories(:top_level), categories(:child_2), child]
127
    assert_equal self_and_ancestors, child.self_and_ancestors
128
  end
129

  
130
  def test_ancestors
131
    child = categories(:child_2_1)
132
    ancestors = [categories(:top_level), categories(:child_2)]
133
    assert_equal ancestors, child.ancestors
134
  end
135
  
136
  def test_self_and_siblings
137
    child = categories(:child_2)
138
    self_and_siblings = [categories(:child_1), child, categories(:child_3)]
139
    assert_equal self_and_siblings, child.self_and_siblings
140
    assert_nothing_raised do
141
      tops = [categories(:top_level), categories(:top_level_2)]
142
      assert_equal tops, categories(:top_level).self_and_siblings
143
    end
144
  end
145

  
146
  def test_siblings
147
    child = categories(:child_2)
148
    siblings = [categories(:child_1), categories(:child_3)]
149
    assert_equal siblings, child.siblings
150
  end
151
  
152
  def test_leaves
153
    leaves = [categories(:child_1), categories(:child_2_1), categories(:child_3), categories(:top_level_2)]
154
    assert categories(:top_level).leaves, leaves
155
  end
156
  
157
  def test_level
158
    assert_equal 0, categories(:top_level).level
159
    assert_equal 1, categories(:child_1).level
160
    assert_equal 2, categories(:child_2_1).level
161
  end
162
  
163
  def test_has_children?
164
    assert categories(:child_2_1).children.empty?
165
    assert !categories(:child_2).children.empty?
166
    assert !categories(:top_level).children.empty?
167
  end
168
  
169
  def test_self_and_descendents
170
    parent = categories(:top_level)
171
    self_and_descendants = [parent, categories(:child_1), categories(:child_2),
172
      categories(:child_2_1), categories(:child_3)]
173
    assert_equal self_and_descendants, parent.self_and_descendants
174
    assert_equal self_and_descendants, parent.self_and_descendants.count
175
  end
176
  
177
  def test_descendents
178
    lawyers = Category.create!(:name => "lawyers")
179
    us = Category.create!(:name => "United States")
180
    us.move_to_child_of(lawyers)
181
    patent = Category.create!(:name => "Patent Law")
182
    patent.move_to_child_of(us)
183
    lawyers.reload
184

  
185
    assert_equal 1, lawyers.children.size
186
    assert_equal 1, us.children.size
187
    assert_equal 2, lawyers.descendants.size
188
  end
189
  
190
  def test_self_and_descendents
191
    parent = categories(:top_level)
192
    descendants = [categories(:child_1), categories(:child_2),
193
      categories(:child_2_1), categories(:child_3)]
194
    assert_equal descendants, parent.descendants
195
  end
196
  
197
  def test_children
198
    category = categories(:top_level)
199
    category.children.each {|c| assert_equal category.id, c.parent_id }
200
  end
201
  
202
  def test_is_or_is_ancestor_of?
203
    assert categories(:top_level).is_or_is_ancestor_of?(categories(:child_1))
204
    assert categories(:top_level).is_or_is_ancestor_of?(categories(:child_2_1))
205
    assert categories(:child_2).is_or_is_ancestor_of?(categories(:child_2_1))
206
    assert !categories(:child_2_1).is_or_is_ancestor_of?(categories(:child_2))
207
    assert !categories(:child_1).is_or_is_ancestor_of?(categories(:child_2))
208
    assert categories(:child_1).is_or_is_ancestor_of?(categories(:child_1))
209
  end
210
  
211
  def test_is_ancestor_of?
212
    assert categories(:top_level).is_ancestor_of?(categories(:child_1))
213
    assert categories(:top_level).is_ancestor_of?(categories(:child_2_1))
214
    assert categories(:child_2).is_ancestor_of?(categories(:child_2_1))
215
    assert !categories(:child_2_1).is_ancestor_of?(categories(:child_2))
216
    assert !categories(:child_1).is_ancestor_of?(categories(:child_2))
217
    assert !categories(:child_1).is_ancestor_of?(categories(:child_1))
218
  end
219

  
220
  def test_is_or_is_ancestor_of_with_scope
221
    root = Scoped.root
222
    child = root.children.first
223
    assert root.is_or_is_ancestor_of?(child)
224
    child.update_attribute :organization_id, 'different'
225
    assert !root.is_or_is_ancestor_of?(child)
226
  end
227

  
228
  def test_is_or_is_descendant_of?
229
    assert categories(:child_1).is_or_is_descendant_of?(categories(:top_level))
230
    assert categories(:child_2_1).is_or_is_descendant_of?(categories(:top_level))
231
    assert categories(:child_2_1).is_or_is_descendant_of?(categories(:child_2))
232
    assert !categories(:child_2).is_or_is_descendant_of?(categories(:child_2_1))
233
    assert !categories(:child_2).is_or_is_descendant_of?(categories(:child_1))
234
    assert categories(:child_1).is_or_is_descendant_of?(categories(:child_1))
235
  end
236
  
237
  def test_is_descendant_of?
238
    assert categories(:child_1).is_descendant_of?(categories(:top_level))
239
    assert categories(:child_2_1).is_descendant_of?(categories(:top_level))
240
    assert categories(:child_2_1).is_descendant_of?(categories(:child_2))
241
    assert !categories(:child_2).is_descendant_of?(categories(:child_2_1))
242
    assert !categories(:child_2).is_descendant_of?(categories(:child_1))
243
    assert !categories(:child_1).is_descendant_of?(categories(:child_1))
244
  end
245
  
246
  def test_is_or_is_descendant_of_with_scope
247
    root = Scoped.root
248
    child = root.children.first
249
    assert child.is_or_is_descendant_of?(root)
250
    child.update_attribute :organization_id, 'different'
251
    assert !child.is_or_is_descendant_of?(root)
252
  end
253
  
254
  def test_same_scope?
255
    root = Scoped.root
256
    child = root.children.first
257
    assert child.same_scope?(root)
258
    child.update_attribute :organization_id, 'different'
259
    assert !child.same_scope?(root)
260
  end
261
  
262
  def test_left_sibling
263
    assert_equal categories(:child_1), categories(:child_2).left_sibling
264
    assert_equal categories(:child_2), categories(:child_3).left_sibling
265
  end
266

  
267
  def test_left_sibling_of_root
268
    assert_nil categories(:top_level).left_sibling
269
  end
270

  
271
  def test_left_sibling_without_siblings
272
    assert_nil categories(:child_2_1).left_sibling
273
  end
274

  
275
  def test_left_sibling_of_leftmost_node
276
    assert_nil categories(:child_1).left_sibling
277
  end
278

  
279
  def test_right_sibling
280
    assert_equal categories(:child_3), categories(:child_2).right_sibling
281
    assert_equal categories(:child_2), categories(:child_1).right_sibling
282
  end
283

  
284
  def test_right_sibling_of_root
285
    assert_equal categories(:top_level_2), categories(:top_level).right_sibling
286
    assert_nil categories(:top_level_2).right_sibling
287
  end
288

  
289
  def test_right_sibling_without_siblings
290
    assert_nil categories(:child_2_1).right_sibling
291
  end
292

  
293
  def test_right_sibling_of_rightmost_node
294
    assert_nil categories(:child_3).right_sibling
295
  end
296
  
297
  def test_move_left
298
    categories(:child_2).move_left
299
    assert_nil categories(:child_2).left_sibling
300
    assert_equal categories(:child_1), categories(:child_2).right_sibling
301
    assert Category.valid?
302
  end
303

  
304
  def test_move_right
305
    categories(:child_2).move_right
306
    assert_nil categories(:child_2).right_sibling
307
    assert_equal categories(:child_3), categories(:child_2).left_sibling
308
    assert Category.valid?
309
  end
310

  
311
  def test_move_to_left_of
312
    categories(:child_3).move_to_left_of(categories(:child_1))
313
    assert_nil categories(:child_3).left_sibling
314
    assert_equal categories(:child_1), categories(:child_3).right_sibling
315
    assert Category.valid?
316
  end
317

  
318
  def test_move_to_right_of
319
    categories(:child_1).move_to_right_of(categories(:child_3))
320
    assert_nil categories(:child_1).right_sibling
321
    assert_equal categories(:child_3), categories(:child_1).left_sibling
322
    assert Category.valid?
323
  end
324
  
325
  def test_move_to_root
326
    categories(:child_2).move_to_root
327
    assert_nil categories(:child_2).parent
328
    assert_equal 0, categories(:child_2).level
329
    assert_equal 1, categories(:child_2_1).level
330
    assert_equal 1, categories(:child_2).left
331
    assert_equal 4, categories(:child_2).right
332
    assert Category.valid?
333
  end
334

  
335
  def test_move_to_child_of
336
    categories(:child_1).move_to_child_of(categories(:child_3))
337
    assert_equal categories(:child_3).id, categories(:child_1).parent_id
338
    assert Category.valid?
339
  end
340
  
341
  def test_move_to_child_of_appends_to_end
342
    child = Category.create! :name => 'New Child'
343
    child.move_to_child_of categories(:top_level)
344
    assert_equal child, categories(:top_level).children.last
345
  end
346
  
347
  def test_subtree_move_to_child_of
348
    assert_equal 4, categories(:child_2).left
349
    assert_equal 7, categories(:child_2).right
350
    
351
    assert_equal 2, categories(:child_1).left
352
    assert_equal 3, categories(:child_1).right
353
    
354
    categories(:child_2).move_to_child_of(categories(:child_1))
355
    assert Category.valid?
356
    assert_equal categories(:child_1).id, categories(:child_2).parent_id
357
    
358
    assert_equal 3, categories(:child_2).left
359
    assert_equal 6, categories(:child_2).right
360
    assert_equal 2, categories(:child_1).left
361
    assert_equal 7, categories(:child_1).right    
362
  end
363
  
364
  def test_slightly_difficult_move_to_child_of
365
    assert_equal 11, categories(:top_level_2).left
366
    assert_equal 12, categories(:top_level_2).right
367
    
368
    # create a new top-level node and move single-node top-level tree inside it.
369
    new_top = Category.create(:name => 'New Top')
370
    assert_equal 13, new_top.left
371
    assert_equal 14, new_top.right
372
    
373
    categories(:top_level_2).move_to_child_of(new_top)
374
    
375
    assert Category.valid?
376
    assert_equal new_top.id, categories(:top_level_2).parent_id
377
    
378
    assert_equal 12, categories(:top_level_2).left
379
    assert_equal 13, categories(:top_level_2).right
380
    assert_equal 11, new_top.left
381
    assert_equal 14, new_top.right    
382
  end
383
  
384
  def test_difficult_move_to_child_of
385
    assert_equal 1, categories(:top_level).left
386
    assert_equal 10, categories(:top_level).right
387
    assert_equal 5, categories(:child_2_1).left
388
    assert_equal 6, categories(:child_2_1).right
389
    
390
    # create a new top-level node and move an entire top-level tree inside it.
391
    new_top = Category.create(:name => 'New Top')
392
    categories(:top_level).move_to_child_of(new_top)
393
    categories(:child_2_1).reload
394
    assert Category.valid?  
395
    assert_equal new_top.id, categories(:top_level).parent_id
396
    
397
    assert_equal 4, categories(:top_level).left
398
    assert_equal 13, categories(:top_level).right
399
    assert_equal 8, categories(:child_2_1).left
400
    assert_equal 9, categories(:child_2_1).right    
401
  end
402

  
403
  #rebuild swaps the position of the 2 children when added using move_to_child twice onto same parent
404
  def test_move_to_child_more_than_once_per_parent_rebuild
405
    root1 = Category.create(:name => 'Root1')
406
    root2 = Category.create(:name => 'Root2')
407
    root3 = Category.create(:name => 'Root3')
408
    
409
    root2.move_to_child_of root1
410
    root3.move_to_child_of root1
411
      
412
    output = Category.roots.last.to_text
413
    Category.update_all('lft = null, rgt = null')
414
    Category.rebuild!
415
    
416
    assert_equal Category.roots.last.to_text, output
417
  end
418
  
419
  # doing move_to_child twice onto same parent from the furthest right first
420
  def test_move_to_child_more_than_once_per_parent_outside_in
421
    node1 = Category.create(:name => 'Node-1')
422
    node2 = Category.create(:name => 'Node-2')
423
    node3 = Category.create(:name => 'Node-3')
424
    
425
    node2.move_to_child_of node1
426
    node3.move_to_child_of node1
427
      
428
    output = Category.roots.last.to_text
429
    Category.update_all('lft = null, rgt = null')
430
    Category.rebuild!
431
    
432
    assert_equal Category.roots.last.to_text, output
433
  end
434

  
435

  
436
  def test_valid_with_null_lefts
437
    assert Category.valid?
438
    Category.update_all('lft = null')
439
    assert !Category.valid?
440
  end
441

  
442
  def test_valid_with_null_rights
443
    assert Category.valid?
444
    Category.update_all('rgt = null')
445
    assert !Category.valid?
446
  end
447
  
448
  def test_valid_with_missing_intermediate_node
449
    # Even though child_2_1 will still exist, it is a sign of a sloppy delete, not an invalid tree.
450
    assert Category.valid?
451
    Category.delete(categories(:child_2).id)
452
    assert Category.valid?
453
  end
454
  
455
  def test_valid_with_overlapping_and_rights
456
    assert Category.valid?
457
    categories(:top_level_2)['lft'] = 0
458
    categories(:top_level_2).save
459
    assert !Category.valid?
460
  end
461
  
462
  def test_rebuild
463
    assert Category.valid?
464
    before_text = Category.root.to_text
465
    Category.update_all('lft = null, rgt = null')
466
    Category.rebuild!
467
    assert Category.valid?
468
    assert_equal before_text, Category.root.to_text
469
  end
470
  
471
  def test_move_possible_for_sibling
472
    assert categories(:child_2).move_possible?(categories(:child_1))
473
  end
474
  
475
  def test_move_not_possible_to_self
476
    assert !categories(:top_level).move_possible?(categories(:top_level))
477
  end
478
  
479
  def test_move_not_possible_to_parent
480
    categories(:top_level).descendants.each do |descendant|
481
      assert !categories(:top_level).move_possible?(descendant)
482
      assert descendant.move_possible?(categories(:top_level))
483
    end
484
  end
485
  
486
  def test_is_or_is_ancestor_of?
487
    [:child_1, :child_2, :child_2_1, :child_3].each do |c|
488
      assert categories(:top_level).is_or_is_ancestor_of?(categories(c))
489
    end
490
    assert !categories(:top_level).is_or_is_ancestor_of?(categories(:top_level_2))
491
  end
492
  
493
  def test_left_and_rights_valid_with_blank_left
494
    assert Category.left_and_rights_valid?
495
    categories(:child_2)[:lft] = nil
496
    categories(:child_2).save(false)
497
    assert !Category.left_and_rights_valid?
498
  end
499

  
500
  def test_left_and_rights_valid_with_blank_right
501
    assert Category.left_and_rights_valid?
502
    categories(:child_2)[:rgt] = nil
503
    categories(:child_2).save(false)
504
    assert !Category.left_and_rights_valid?
505
  end
506

  
507
  def test_left_and_rights_valid_with_equal
508
    assert Category.left_and_rights_valid?
509
    categories(:top_level_2)[:lft] = categories(:top_level_2)[:rgt]
510
    categories(:top_level_2).save(false)
511
    assert !Category.left_and_rights_valid?
512
  end
513

  
514
  def test_left_and_rights_valid_with_left_equal_to_parent
515
    assert Category.left_and_rights_valid?
516
    categories(:child_2)[:lft] = categories(:top_level)[:lft]
517
    categories(:child_2).save(false)
518
    assert !Category.left_and_rights_valid?
519
  end
520

  
521
  def test_left_and_rights_valid_with_right_equal_to_parent
522
    assert Category.left_and_rights_valid?
523
    categories(:child_2)[:rgt] = categories(:top_level)[:rgt]
524
    categories(:child_2).save(false)
525
    assert !Category.left_and_rights_valid?
526
  end
527
  
528
  def test_moving_dirty_objects_doesnt_invalidate_tree
529
    r1 = Category.create
530
    r2 = Category.create
531
    r3 = Category.create
532
    r4 = Category.create
533
    nodes = [r1, r2, r3, r4]
534
    
535
    r2.move_to_child_of(r1)
536
    assert Category.valid?
537
    
538
    r3.move_to_child_of(r1)
539
    assert Category.valid?
540
    
541
    r4.move_to_child_of(r2)
542
    assert Category.valid?
543
  end
544
  
545
  def test_multi_scoped_no_duplicates_for_columns?
546
    assert_nothing_raised do
547
      Note.no_duplicates_for_columns?
548
    end
549
  end
550

  
551
  def test_multi_scoped_all_roots_valid?
552
    assert_nothing_raised do
553
      Note.all_roots_valid?
554
    end
555
  end
556

  
557
  def test_multi_scoped
558
    note1 = Note.create!(:body => "A", :notable_id => 2, :notable_type => 'Category')
559
    note2 = Note.create!(:body => "B", :notable_id => 2, :notable_type => 'Category')
560
    note3 = Note.create!(:body => "C", :notable_id => 2, :notable_type => 'Default')
561
    
562
    assert_equal [note1, note2], note1.self_and_siblings
563
    assert_equal [note3], note3.self_and_siblings
564
  end
565
  
566
  def test_multi_scoped_rebuild
567
    root = Note.create!(:body => "A", :notable_id => 3, :notable_type => 'Category')
568
    child1 = Note.create!(:body => "B", :notable_id => 3, :notable_type => 'Category')
569
    child2 = Note.create!(:body => "C", :notable_id => 3, :notable_type => 'Category')
570
    
571
    child1.move_to_child_of root
572
    child2.move_to_child_of root
573
          
574
    Note.update_all('lft = null, rgt = null')
575
    Note.rebuild!
576
    
577
    assert_equal Note.roots.find_by_body('A'), root
578
    assert_equal [child1, child2], Note.roots.find_by_body('A').children
579
  end
580
  
581
  def test_same_scope_with_multi_scopes
582
    assert_nothing_raised do
583
      notes(:scope1).same_scope?(notes(:child_1))
584
    end
585
    assert notes(:scope1).same_scope?(notes(:child_1))
586
    assert notes(:child_1).same_scope?(notes(:scope1))
587
    assert !notes(:scope1).same_scope?(notes(:scope2))
588
  end
589
  
590
  def test_quoting_of_multi_scope_column_names
591
    assert_equal ["\"notable_id\"", "\"notable_type\""], Note.quoted_scope_column_names
592
  end
593
  
594
  def test_equal_in_same_scope
595
    assert_equal notes(:scope1), notes(:scope1)
596
    assert_not_equal notes(:scope1), notes(:child_1)
597
  end
598
  
599
  def test_equal_in_different_scopes
600
    assert_not_equal notes(:scope1), notes(:scope2)
601
  end
602
  
603
end

Also available in: Unified diff