Revision 1297:0a574315af3e .svn/pristine/a7
| .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