##// END OF EJS Templates
Merged Rails 2.1 compatibility branch....
Jean-Philippe Lang -
r1609:7cdd88a6ce36
parent child
Show More
@@ -0,0 +1,17
1
2 ActiveRecord::Errors.default_error_messages = {
3 :inclusion => "activerecord_error_inclusion",
4 :exclusion => "activerecord_error_exclusion",
5 :invalid => "activerecord_error_invalid",
6 :confirmation => "activerecord_error_confirmation",
7 :accepted => "activerecord_error_accepted",
8 :empty => "activerecord_error_empty",
9 :blank => "activerecord_error_blank",
10 :too_long => "activerecord_error_too_long",
11 :too_short => "activerecord_error_too_short",
12 :wrong_length => "activerecord_error_wrong_length",
13 :taken => "activerecord_error_taken",
14 :not_a_number => "activerecord_error_not_a_number"
15 }
16
17 ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| "#{html_tag}" }
@@ -0,0 +1,4
1 # Add new mime types for use in respond_to blocks:
2
3 Mime::SET << Mime::CSV unless Mime::SET.include?(Mime::CSV)
4 Mime::Type.register 'application/pdf', :pdf
@@ -0,0 +1,7
1 GLoc.set_config :default_language => :en
2 GLoc.clear_strings
3 GLoc.set_kcode
4 GLoc.load_localized_strings
5 GLoc.set_config(:raise_string_not_found_errors => false)
6
7 require 'redmine'
@@ -0,0 +1,3
1 #!/usr/bin/env ruby
2 require File.dirname(__FILE__) + '/../config/boot'
3 require 'commands/dbconsole'
@@ -0,0 +1,3
1 #!/usr/bin/env ruby
2 require File.dirname(__FILE__) + '/../../config/boot'
3 require 'commands/performance/request'
@@ -0,0 +1,3
1 #!/usr/bin/env ruby
2 require File.dirname(__FILE__) + '/../../config/boot'
3 require 'commands/process/inspector'
@@ -1,434 +1,434
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class ProjectsController < ApplicationController
18 class ProjectsController < ApplicationController
19 layout 'base'
19 layout 'base'
20 menu_item :overview
20 menu_item :overview
21 menu_item :activity, :only => :activity
21 menu_item :activity, :only => :activity
22 menu_item :roadmap, :only => :roadmap
22 menu_item :roadmap, :only => :roadmap
23 menu_item :files, :only => [:list_files, :add_file]
23 menu_item :files, :only => [:list_files, :add_file]
24 menu_item :settings, :only => :settings
24 menu_item :settings, :only => :settings
25 menu_item :issues, :only => [:changelog]
25 menu_item :issues, :only => [:changelog]
26
26
27 before_filter :find_project, :except => [ :index, :list, :add, :activity ]
27 before_filter :find_project, :except => [ :index, :list, :add, :activity ]
28 before_filter :find_optional_project, :only => :activity
28 before_filter :find_optional_project, :only => :activity
29 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy, :activity ]
29 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy, :activity ]
30 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
30 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
31 accept_key_auth :activity, :calendar
31 accept_key_auth :activity, :calendar
32
32
33 helper :sort
33 helper :sort
34 include SortHelper
34 include SortHelper
35 helper :custom_fields
35 helper :custom_fields
36 include CustomFieldsHelper
36 include CustomFieldsHelper
37 helper :ifpdf
37 helper :ifpdf
38 include IfpdfHelper
38 include IfpdfHelper
39 helper :issues
39 helper :issues
40 helper IssuesHelper
40 helper IssuesHelper
41 helper :queries
41 helper :queries
42 include QueriesHelper
42 include QueriesHelper
43 helper :repositories
43 helper :repositories
44 include RepositoriesHelper
44 include RepositoriesHelper
45 include ProjectsHelper
45 include ProjectsHelper
46
46
47 # Lists visible projects
47 # Lists visible projects
48 def index
48 def index
49 projects = Project.find :all,
49 projects = Project.find :all,
50 :conditions => Project.visible_by(User.current),
50 :conditions => Project.visible_by(User.current),
51 :include => :parent
51 :include => :parent
52 respond_to do |format|
52 respond_to do |format|
53 format.html {
53 format.html {
54 @project_tree = projects.group_by {|p| p.parent || p}
54 @project_tree = projects.group_by {|p| p.parent || p}
55 @project_tree.each_key {|p| @project_tree[p] -= [p]}
55 @project_tree.keys.each {|p| @project_tree[p] -= [p]}
56 }
56 }
57 format.atom {
57 format.atom {
58 render_feed(projects.sort_by(&:created_on).reverse.slice(0, Setting.feeds_limit.to_i),
58 render_feed(projects.sort_by(&:created_on).reverse.slice(0, Setting.feeds_limit.to_i),
59 :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
59 :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
60 }
60 }
61 end
61 end
62 end
62 end
63
63
64 # Add a new project
64 # Add a new project
65 def add
65 def add
66 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
66 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
67 @trackers = Tracker.all
67 @trackers = Tracker.all
68 @root_projects = Project.find(:all,
68 @root_projects = Project.find(:all,
69 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
69 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
70 :order => 'name')
70 :order => 'name')
71 @project = Project.new(params[:project])
71 @project = Project.new(params[:project])
72 if request.get?
72 if request.get?
73 @project.trackers = Tracker.all
73 @project.trackers = Tracker.all
74 @project.is_public = Setting.default_projects_public?
74 @project.is_public = Setting.default_projects_public?
75 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
75 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
76 else
76 else
77 @project.enabled_module_names = params[:enabled_modules]
77 @project.enabled_module_names = params[:enabled_modules]
78 if @project.save
78 if @project.save
79 flash[:notice] = l(:notice_successful_create)
79 flash[:notice] = l(:notice_successful_create)
80 redirect_to :controller => 'admin', :action => 'projects'
80 redirect_to :controller => 'admin', :action => 'projects'
81 end
81 end
82 end
82 end
83 end
83 end
84
84
85 # Show @project
85 # Show @project
86 def show
86 def show
87 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
87 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
88 @subprojects = @project.children.find(:all, :conditions => Project.visible_by(User.current))
88 @subprojects = @project.children.find(:all, :conditions => Project.visible_by(User.current))
89 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
89 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
90 @trackers = @project.rolled_up_trackers
90 @trackers = @project.rolled_up_trackers
91
91
92 cond = @project.project_condition(Setting.display_subprojects_issues?)
92 cond = @project.project_condition(Setting.display_subprojects_issues?)
93 Issue.visible_by(User.current) do
93 Issue.visible_by(User.current) do
94 @open_issues_by_tracker = Issue.count(:group => :tracker,
94 @open_issues_by_tracker = Issue.count(:group => :tracker,
95 :include => [:project, :status, :tracker],
95 :include => [:project, :status, :tracker],
96 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
96 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
97 @total_issues_by_tracker = Issue.count(:group => :tracker,
97 @total_issues_by_tracker = Issue.count(:group => :tracker,
98 :include => [:project, :status, :tracker],
98 :include => [:project, :status, :tracker],
99 :conditions => cond)
99 :conditions => cond)
100 end
100 end
101 TimeEntry.visible_by(User.current) do
101 TimeEntry.visible_by(User.current) do
102 @total_hours = TimeEntry.sum(:hours,
102 @total_hours = TimeEntry.sum(:hours,
103 :include => :project,
103 :include => :project,
104 :conditions => cond).to_f
104 :conditions => cond).to_f
105 end
105 end
106 @key = User.current.rss_key
106 @key = User.current.rss_key
107 end
107 end
108
108
109 def settings
109 def settings
110 @root_projects = Project.find(:all,
110 @root_projects = Project.find(:all,
111 :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id],
111 :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id],
112 :order => 'name')
112 :order => 'name')
113 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
113 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
114 @issue_category ||= IssueCategory.new
114 @issue_category ||= IssueCategory.new
115 @member ||= @project.members.new
115 @member ||= @project.members.new
116 @trackers = Tracker.all
116 @trackers = Tracker.all
117 @repository ||= @project.repository
117 @repository ||= @project.repository
118 @wiki ||= @project.wiki
118 @wiki ||= @project.wiki
119 end
119 end
120
120
121 # Edit @project
121 # Edit @project
122 def edit
122 def edit
123 if request.post?
123 if request.post?
124 @project.attributes = params[:project]
124 @project.attributes = params[:project]
125 if @project.save
125 if @project.save
126 flash[:notice] = l(:notice_successful_update)
126 flash[:notice] = l(:notice_successful_update)
127 redirect_to :action => 'settings', :id => @project
127 redirect_to :action => 'settings', :id => @project
128 else
128 else
129 settings
129 settings
130 render :action => 'settings'
130 render :action => 'settings'
131 end
131 end
132 end
132 end
133 end
133 end
134
134
135 def modules
135 def modules
136 @project.enabled_module_names = params[:enabled_modules]
136 @project.enabled_module_names = params[:enabled_modules]
137 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
137 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
138 end
138 end
139
139
140 def archive
140 def archive
141 @project.archive if request.post? && @project.active?
141 @project.archive if request.post? && @project.active?
142 redirect_to :controller => 'admin', :action => 'projects'
142 redirect_to :controller => 'admin', :action => 'projects'
143 end
143 end
144
144
145 def unarchive
145 def unarchive
146 @project.unarchive if request.post? && !@project.active?
146 @project.unarchive if request.post? && !@project.active?
147 redirect_to :controller => 'admin', :action => 'projects'
147 redirect_to :controller => 'admin', :action => 'projects'
148 end
148 end
149
149
150 # Delete @project
150 # Delete @project
151 def destroy
151 def destroy
152 @project_to_destroy = @project
152 @project_to_destroy = @project
153 if request.post? and params[:confirm]
153 if request.post? and params[:confirm]
154 @project_to_destroy.destroy
154 @project_to_destroy.destroy
155 redirect_to :controller => 'admin', :action => 'projects'
155 redirect_to :controller => 'admin', :action => 'projects'
156 end
156 end
157 # hide project in layout
157 # hide project in layout
158 @project = nil
158 @project = nil
159 end
159 end
160
160
161 # Add a new issue category to @project
161 # Add a new issue category to @project
162 def add_issue_category
162 def add_issue_category
163 @category = @project.issue_categories.build(params[:category])
163 @category = @project.issue_categories.build(params[:category])
164 if request.post? and @category.save
164 if request.post? and @category.save
165 respond_to do |format|
165 respond_to do |format|
166 format.html do
166 format.html do
167 flash[:notice] = l(:notice_successful_create)
167 flash[:notice] = l(:notice_successful_create)
168 redirect_to :action => 'settings', :tab => 'categories', :id => @project
168 redirect_to :action => 'settings', :tab => 'categories', :id => @project
169 end
169 end
170 format.js do
170 format.js do
171 # IE doesn't support the replace_html rjs method for select box options
171 # IE doesn't support the replace_html rjs method for select box options
172 render(:update) {|page| page.replace "issue_category_id",
172 render(:update) {|page| page.replace "issue_category_id",
173 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
173 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
174 }
174 }
175 end
175 end
176 end
176 end
177 end
177 end
178 end
178 end
179
179
180 # Add a new version to @project
180 # Add a new version to @project
181 def add_version
181 def add_version
182 @version = @project.versions.build(params[:version])
182 @version = @project.versions.build(params[:version])
183 if request.post? and @version.save
183 if request.post? and @version.save
184 flash[:notice] = l(:notice_successful_create)
184 flash[:notice] = l(:notice_successful_create)
185 redirect_to :action => 'settings', :tab => 'versions', :id => @project
185 redirect_to :action => 'settings', :tab => 'versions', :id => @project
186 end
186 end
187 end
187 end
188
188
189 def add_file
189 def add_file
190 if request.post?
190 if request.post?
191 @version = @project.versions.find_by_id(params[:version_id])
191 @version = @project.versions.find_by_id(params[:version_id])
192 attachments = attach_files(@version, params[:attachments])
192 attachments = attach_files(@version, params[:attachments])
193 Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('file_added')
193 Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('file_added')
194 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
194 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
195 end
195 end
196 @versions = @project.versions.sort
196 @versions = @project.versions.sort
197 end
197 end
198
198
199 def list_files
199 def list_files
200 sort_init "#{Attachment.table_name}.filename", "asc"
200 sort_init "#{Attachment.table_name}.filename", "asc"
201 sort_update
201 sort_update
202 @versions = @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
202 @versions = @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
203 render :layout => !request.xhr?
203 render :layout => !request.xhr?
204 end
204 end
205
205
206 # Show changelog for @project
206 # Show changelog for @project
207 def changelog
207 def changelog
208 @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
208 @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
209 retrieve_selected_tracker_ids(@trackers)
209 retrieve_selected_tracker_ids(@trackers)
210 @versions = @project.versions.sort
210 @versions = @project.versions.sort
211 end
211 end
212
212
213 def roadmap
213 def roadmap
214 @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
214 @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
215 retrieve_selected_tracker_ids(@trackers)
215 retrieve_selected_tracker_ids(@trackers)
216 @versions = @project.versions.sort
216 @versions = @project.versions.sort
217 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
217 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
218 end
218 end
219
219
220 def activity
220 def activity
221 @days = Setting.activity_days_default.to_i
221 @days = Setting.activity_days_default.to_i
222
222
223 if params[:from]
223 if params[:from]
224 begin; @date_to = params[:from].to_date; rescue; end
224 begin; @date_to = params[:from].to_date; rescue; end
225 end
225 end
226
226
227 @date_to ||= Date.today + 1
227 @date_to ||= Date.today + 1
228 @date_from = @date_to - @days
228 @date_from = @date_to - @days
229
229
230 @event_types = %w(issues news files documents changesets wiki_pages messages)
230 @event_types = %w(issues news files documents changesets wiki_pages messages)
231 if @project
231 if @project
232 @event_types.delete('wiki_pages') unless @project.wiki
232 @event_types.delete('wiki_pages') unless @project.wiki
233 @event_types.delete('changesets') unless @project.repository
233 @event_types.delete('changesets') unless @project.repository
234 @event_types.delete('messages') unless @project.boards.any?
234 @event_types.delete('messages') unless @project.boards.any?
235 # only show what the user is allowed to view
235 # only show what the user is allowed to view
236 @event_types = @event_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
236 @event_types = @event_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
237 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
237 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
238 end
238 end
239 @scope = @event_types.select {|t| params["show_#{t}"]}
239 @scope = @event_types.select {|t| params["show_#{t}"]}
240 # default events if none is specified in parameters
240 # default events if none is specified in parameters
241 @scope = (@event_types - %w(wiki_pages messages))if @scope.empty?
241 @scope = (@event_types - %w(wiki_pages messages))if @scope.empty?
242
242
243 @events = []
243 @events = []
244
244
245 if @scope.include?('issues')
245 if @scope.include?('issues')
246 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_issues, :project => @project, :with_subprojects => @with_subprojects))
246 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_issues, :project => @project, :with_subprojects => @with_subprojects))
247 cond.add(["#{Issue.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
247 cond.add(["#{Issue.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
248 @events += Issue.find(:all, :include => [:project, :author, :tracker], :conditions => cond.conditions)
248 @events += Issue.find(:all, :include => [:project, :author, :tracker], :conditions => cond.conditions)
249
249
250 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_issues, :project => @project, :with_subprojects => @with_subprojects))
250 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_issues, :project => @project, :with_subprojects => @with_subprojects))
251 cond.add(["#{Journal.table_name}.journalized_type = 'Issue' AND #{Journal.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
251 cond.add(["#{Journal.table_name}.journalized_type = 'Issue' AND #{Journal.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
252 cond.add("#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> ''")
252 cond.add("#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> ''")
253 @events += Journal.find(:all, :include => [{:issue => :project}, :details, :user], :conditions => cond.conditions)
253 @events += Journal.find(:all, :include => [{:issue => :project}, :details, :user], :conditions => cond.conditions)
254 end
254 end
255
255
256 if @scope.include?('news')
256 if @scope.include?('news')
257 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_news, :project => @project, :with_subprojects => @with_subprojects))
257 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_news, :project => @project, :with_subprojects => @with_subprojects))
258 cond.add(["#{News.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
258 cond.add(["#{News.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
259 @events += News.find(:all, :include => [:project, :author], :conditions => cond.conditions)
259 @events += News.find(:all, :include => [:project, :author], :conditions => cond.conditions)
260 end
260 end
261
261
262 if @scope.include?('files')
262 if @scope.include?('files')
263 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_files, :project => @project, :with_subprojects => @with_subprojects))
263 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_files, :project => @project, :with_subprojects => @with_subprojects))
264 cond.add(["#{Attachment.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
264 cond.add(["#{Attachment.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
265 @events += Attachment.find(:all, :select => "#{Attachment.table_name}.*",
265 @events += Attachment.find(:all, :select => "#{Attachment.table_name}.*",
266 :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
266 :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
267 "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id",
267 "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id",
268 :conditions => cond.conditions)
268 :conditions => cond.conditions)
269 end
269 end
270
270
271 if @scope.include?('documents')
271 if @scope.include?('documents')
272 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_documents, :project => @project, :with_subprojects => @with_subprojects))
272 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_documents, :project => @project, :with_subprojects => @with_subprojects))
273 cond.add(["#{Document.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
273 cond.add(["#{Document.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
274 @events += Document.find(:all, :include => :project, :conditions => cond.conditions)
274 @events += Document.find(:all, :include => :project, :conditions => cond.conditions)
275
275
276 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_documents, :project => @project, :with_subprojects => @with_subprojects))
276 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_documents, :project => @project, :with_subprojects => @with_subprojects))
277 cond.add(["#{Attachment.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
277 cond.add(["#{Attachment.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
278 @events += Attachment.find(:all, :select => "#{Attachment.table_name}.*",
278 @events += Attachment.find(:all, :select => "#{Attachment.table_name}.*",
279 :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
279 :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
280 "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id",
280 "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id",
281 :conditions => cond.conditions)
281 :conditions => cond.conditions)
282 end
282 end
283
283
284 if @scope.include?('wiki_pages')
284 if @scope.include?('wiki_pages')
285 select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
285 select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
286 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
286 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
287 "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
287 "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
288 "#{WikiContent.versioned_table_name}.id"
288 "#{WikiContent.versioned_table_name}.id"
289 joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
289 joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
290 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " +
290 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " +
291 "LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"
291 "LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"
292
292
293 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_wiki_pages, :project => @project, :with_subprojects => @with_subprojects))
293 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_wiki_pages, :project => @project, :with_subprojects => @with_subprojects))
294 cond.add(["#{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?", @date_from, @date_to])
294 cond.add(["#{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?", @date_from, @date_to])
295 @events += WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => cond.conditions)
295 @events += WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => cond.conditions)
296 end
296 end
297
297
298 if @scope.include?('changesets')
298 if @scope.include?('changesets')
299 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_changesets, :project => @project, :with_subprojects => @with_subprojects))
299 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_changesets, :project => @project, :with_subprojects => @with_subprojects))
300 cond.add(["#{Changeset.table_name}.committed_on BETWEEN ? AND ?", @date_from, @date_to])
300 cond.add(["#{Changeset.table_name}.committed_on BETWEEN ? AND ?", @date_from, @date_to])
301 @events += Changeset.find(:all, :include => {:repository => :project}, :conditions => cond.conditions)
301 @events += Changeset.find(:all, :include => {:repository => :project}, :conditions => cond.conditions)
302 end
302 end
303
303
304 if @scope.include?('messages')
304 if @scope.include?('messages')
305 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_messages, :project => @project, :with_subprojects => @with_subprojects))
305 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_messages, :project => @project, :with_subprojects => @with_subprojects))
306 cond.add(["#{Message.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
306 cond.add(["#{Message.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
307 @events += Message.find(:all, :include => [{:board => :project}, :author], :conditions => cond.conditions)
307 @events += Message.find(:all, :include => [{:board => :project}, :author], :conditions => cond.conditions)
308 end
308 end
309
309
310 @events_by_day = @events.group_by(&:event_date)
310 @events_by_day = @events.group_by(&:event_date)
311
311
312 respond_to do |format|
312 respond_to do |format|
313 format.html { render :layout => false if request.xhr? }
313 format.html { render :layout => false if request.xhr? }
314 format.atom { render_feed(@events, :title => "#{@project || Setting.app_title}: #{l(:label_activity)}") }
314 format.atom { render_feed(@events, :title => "#{@project || Setting.app_title}: #{l(:label_activity)}") }
315 end
315 end
316 end
316 end
317
317
318 def calendar
318 def calendar
319 @trackers = @project.rolled_up_trackers
319 @trackers = @project.rolled_up_trackers
320 retrieve_selected_tracker_ids(@trackers)
320 retrieve_selected_tracker_ids(@trackers)
321
321
322 if params[:year] and params[:year].to_i > 1900
322 if params[:year] and params[:year].to_i > 1900
323 @year = params[:year].to_i
323 @year = params[:year].to_i
324 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
324 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
325 @month = params[:month].to_i
325 @month = params[:month].to_i
326 end
326 end
327 end
327 end
328 @year ||= Date.today.year
328 @year ||= Date.today.year
329 @month ||= Date.today.month
329 @month ||= Date.today.month
330 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
330 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
331 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
331 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
332 events = []
332 events = []
333 @project.issues_with_subprojects(@with_subprojects) do
333 @project.issues_with_subprojects(@with_subprojects) do
334 events += Issue.find(:all,
334 events += Issue.find(:all,
335 :include => [:tracker, :status, :assigned_to, :priority, :project],
335 :include => [:tracker, :status, :assigned_to, :priority, :project],
336 :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?)) AND #{Issue.table_name}.tracker_id IN (#{@selected_tracker_ids.join(',')})", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
336 :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?)) AND #{Issue.table_name}.tracker_id IN (#{@selected_tracker_ids.join(',')})", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
337 ) unless @selected_tracker_ids.empty?
337 ) unless @selected_tracker_ids.empty?
338 events += Version.find(:all, :include => :project,
338 events += Version.find(:all, :include => :project,
339 :conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
339 :conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
340 end
340 end
341 @calendar.events = events
341 @calendar.events = events
342
342
343 render :layout => false if request.xhr?
343 render :layout => false if request.xhr?
344 end
344 end
345
345
346 def gantt
346 def gantt
347 @trackers = @project.rolled_up_trackers
347 @trackers = @project.rolled_up_trackers
348 retrieve_selected_tracker_ids(@trackers)
348 retrieve_selected_tracker_ids(@trackers)
349
349
350 if params[:year] and params[:year].to_i >0
350 if params[:year] and params[:year].to_i >0
351 @year_from = params[:year].to_i
351 @year_from = params[:year].to_i
352 if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
352 if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
353 @month_from = params[:month].to_i
353 @month_from = params[:month].to_i
354 else
354 else
355 @month_from = 1
355 @month_from = 1
356 end
356 end
357 else
357 else
358 @month_from ||= Date.today.month
358 @month_from ||= Date.today.month
359 @year_from ||= Date.today.year
359 @year_from ||= Date.today.year
360 end
360 end
361
361
362 zoom = (params[:zoom] || User.current.pref[:gantt_zoom]).to_i
362 zoom = (params[:zoom] || User.current.pref[:gantt_zoom]).to_i
363 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
363 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
364 months = (params[:months] || User.current.pref[:gantt_months]).to_i
364 months = (params[:months] || User.current.pref[:gantt_months]).to_i
365 @months = (months > 0 && months < 25) ? months : 6
365 @months = (months > 0 && months < 25) ? months : 6
366
366
367 # Save gantt paramters as user preference (zoom and months count)
367 # Save gantt paramters as user preference (zoom and months count)
368 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
368 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
369 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
369 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
370 User.current.preference.save
370 User.current.preference.save
371 end
371 end
372
372
373 @date_from = Date.civil(@year_from, @month_from, 1)
373 @date_from = Date.civil(@year_from, @month_from, 1)
374 @date_to = (@date_from >> @months) - 1
374 @date_to = (@date_from >> @months) - 1
375 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
375 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
376
376
377 @events = []
377 @events = []
378 @project.issues_with_subprojects(@with_subprojects) do
378 @project.issues_with_subprojects(@with_subprojects) do
379 # Issues that have start and due dates
379 # Issues that have start and due dates
380 @events += Issue.find(:all,
380 @events += Issue.find(:all,
381 :order => "start_date, due_date",
381 :order => "start_date, due_date",
382 :include => [:tracker, :status, :assigned_to, :priority, :project],
382 :include => [:tracker, :status, :assigned_to, :priority, :project],
383 :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
383 :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
384 ) unless @selected_tracker_ids.empty?
384 ) unless @selected_tracker_ids.empty?
385 # Issues that don't have a due date but that are assigned to a version with a date
385 # Issues that don't have a due date but that are assigned to a version with a date
386 @events += Issue.find(:all,
386 @events += Issue.find(:all,
387 :order => "start_date, effective_date",
387 :order => "start_date, effective_date",
388 :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
388 :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
389 :conditions => ["(((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
389 :conditions => ["(((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
390 ) unless @selected_tracker_ids.empty?
390 ) unless @selected_tracker_ids.empty?
391 @events += Version.find(:all, :include => :project,
391 @events += Version.find(:all, :include => :project,
392 :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
392 :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
393 end
393 end
394 @events.sort! {|x,y| x.start_date <=> y.start_date }
394 @events.sort! {|x,y| x.start_date <=> y.start_date }
395
395
396 if params[:format]=='pdf'
396 if params[:format]=='pdf'
397 @options_for_rfpdf ||= {}
397 @options_for_rfpdf ||= {}
398 @options_for_rfpdf[:file_name] = "#{@project.identifier}-gantt.pdf"
398 @options_for_rfpdf[:file_name] = "#{@project.identifier}-gantt.pdf"
399 render :template => "projects/gantt.rfpdf", :layout => false
399 render :template => "projects/gantt.rfpdf", :layout => false
400 elsif params[:format]=='png' && respond_to?('gantt_image')
400 elsif params[:format]=='png' && respond_to?('gantt_image')
401 image = gantt_image(@events, @date_from, @months, @zoom)
401 image = gantt_image(@events, @date_from, @months, @zoom)
402 image.format = 'PNG'
402 image.format = 'PNG'
403 send_data(image.to_blob, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png")
403 send_data(image.to_blob, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png")
404 else
404 else
405 render :template => "projects/gantt.rhtml"
405 render :template => "projects/gantt.rhtml"
406 end
406 end
407 end
407 end
408
408
409 private
409 private
410 # Find project of id params[:id]
410 # Find project of id params[:id]
411 # if not found, redirect to project list
411 # if not found, redirect to project list
412 # Used as a before_filter
412 # Used as a before_filter
413 def find_project
413 def find_project
414 @project = Project.find(params[:id])
414 @project = Project.find(params[:id])
415 rescue ActiveRecord::RecordNotFound
415 rescue ActiveRecord::RecordNotFound
416 render_404
416 render_404
417 end
417 end
418
418
419 def find_optional_project
419 def find_optional_project
420 return true unless params[:id]
420 return true unless params[:id]
421 @project = Project.find(params[:id])
421 @project = Project.find(params[:id])
422 authorize
422 authorize
423 rescue ActiveRecord::RecordNotFound
423 rescue ActiveRecord::RecordNotFound
424 render_404
424 render_404
425 end
425 end
426
426
427 def retrieve_selected_tracker_ids(selectable_trackers)
427 def retrieve_selected_tracker_ids(selectable_trackers)
428 if ids = params[:tracker_ids]
428 if ids = params[:tracker_ids]
429 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
429 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
430 else
430 else
431 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
431 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
432 end
432 end
433 end
433 end
434 end
434 end
@@ -1,98 +1,102
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'iconv'
18 require 'iconv'
19
19
20 module RepositoriesHelper
20 module RepositoriesHelper
21 def format_revision(txt)
21 def format_revision(txt)
22 txt.to_s[0,8]
22 txt.to_s[0,8]
23 end
23 end
24
24
25 def to_path_param(path)
26 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
27 end
28
25 def to_utf8(str)
29 def to_utf8(str)
26 return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
30 return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
27 @encodings ||= Setting.repositories_encodings.split(',').collect(&:strip)
31 @encodings ||= Setting.repositories_encodings.split(',').collect(&:strip)
28 @encodings.each do |encoding|
32 @encodings.each do |encoding|
29 begin
33 begin
30 return Iconv.conv('UTF-8', encoding, str)
34 return Iconv.conv('UTF-8', encoding, str)
31 rescue Iconv::Failure
35 rescue Iconv::Failure
32 # do nothing here and try the next encoding
36 # do nothing here and try the next encoding
33 end
37 end
34 end
38 end
35 str
39 str
36 end
40 end
37
41
38 def repository_field_tags(form, repository)
42 def repository_field_tags(form, repository)
39 method = repository.class.name.demodulize.underscore + "_field_tags"
43 method = repository.class.name.demodulize.underscore + "_field_tags"
40 send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method)
44 send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method)
41 end
45 end
42
46
43 def scm_select_tag(repository)
47 def scm_select_tag(repository)
44 scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
48 scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
45 REDMINE_SUPPORTED_SCM.each do |scm|
49 REDMINE_SUPPORTED_SCM.each do |scm|
46 scm_options << ["Repository::#{scm}".constantize.scm_name, scm] if Setting.enabled_scm.include?(scm) || (repository && repository.class.name.demodulize == scm)
50 scm_options << ["Repository::#{scm}".constantize.scm_name, scm] if Setting.enabled_scm.include?(scm) || (repository && repository.class.name.demodulize == scm)
47 end
51 end
48
52
49 select_tag('repository_scm',
53 select_tag('repository_scm',
50 options_for_select(scm_options, repository.class.name.demodulize),
54 options_for_select(scm_options, repository.class.name.demodulize),
51 :disabled => (repository && !repository.new_record?),
55 :disabled => (repository && !repository.new_record?),
52 :onchange => remote_function(:url => { :controller => 'repositories', :action => 'edit', :id => @project }, :method => :get, :with => "Form.serialize(this.form)")
56 :onchange => remote_function(:url => { :controller => 'repositories', :action => 'edit', :id => @project }, :method => :get, :with => "Form.serialize(this.form)")
53 )
57 )
54 end
58 end
55
59
56 def with_leading_slash(path)
60 def with_leading_slash(path)
57 path.to_s.starts_with?('/') ? path : "/#{path}"
61 path.to_s.starts_with?('/') ? path : "/#{path}"
58 end
62 end
59
63
60 def without_leading_slash(path)
64 def without_leading_slash(path)
61 path.gsub(%r{^/+}, '')
65 path.gsub(%r{^/+}, '')
62 end
66 end
63
67
64 def subversion_field_tags(form, repository)
68 def subversion_field_tags(form, repository)
65 content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)) +
69 content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)) +
66 '<br />(http://, https://, svn://, file:///)') +
70 '<br />(http://, https://, svn://, file:///)') +
67 content_tag('p', form.text_field(:login, :size => 30)) +
71 content_tag('p', form.text_field(:login, :size => 30)) +
68 content_tag('p', form.password_field(:password, :size => 30, :name => 'ignore',
72 content_tag('p', form.password_field(:password, :size => 30, :name => 'ignore',
69 :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
73 :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
70 :onfocus => "this.value=''; this.name='repository[password]';",
74 :onfocus => "this.value=''; this.name='repository[password]';",
71 :onchange => "this.name='repository[password]';"))
75 :onchange => "this.name='repository[password]';"))
72 end
76 end
73
77
74 def darcs_field_tags(form, repository)
78 def darcs_field_tags(form, repository)
75 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
79 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
76 end
80 end
77
81
78 def mercurial_field_tags(form, repository)
82 def mercurial_field_tags(form, repository)
79 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
83 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
80 end
84 end
81
85
82 def git_field_tags(form, repository)
86 def git_field_tags(form, repository)
83 content_tag('p', form.text_field(:url, :label => 'Path to .git directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
87 content_tag('p', form.text_field(:url, :label => 'Path to .git directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
84 end
88 end
85
89
86 def cvs_field_tags(form, repository)
90 def cvs_field_tags(form, repository)
87 content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) +
91 content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) +
88 content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?))
92 content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?))
89 end
93 end
90
94
91 def bazaar_field_tags(form, repository)
95 def bazaar_field_tags(form, repository)
92 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
96 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
93 end
97 end
94
98
95 def filesystem_field_tags(form, repository)
99 def filesystem_field_tags(form, repository)
96 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
100 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
97 end
101 end
98 end
102 end
@@ -1,52 +1,54
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class UserPreference < ActiveRecord::Base
18 class UserPreference < ActiveRecord::Base
19 belongs_to :user
19 belongs_to :user
20 serialize :others
20 serialize :others
21
21
22 attr_protected :others
22 attr_protected :others
23
23
24 def initialize(attributes = nil)
24 def initialize(attributes = nil)
25 super
25 super
26 self.others ||= {}
26 self.others ||= {}
27 end
27 end
28
28
29 def before_save
29 def before_save
30 self.others ||= {}
30 self.others ||= {}
31 end
31 end
32
32
33 def [](attr_name)
33 def [](attr_name)
34 if attribute_present? attr_name
34 if attribute_present? attr_name
35 super
35 super
36 else
36 else
37 others ? others[attr_name] : nil
37 others ? others[attr_name] : nil
38 end
38 end
39 end
39 end
40
40
41 def []=(attr_name, value)
41 def []=(attr_name, value)
42 if attribute_present? attr_name
42 if attribute_present? attr_name
43 super
43 super
44 else
44 else
45 self.others ||= {}
45 h = read_attribute(:others).dup || {}
46 self.others.store attr_name, value
46 h.update(attr_name => value)
47 write_attribute(:others, h)
48 value
47 end
49 end
48 end
50 end
49
51
50 def comments_sorting; self[:comments_sorting] end
52 def comments_sorting; self[:comments_sorting] end
51 def comments_sorting=(order); self[:comments_sorting]=order end
53 def comments_sorting=(order); self[:comments_sorting]=order end
52 end
54 end
@@ -1,24 +1,24
1 <% @entries.each do |entry| %>
1 <% @entries.each do |entry| %>
2 <% tr_id = Digest::MD5.hexdigest(entry.path)
2 <% tr_id = Digest::MD5.hexdigest(entry.path)
3 depth = params[:depth].to_i %>
3 depth = params[:depth].to_i %>
4 <tr id="<%= tr_id %>" class="<%= params[:parent_id] %> entry <%= entry.kind %>">
4 <tr id="<%= tr_id %>" class="<%= params[:parent_id] %> entry <%= entry.kind %>">
5 <td style="padding-left: <%=18 * depth%>px;" class="filename">
5 <td style="padding-left: <%=18 * depth%>px;" class="filename">
6 <% if entry.is_dir? %>
6 <% if entry.is_dir? %>
7 <span class="expander" onclick="<%= remote_function :url => {:action => 'browse', :id => @project, :path => entry.path, :rev => @rev, :depth => (depth + 1), :parent_id => tr_id},
7 <span class="expander" onclick="<%= remote_function :url => {:action => 'browse', :id => @project, :path => to_path_param(entry.path), :rev => @rev, :depth => (depth + 1), :parent_id => tr_id},
8 :update => { :success => tr_id },
8 :update => { :success => tr_id },
9 :position => :after,
9 :position => :after,
10 :success => "scmEntryLoaded('#{tr_id}')",
10 :success => "scmEntryLoaded('#{tr_id}')",
11 :condition => "scmEntryClick('#{tr_id}')"%>">&nbsp</span>
11 :condition => "scmEntryClick('#{tr_id}')"%>">&nbsp</span>
12 <% end %>
12 <% end %>
13 <%= link_to h(entry.name),
13 <%= link_to h(entry.name),
14 {:action => (entry.is_dir? ? 'browse' : 'changes'), :id => @project, :path => entry.path, :rev => @rev},
14 {:action => (entry.is_dir? ? 'browse' : 'changes'), :id => @project, :path => to_path_param(entry.path), :rev => @rev},
15 :class => (entry.is_dir? ? 'icon icon-folder' : 'icon icon-file')%>
15 :class => (entry.is_dir? ? 'icon icon-folder' : 'icon icon-file')%>
16 </td>
16 </td>
17 <td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td>
17 <td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td>
18 <td class="revision"><%= link_to(format_revision(entry.lastrev.name), :action => 'revision', :id => @project, :rev => entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %></td>
18 <td class="revision"><%= link_to(format_revision(entry.lastrev.name), :action => 'revision', :id => @project, :rev => entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %></td>
19 <td class="age"><%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %></td>
19 <td class="age"><%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %></td>
20 <td class="author"><%=h(entry.lastrev.author.to_s.split('<').first) if entry.lastrev %></td>
20 <td class="author"><%=h(entry.lastrev.author.to_s.split('<').first) if entry.lastrev %></td>
21 <% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %>
21 <% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %>
22 <td class="comments"><%=h truncate(changeset.comments, 50) unless changeset.nil? %></td>
22 <td class="comments"><%=h truncate(changeset.comments, 50) unless changeset.nil? %></td>
23 </tr>
23 </tr>
24 <% end %>
24 <% end %>
@@ -1,21 +1,21
1 <%= link_to 'root', :action => 'browse', :id => @project, :path => '', :rev => @rev %>
1 <%= link_to 'root', :action => 'browse', :id => @project, :path => '', :rev => @rev %>
2 <%
2 <%
3 dirs = path.split('/')
3 dirs = path.split('/')
4 if 'file' == kind
4 if 'file' == kind
5 filename = dirs.pop
5 filename = dirs.pop
6 end
6 end
7 link_path = ''
7 link_path = ''
8 dirs.each do |dir|
8 dirs.each do |dir|
9 next if dir.blank?
9 next if dir.blank?
10 link_path << '/' unless link_path.empty?
10 link_path << '/' unless link_path.empty?
11 link_path << "#{dir}"
11 link_path << "#{dir}"
12 %>
12 %>
13 / <%= link_to h(dir), :action => 'browse', :id => @project, :path => link_path, :rev => @rev %>
13 / <%= link_to h(dir), :action => 'browse', :id => @project, :path => to_path_param(link_path), :rev => @rev %>
14 <% end %>
14 <% end %>
15 <% if filename %>
15 <% if filename %>
16 / <%= link_to h(filename), :action => 'changes', :id => @project, :path => "#{link_path}/#{filename}", :rev => @rev %>
16 / <%= link_to h(filename), :action => 'changes', :id => @project, :path => to_path_param("#{link_path}/#{filename}"), :rev => @rev %>
17 <% end %>
17 <% end %>
18
18
19 <%= "@ #{revision}" if revision %>
19 <%= "@ #{revision}" if revision %>
20
20
21 <% html_title(with_leading_slash(path)) -%>
21 <% html_title(with_leading_slash(path)) -%>
@@ -1,28 +1,28
1 <% form_tag({:controller => 'repositories', :action => 'diff', :id => @project, :path => path}, :method => :get) do %>
1 <% form_tag({:controller => 'repositories', :action => 'diff', :id => @project, :path => to_path_param(path)}, :method => :get) do %>
2 <table class="list changesets">
2 <table class="list changesets">
3 <thead><tr>
3 <thead><tr>
4 <th>#</th>
4 <th>#</th>
5 <th></th>
5 <th></th>
6 <th></th>
6 <th></th>
7 <th><%= l(:label_date) %></th>
7 <th><%= l(:label_date) %></th>
8 <th><%= l(:field_author) %></th>
8 <th><%= l(:field_author) %></th>
9 <th><%= l(:field_comments) %></th>
9 <th><%= l(:field_comments) %></th>
10 </tr></thead>
10 </tr></thead>
11 <tbody>
11 <tbody>
12 <% show_diff = entry && entry.is_file? && revisions.size > 1 %>
12 <% show_diff = entry && entry.is_file? && revisions.size > 1 %>
13 <% line_num = 1 %>
13 <% line_num = 1 %>
14 <% revisions.each do |changeset| %>
14 <% revisions.each do |changeset| %>
15 <tr class="changeset <%= cycle 'odd', 'even' %>">
15 <tr class="changeset <%= cycle 'odd', 'even' %>">
16 <td class="id"><%= link_to format_revision(changeset.revision), :action => 'revision', :id => project, :rev => changeset.revision %></td>
16 <td class="id"><%= link_to format_revision(changeset.revision), :action => 'revision', :id => project, :rev => changeset.revision %></td>
17 <td class="checkbox"><%= radio_button_tag('rev', changeset.revision, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < revisions.size) %></td>
17 <td class="checkbox"><%= radio_button_tag('rev', changeset.revision, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < revisions.size) %></td>
18 <td class="checkbox"><%= radio_button_tag('rev_to', changeset.revision, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('cb-#{line_num}').checked==true) {$('cb-#{line_num-1}').checked=true;}") if show_diff && (line_num > 1) %></td>
18 <td class="checkbox"><%= radio_button_tag('rev_to', changeset.revision, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('cb-#{line_num}').checked==true) {$('cb-#{line_num-1}').checked=true;}") if show_diff && (line_num > 1) %></td>
19 <td class="committed_on"><%= format_time(changeset.committed_on) %></td>
19 <td class="committed_on"><%= format_time(changeset.committed_on) %></td>
20 <td class="author"><%=h changeset.committer.to_s.split('<').first %></td>
20 <td class="author"><%=h changeset.committer.to_s.split('<').first %></td>
21 <td class="comments"><%= textilizable(changeset.comments) %></td>
21 <td class="comments"><%= textilizable(changeset.comments) %></td>
22 </tr>
22 </tr>
23 <% line_num += 1 %>
23 <% line_num += 1 %>
24 <% end %>
24 <% end %>
25 </tbody>
25 </tbody>
26 </table>
26 </table>
27 <%= submit_tag(l(:label_view_diff), :name => nil) if show_diff %>
27 <%= submit_tag(l(:label_view_diff), :name => nil) if show_diff %>
28 <% end %>
28 <% end %>
@@ -1,19 +1,19
1 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2>
1 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2>
2
2
3 <h3><%=h @entry.name %></h3>
3 <h3><%=h @entry.name %></h3>
4
4
5 <p>
5 <p>
6 <% if @repository.supports_cat? %>
6 <% if @repository.supports_cat? %>
7 <%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => @path, :rev => @rev } %> |
7 <%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => to_path_param(@path), :rev => @rev } %> |
8 <% end %>
8 <% end %>
9 <% if @repository.supports_annotate? %>
9 <% if @repository.supports_annotate? %>
10 <%= link_to l(:button_annotate), {:action => 'annotate', :id => @project, :path => @path, :rev => @rev } %> |
10 <%= link_to l(:button_annotate), {:action => 'annotate', :id => @project, :path => to_path_param(@path), :rev => @rev } %> |
11 <% end %>
11 <% end %>
12 <%= link_to(l(:button_download), {:action => 'entry', :id => @project, :path => @path, :rev => @rev, :format => 'raw' }) if @repository.supports_cat? %>
12 <%= link_to(l(:button_download), {:action => 'entry', :id => @project, :path => to_path_param(@path), :rev => @rev, :format => 'raw' }) if @repository.supports_cat? %>
13 <%= "(#{number_to_human_size(@entry.size)})" if @entry.size %>
13 <%= "(#{number_to_human_size(@entry.size)})" if @entry.size %>
14 </p>
14 </p>
15
15
16 <%= render(:partial => 'revisions',
16 <%= render(:partial => 'revisions',
17 :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }) unless @changesets.empty? %>
17 :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }) unless @changesets.empty? %>
18
18
19 <% html_title(l(:label_change_plural)) -%>
19 <% html_title(l(:label_change_plural)) -%>
@@ -1,71 +1,71
1 <div class="contextual">
1 <div class="contextual">
2 &#171;
2 &#171;
3 <% unless @changeset.previous.nil? -%>
3 <% unless @changeset.previous.nil? -%>
4 <%= link_to l(:label_previous), :controller => 'repositories', :action => 'revision', :id => @project, :rev => @changeset.previous.revision %>
4 <%= link_to l(:label_previous), :controller => 'repositories', :action => 'revision', :id => @project, :rev => @changeset.previous.revision %>
5 <% else -%>
5 <% else -%>
6 <%= l(:label_previous) %>
6 <%= l(:label_previous) %>
7 <% end -%>
7 <% end -%>
8 |
8 |
9 <% unless @changeset.next.nil? -%>
9 <% unless @changeset.next.nil? -%>
10 <%= link_to l(:label_next), :controller => 'repositories', :action => 'revision', :id => @project, :rev => @changeset.next.revision %>
10 <%= link_to l(:label_next), :controller => 'repositories', :action => 'revision', :id => @project, :rev => @changeset.next.revision %>
11 <% else -%>
11 <% else -%>
12 <%= l(:label_next) %>
12 <%= l(:label_next) %>
13 <% end -%>
13 <% end -%>
14 &#187;&nbsp;
14 &#187;&nbsp;
15
15
16 <% form_tag({:controller => 'repositories', :action => 'revision', :id => @project, :rev => nil}, :method => :get) do %>
16 <% form_tag({:controller => 'repositories', :action => 'revision', :id => @project, :rev => nil}, :method => :get) do %>
17 <%= text_field_tag 'rev', @rev, :size => 5 %>
17 <%= text_field_tag 'rev', @rev, :size => 5 %>
18 <%= submit_tag 'OK', :name => nil %>
18 <%= submit_tag 'OK', :name => nil %>
19 <% end %>
19 <% end %>
20 </div>
20 </div>
21
21
22 <h2><%= l(:label_revision) %> <%= format_revision(@changeset.revision) %></h2>
22 <h2><%= l(:label_revision) %> <%= format_revision(@changeset.revision) %></h2>
23
23
24 <p><% if @changeset.scmid %>ID: <%= @changeset.scmid %><br /><% end %>
24 <p><% if @changeset.scmid %>ID: <%= @changeset.scmid %><br /><% end %>
25 <em><%= @changeset.committer.to_s.split('<').first %>, <%= format_time(@changeset.committed_on) %></em></p>
25 <em><%= @changeset.committer.to_s.split('<').first %>, <%= format_time(@changeset.committed_on) %></em></p>
26
26
27 <%= textilizable @changeset.comments %>
27 <%= textilizable @changeset.comments %>
28
28
29 <% if @changeset.issues.any? %>
29 <% if @changeset.issues.any? %>
30 <h3><%= l(:label_related_issues) %></h3>
30 <h3><%= l(:label_related_issues) %></h3>
31 <ul>
31 <ul>
32 <% @changeset.issues.each do |issue| %>
32 <% @changeset.issues.each do |issue| %>
33 <li><%= link_to_issue issue %>: <%=h issue.subject %></li>
33 <li><%= link_to_issue issue %>: <%=h issue.subject %></li>
34 <% end %>
34 <% end %>
35 </ul>
35 </ul>
36 <% end %>
36 <% end %>
37
37
38 <h3><%= l(:label_attachment_plural) %></h3>
38 <h3><%= l(:label_attachment_plural) %></h3>
39 <div style="float:right;">
39 <div style="float:right;">
40 <div class="square action_A"></div> <div style="float:left;"><%= l(:label_added) %>&nbsp;</div>
40 <div class="square action_A"></div> <div style="float:left;"><%= l(:label_added) %>&nbsp;</div>
41 <div class="square action_M"></div> <div style="float:left;"><%= l(:label_modified) %>&nbsp;</div>
41 <div class="square action_M"></div> <div style="float:left;"><%= l(:label_modified) %>&nbsp;</div>
42 <div class="square action_D"></div> <div style="float:left;"><%= l(:label_deleted) %>&nbsp;</div>
42 <div class="square action_D"></div> <div style="float:left;"><%= l(:label_deleted) %>&nbsp;</div>
43 </div>
43 </div>
44 <p><%= link_to(l(:label_view_diff), :action => 'diff', :id => @project, :path => "", :rev => @changeset.revision) if @changeset.changes.any? %></p>
44 <p><%= link_to(l(:label_view_diff), :action => 'diff', :id => @project, :path => "", :rev => @changeset.revision) if @changeset.changes.any? %></p>
45 <table class="list">
45 <table class="list">
46 <tbody>
46 <tbody>
47 <% @changes.each do |change| %>
47 <% @changes.each do |change| %>
48 <tr class="<%= cycle 'odd', 'even' %>">
48 <tr class="<%= cycle 'odd', 'even' %>">
49 <td><div class="square action_<%= change.action %>"></div>
49 <td><div class="square action_<%= change.action %>"></div>
50 <% if change.action == "D" -%>
50 <% if change.action == "D" -%>
51 <%= change.path -%>
51 <%= change.path -%>
52 <% else -%>
52 <% else -%>
53 <%= link_to change.path, :action => 'entry', :id => @project, :path => without_leading_slash(change.relative_path), :rev => @changeset.revision -%>
53 <%= link_to change.path, :action => 'entry', :id => @project, :path => without_leading_slash(change.relative_path), :rev => @changeset.revision -%>
54 <% end -%>
54 <% end -%>
55 <%= "(#{change.revision})" unless change.revision.blank? %></td>
55 <%= "(#{change.revision})" unless change.revision.blank? %></td>
56 <td align="right">
56 <td align="right">
57 <% if change.action == "M" %>
57 <% if change.action == "M" %>
58 <%= link_to l(:label_view_diff), :action => 'diff', :id => @project, :path => without_leading_slash(change.relative_path), :rev => @changeset.revision %>
58 <%= link_to l(:label_view_diff), :action => 'diff', :id => @project, :path => to_path_param(change.relative_path), :rev => @changeset.revision %>
59 <% end %>
59 <% end %>
60 </td>
60 </td>
61 </tr>
61 </tr>
62 <% end %>
62 <% end %>
63 </tbody>
63 </tbody>
64 </table>
64 </table>
65 <p class="pagination"><%= pagination_links_full @changes_pages %></p>
65 <p class="pagination"><%= pagination_links_full @changes_pages %></p>
66
66
67 <% content_for :header_tags do %>
67 <% content_for :header_tags do %>
68 <%= stylesheet_link_tag "scm" %>
68 <%= stylesheet_link_tag "scm" %>
69 <% end %>
69 <% end %>
70
70
71 <% html_title("#{l(:label_revision)} #{@changeset.revision}") -%>
71 <% html_title("#{l(:label_revision)} #{@changeset.revision}") -%>
@@ -1,19 +1,109
1 # Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb
1 # Don't change this file!
2 # Configure your app in config/environment.rb and config/environments/*.rb
2
3
3 unless defined?(RAILS_ROOT)
4 RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
4 root_path = File.join(File.dirname(__FILE__), '..')
5
5 unless RUBY_PLATFORM =~ /mswin32/
6 module Rails
6 require 'pathname'
7 class << self
7 root_path = Pathname.new(root_path).cleanpath(true).to_s
8 def boot!
9 unless booted?
10 preinitialize
11 pick_boot.run
12 end
13 end
14
15 def booted?
16 defined? Rails::Initializer
17 end
18
19 def pick_boot
20 (vendor_rails? ? VendorBoot : GemBoot).new
21 end
22
23 def vendor_rails?
24 File.exist?("#{RAILS_ROOT}/vendor/rails")
25 end
26
27 def preinitialize
28 load(preinitializer_path) if File.exist?(preinitializer_path)
29 end
30
31 def preinitializer_path
32 "#{RAILS_ROOT}/config/preinitializer.rb"
33 end
34 end
35
36 class Boot
37 def run
38 load_initializer
39 Rails::Initializer.run(:set_load_path)
8 end
40 end
9 RAILS_ROOT = root_path
10 end
41 end
11
42
12 if File.directory?("#{RAILS_ROOT}/vendor/rails")
43 class VendorBoot < Boot
44 def load_initializer
13 require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
45 require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
46 Rails::Initializer.run(:install_gem_spec_stubs)
47 end
48 end
49
50 class GemBoot < Boot
51 def load_initializer
52 self.class.load_rubygems
53 load_rails_gem
54 require 'initializer'
55 end
56
57 def load_rails_gem
58 if version = self.class.gem_version
59 gem 'rails', version
14 else
60 else
61 gem 'rails'
62 end
63 rescue Gem::LoadError => load_error
64 $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
65 exit 1
66 end
67
68 class << self
69 def rubygems_version
70 Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion
71 end
72
73 def gem_version
74 if defined? RAILS_GEM_VERSION
75 RAILS_GEM_VERSION
76 elsif ENV.include?('RAILS_GEM_VERSION')
77 ENV['RAILS_GEM_VERSION']
78 else
79 parse_gem_version(read_environment_rb)
80 end
81 end
82
83 def load_rubygems
15 require 'rubygems'
84 require 'rubygems'
16 require 'initializer'
85
86 unless rubygems_version >= '0.9.4'
87 $stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.)
88 exit 1
17 end
89 end
18
90
19 Rails::Initializer.run(:set_load_path)
91 rescue LoadError
92 $stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org)
93 exit 1
94 end
95
96 def parse_gem_version(text)
97 $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
98 end
99
100 private
101 def read_environment_rb
102 File.read("#{RAILS_ROOT}/config/environment.rb")
103 end
104 end
105 end
106 end
107
108 # All that for this:
109 Rails.boot!
@@ -1,102 +1,73
1 # Be sure to restart your web server when you modify this file.
1 # Be sure to restart your web server when you modify this file.
2
2
3 # Uncomment below to force Rails into production mode when
3 # Uncomment below to force Rails into production mode when
4 # you don't control web/app server and can't set it the proper way
4 # you don't control web/app server and can't set it the proper way
5 # ENV['RAILS_ENV'] ||= 'production'
5 # ENV['RAILS_ENV'] ||= 'production'
6
6
7 # Specifies gem version of Rails to use when vendor/rails is not present
7 # Specifies gem version of Rails to use when vendor/rails is not present
8 RAILS_GEM_VERSION = '2.0.2' unless defined? RAILS_GEM_VERSION
8 RAILS_GEM_VERSION = '2.1.0' unless defined? RAILS_GEM_VERSION
9
9
10 # Bootstrap the Rails environment, frameworks, and default configuration
10 # Bootstrap the Rails environment, frameworks, and default configuration
11 require File.join(File.dirname(__FILE__), 'boot')
11 require File.join(File.dirname(__FILE__), 'boot')
12
12
13 # Load Engine plugin if available
13 # Load Engine plugin if available
14 begin
14 begin
15 require File.join(File.dirname(__FILE__), '../vendor/plugins/engines/boot')
15 require File.join(File.dirname(__FILE__), '../vendor/plugins/engines/boot')
16 rescue LoadError
16 rescue LoadError
17 # Not available
17 # Not available
18 end
18 end
19
19
20 Rails::Initializer.run do |config|
20 Rails::Initializer.run do |config|
21 # Settings in config/environments/* take precedence those specified here
21 # Settings in config/environments/* take precedence those specified here
22
22
23 # Skip frameworks you're not going to use
23 # Skip frameworks you're not going to use
24 # config.frameworks -= [ :action_web_service, :action_mailer ]
24 # config.frameworks -= [ :action_web_service, :action_mailer ]
25
25
26 # Add additional load paths for sweepers
26 # Add additional load paths for sweepers
27 config.load_paths += %W( #{RAILS_ROOT}/app/sweepers )
27 config.load_paths += %W( #{RAILS_ROOT}/app/sweepers )
28
28
29 # Force all environments to use the same logger level
29 # Force all environments to use the same logger level
30 # (by default production uses :info, the others :debug)
30 # (by default production uses :info, the others :debug)
31 # config.log_level = :debug
31 # config.log_level = :debug
32
32
33 # Use the database for sessions instead of the file system
33 # Use the database for sessions instead of the file system
34 # (create the session table with 'rake db:sessions:create')
34 # (create the session table with 'rake db:sessions:create')
35 # config.action_controller.session_store = :active_record_store
35 # config.action_controller.session_store = :active_record_store
36 config.action_controller.session_store = :PStore
36 config.action_controller.session_store = :PStore
37
37
38 # Enable page/fragment caching by setting a file-based store
38 # Enable page/fragment caching by setting a file-based store
39 # (remember to create the caching directory and make it readable to the application)
39 # (remember to create the caching directory and make it readable to the application)
40 # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
40 # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
41
41
42 # Activate observers that should always be running
42 # Activate observers that should always be running
43 # config.active_record.observers = :cacher, :garbage_collector
43 # config.active_record.observers = :cacher, :garbage_collector
44 config.active_record.observers = :message_observer
44 config.active_record.observers = :message_observer
45
45
46 # Make Active Record use UTC-base instead of local time
46 # Make Active Record use UTC-base instead of local time
47 # config.active_record.default_timezone = :utc
47 # config.active_record.default_timezone = :utc
48
48
49 # Use Active Record's schema dumper instead of SQL when creating the test database
49 # Use Active Record's schema dumper instead of SQL when creating the test database
50 # (enables use of different database adapters for development and test environments)
50 # (enables use of different database adapters for development and test environments)
51 # config.active_record.schema_format = :ruby
51 # config.active_record.schema_format = :ruby
52
52
53 # See Rails::Configuration for more options
53 # See Rails::Configuration for more options
54
54
55 # SMTP server configuration
55 # SMTP server configuration
56 config.action_mailer.smtp_settings = {
56 config.action_mailer.smtp_settings = {
57 :address => "127.0.0.1",
57 :address => "127.0.0.1",
58 :port => 25,
58 :port => 25,
59 :domain => "somenet.foo",
59 :domain => "somenet.foo",
60 :authentication => :login,
60 :authentication => :login,
61 :user_name => "redmine@somenet.foo",
61 :user_name => "redmine@somenet.foo",
62 :password => "redmine",
62 :password => "redmine",
63 }
63 }
64
64
65 config.action_mailer.perform_deliveries = true
65 config.action_mailer.perform_deliveries = true
66
66
67 # Tell ActionMailer not to deliver emails to the real world.
67 # Tell ActionMailer not to deliver emails to the real world.
68 # The :test delivery method accumulates sent emails in the
68 # The :test delivery method accumulates sent emails in the
69 # ActionMailer::Base.deliveries array.
69 # ActionMailer::Base.deliveries array.
70 #config.action_mailer.delivery_method = :test
70 #config.action_mailer.delivery_method = :test
71 config.action_mailer.delivery_method = :smtp
71 config.action_mailer.delivery_method = :smtp
72
72
73 end
73 end
74
75 ActiveRecord::Errors.default_error_messages = {
76 :inclusion => "activerecord_error_inclusion",
77 :exclusion => "activerecord_error_exclusion",
78 :invalid => "activerecord_error_invalid",
79 :confirmation => "activerecord_error_confirmation",
80 :accepted => "activerecord_error_accepted",
81 :empty => "activerecord_error_empty",
82 :blank => "activerecord_error_blank",
83 :too_long => "activerecord_error_too_long",
84 :too_short => "activerecord_error_too_short",
85 :wrong_length => "activerecord_error_wrong_length",
86 :taken => "activerecord_error_taken",
87 :not_a_number => "activerecord_error_not_a_number"
88 }
89
90 ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| "#{html_tag}" }
91
92 Mime::SET << Mime::CSV unless Mime::SET.include?(Mime::CSV)
93 Mime::Type.register 'application/pdf', :pdf
94
95 GLoc.set_config :default_language => :en
96 GLoc.clear_strings
97 GLoc.set_kcode
98 GLoc.load_localized_strings
99 GLoc.set_config(:raise_string_not_found_errors => false)
100
101 require 'redmine'
102
@@ -1,15 +1,15
1 class AddEnumerationsPosition < ActiveRecord::Migration
1 class AddEnumerationsPosition < ActiveRecord::Migration
2 def self.up
2 def self.up
3 add_column(:enumerations, :position, :integer, :default => 1) unless Enumeration.column_names.include?('position')
3 add_column(:enumerations, :position, :integer, :default => 1) unless Enumeration.column_names.include?('position')
4 Enumeration.find(:all).group_by(&:opt).each_value do |enums|
4 Enumeration.find(:all).group_by(&:opt).each do |opt, enums|
5 enums.each_with_index do |enum, i|
5 enums.each_with_index do |enum, i|
6 # do not call model callbacks
6 # do not call model callbacks
7 Enumeration.update_all "position = #{i+1}", {:id => enum.id}
7 Enumeration.update_all "position = #{i+1}", {:id => enum.id}
8 end
8 end
9 end
9 end
10 end
10 end
11
11
12 def self.down
12 def self.down
13 remove_column :enumerations, :position
13 remove_column :enumerations, :position
14 end
14 end
15 end
15 end
@@ -1,15 +1,15
1 class AddCustomFieldsPosition < ActiveRecord::Migration
1 class AddCustomFieldsPosition < ActiveRecord::Migration
2 def self.up
2 def self.up
3 add_column(:custom_fields, :position, :integer, :default => 1)
3 add_column(:custom_fields, :position, :integer, :default => 1)
4 CustomField.find(:all).group_by(&:type).each_value do |fields|
4 CustomField.find(:all).group_by(&:type).each do |t, fields|
5 fields.each_with_index do |field, i|
5 fields.each_with_index do |field, i|
6 # do not call model callbacks
6 # do not call model callbacks
7 CustomField.update_all "position = #{i+1}", {:id => field.id}
7 CustomField.update_all "position = #{i+1}", {:id => field.id}
8 end
8 end
9 end
9 end
10 end
10 end
11
11
12 def self.down
12 def self.down
13 remove_column :custom_fields, :position
13 remove_column :custom_fields, :position
14 end
14 end
15 end
15 end
@@ -1,51 +1,51
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'action_view/helpers/form_helper'
18 require 'action_view/helpers/form_helper'
19
19
20 class TabularFormBuilder < ActionView::Helpers::FormBuilder
20 class TabularFormBuilder < ActionView::Helpers::FormBuilder
21 include GLoc
21 include GLoc
22
22
23 def initialize(object_name, object, template, options, proc)
23 def initialize(object_name, object, template, options, proc)
24 set_language_if_valid options.delete(:lang)
24 set_language_if_valid options.delete(:lang)
25 @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
25 super
26 end
26 end
27
27
28 (field_helpers - %w(radio_button hidden_field) + %w(date_select)).each do |selector|
28 (field_helpers - %w(radio_button hidden_field) + %w(date_select)).each do |selector|
29 src = <<-END_SRC
29 src = <<-END_SRC
30 def #{selector}(field, options = {})
30 def #{selector}(field, options = {})
31 return super if options.delete :no_label
31 return super if options.delete :no_label
32 label_text = l(options[:label]) if options[:label]
32 label_text = l(options[:label]) if options[:label]
33 label_text ||= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym)
33 label_text ||= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym)
34 label_text << @template.content_tag("span", " *", :class => "required") if options.delete(:required)
34 label_text << @template.content_tag("span", " *", :class => "required") if options.delete(:required)
35 label = @template.content_tag("label", label_text,
35 label = @template.content_tag("label", label_text,
36 :class => (@object && @object.errors[field] ? "error" : nil),
36 :class => (@object && @object.errors[field] ? "error" : nil),
37 :for => (@object_name.to_s + "_" + field.to_s))
37 :for => (@object_name.to_s + "_" + field.to_s))
38 label + super
38 label + super
39 end
39 end
40 END_SRC
40 END_SRC
41 class_eval src, __FILE__, __LINE__
41 class_eval src, __FILE__, __LINE__
42 end
42 end
43
43
44 def select(field, choices, options = {}, html_options = {})
44 def select(field, choices, options = {}, html_options = {})
45 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
45 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
46 label = @template.content_tag("label", label_text,
46 label = @template.content_tag("label", label_text,
47 :class => (@object && @object.errors[field] ? "error" : nil),
47 :class => (@object && @object.errors[field] ? "error" : nil),
48 :for => (@object_name.to_s + "_" + field.to_s))
48 :for => (@object_name.to_s + "_" + field.to_s))
49 label + super
49 label + super
50 end
50 end
51 end
51 end
@@ -1,307 +1,307
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'projects_controller'
19 require 'projects_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class ProjectsController; def rescue_action(e) raise e end; end
22 class ProjectsController; def rescue_action(e) raise e end; end
23
23
24 class ProjectsControllerTest < Test::Unit::TestCase
24 class ProjectsControllerTest < Test::Unit::TestCase
25 fixtures :projects, :versions, :users, :roles, :members, :issues, :journals, :journal_details,
25 fixtures :projects, :versions, :users, :roles, :members, :issues, :journals, :journal_details,
26 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages
26 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages
27
27
28 def setup
28 def setup
29 @controller = ProjectsController.new
29 @controller = ProjectsController.new
30 @request = ActionController::TestRequest.new
30 @request = ActionController::TestRequest.new
31 @response = ActionController::TestResponse.new
31 @response = ActionController::TestResponse.new
32 @request.session[:user_id] = nil
32 @request.session[:user_id] = nil
33 end
33 end
34
34
35 def test_index
35 def test_index
36 get :index
36 get :index
37 assert_response :success
37 assert_response :success
38 assert_template 'index'
38 assert_template 'index'
39 assert_not_nil assigns(:project_tree)
39 assert_not_nil assigns(:project_tree)
40 # Root project as hash key
40 # Root project as hash key
41 assert assigns(:project_tree).has_key?(Project.find(1))
41 assert assigns(:project_tree).keys.include?(Project.find(1))
42 # Subproject in corresponding value
42 # Subproject in corresponding value
43 assert assigns(:project_tree)[Project.find(1)].include?(Project.find(3))
43 assert assigns(:project_tree)[Project.find(1)].include?(Project.find(3))
44 end
44 end
45
45
46 def test_show_by_id
46 def test_show_by_id
47 get :show, :id => 1
47 get :show, :id => 1
48 assert_response :success
48 assert_response :success
49 assert_template 'show'
49 assert_template 'show'
50 assert_not_nil assigns(:project)
50 assert_not_nil assigns(:project)
51 end
51 end
52
52
53 def test_show_by_identifier
53 def test_show_by_identifier
54 get :show, :id => 'ecookbook'
54 get :show, :id => 'ecookbook'
55 assert_response :success
55 assert_response :success
56 assert_template 'show'
56 assert_template 'show'
57 assert_not_nil assigns(:project)
57 assert_not_nil assigns(:project)
58 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
58 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
59 end
59 end
60
60
61 def test_private_subprojects_hidden
61 def test_private_subprojects_hidden
62 get :show, :id => 'ecookbook'
62 get :show, :id => 'ecookbook'
63 assert_response :success
63 assert_response :success
64 assert_template 'show'
64 assert_template 'show'
65 assert_no_tag :tag => 'a', :content => /Private child/
65 assert_no_tag :tag => 'a', :content => /Private child/
66 end
66 end
67
67
68 def test_private_subprojects_visible
68 def test_private_subprojects_visible
69 @request.session[:user_id] = 2 # manager who is a member of the private subproject
69 @request.session[:user_id] = 2 # manager who is a member of the private subproject
70 get :show, :id => 'ecookbook'
70 get :show, :id => 'ecookbook'
71 assert_response :success
71 assert_response :success
72 assert_template 'show'
72 assert_template 'show'
73 assert_tag :tag => 'a', :content => /Private child/
73 assert_tag :tag => 'a', :content => /Private child/
74 end
74 end
75
75
76 def test_settings
76 def test_settings
77 @request.session[:user_id] = 2 # manager
77 @request.session[:user_id] = 2 # manager
78 get :settings, :id => 1
78 get :settings, :id => 1
79 assert_response :success
79 assert_response :success
80 assert_template 'settings'
80 assert_template 'settings'
81 end
81 end
82
82
83 def test_edit
83 def test_edit
84 @request.session[:user_id] = 2 # manager
84 @request.session[:user_id] = 2 # manager
85 post :edit, :id => 1, :project => {:name => 'Test changed name',
85 post :edit, :id => 1, :project => {:name => 'Test changed name',
86 :issue_custom_field_ids => ['']}
86 :issue_custom_field_ids => ['']}
87 assert_redirected_to 'projects/settings/ecookbook'
87 assert_redirected_to 'projects/settings/ecookbook'
88 project = Project.find(1)
88 project = Project.find(1)
89 assert_equal 'Test changed name', project.name
89 assert_equal 'Test changed name', project.name
90 end
90 end
91
91
92 def test_get_destroy
92 def test_get_destroy
93 @request.session[:user_id] = 1 # admin
93 @request.session[:user_id] = 1 # admin
94 get :destroy, :id => 1
94 get :destroy, :id => 1
95 assert_response :success
95 assert_response :success
96 assert_template 'destroy'
96 assert_template 'destroy'
97 assert_not_nil Project.find_by_id(1)
97 assert_not_nil Project.find_by_id(1)
98 end
98 end
99
99
100 def test_post_destroy
100 def test_post_destroy
101 @request.session[:user_id] = 1 # admin
101 @request.session[:user_id] = 1 # admin
102 post :destroy, :id => 1, :confirm => 1
102 post :destroy, :id => 1, :confirm => 1
103 assert_redirected_to 'admin/projects'
103 assert_redirected_to 'admin/projects'
104 assert_nil Project.find_by_id(1)
104 assert_nil Project.find_by_id(1)
105 end
105 end
106
106
107 def test_list_files
107 def test_list_files
108 get :list_files, :id => 1
108 get :list_files, :id => 1
109 assert_response :success
109 assert_response :success
110 assert_template 'list_files'
110 assert_template 'list_files'
111 assert_not_nil assigns(:versions)
111 assert_not_nil assigns(:versions)
112 end
112 end
113
113
114 def test_changelog
114 def test_changelog
115 get :changelog, :id => 1
115 get :changelog, :id => 1
116 assert_response :success
116 assert_response :success
117 assert_template 'changelog'
117 assert_template 'changelog'
118 assert_not_nil assigns(:versions)
118 assert_not_nil assigns(:versions)
119 end
119 end
120
120
121 def test_roadmap
121 def test_roadmap
122 get :roadmap, :id => 1
122 get :roadmap, :id => 1
123 assert_response :success
123 assert_response :success
124 assert_template 'roadmap'
124 assert_template 'roadmap'
125 assert_not_nil assigns(:versions)
125 assert_not_nil assigns(:versions)
126 # Version with no date set appears
126 # Version with no date set appears
127 assert assigns(:versions).include?(Version.find(3))
127 assert assigns(:versions).include?(Version.find(3))
128 # Completed version doesn't appear
128 # Completed version doesn't appear
129 assert !assigns(:versions).include?(Version.find(1))
129 assert !assigns(:versions).include?(Version.find(1))
130 end
130 end
131
131
132 def test_roadmap_with_completed_versions
132 def test_roadmap_with_completed_versions
133 get :roadmap, :id => 1, :completed => 1
133 get :roadmap, :id => 1, :completed => 1
134 assert_response :success
134 assert_response :success
135 assert_template 'roadmap'
135 assert_template 'roadmap'
136 assert_not_nil assigns(:versions)
136 assert_not_nil assigns(:versions)
137 # Version with no date set appears
137 # Version with no date set appears
138 assert assigns(:versions).include?(Version.find(3))
138 assert assigns(:versions).include?(Version.find(3))
139 # Completed version appears
139 # Completed version appears
140 assert assigns(:versions).include?(Version.find(1))
140 assert assigns(:versions).include?(Version.find(1))
141 end
141 end
142
142
143 def test_project_activity
143 def test_project_activity
144 get :activity, :id => 1, :with_subprojects => 0
144 get :activity, :id => 1, :with_subprojects => 0
145 assert_response :success
145 assert_response :success
146 assert_template 'activity'
146 assert_template 'activity'
147 assert_not_nil assigns(:events_by_day)
147 assert_not_nil assigns(:events_by_day)
148 assert_not_nil assigns(:events)
148 assert_not_nil assigns(:events)
149
149
150 # subproject issue not included by default
150 # subproject issue not included by default
151 assert !assigns(:events).include?(Issue.find(5))
151 assert !assigns(:events).include?(Issue.find(5))
152
152
153 assert_tag :tag => "h3",
153 assert_tag :tag => "h3",
154 :content => /#{2.days.ago.to_date.day}/,
154 :content => /#{2.days.ago.to_date.day}/,
155 :sibling => { :tag => "dl",
155 :sibling => { :tag => "dl",
156 :child => { :tag => "dt",
156 :child => { :tag => "dt",
157 :attributes => { :class => /issue-edit/ },
157 :attributes => { :class => /issue-edit/ },
158 :child => { :tag => "a",
158 :child => { :tag => "a",
159 :content => /(#{IssueStatus.find(2).name})/,
159 :content => /(#{IssueStatus.find(2).name})/,
160 }
160 }
161 }
161 }
162 }
162 }
163
163
164 get :activity, :id => 1, :from => 3.days.ago.to_date
164 get :activity, :id => 1, :from => 3.days.ago.to_date
165 assert_response :success
165 assert_response :success
166 assert_template 'activity'
166 assert_template 'activity'
167 assert_not_nil assigns(:events_by_day)
167 assert_not_nil assigns(:events_by_day)
168
168
169 assert_tag :tag => "h3",
169 assert_tag :tag => "h3",
170 :content => /#{3.day.ago.to_date.day}/,
170 :content => /#{3.day.ago.to_date.day}/,
171 :sibling => { :tag => "dl",
171 :sibling => { :tag => "dl",
172 :child => { :tag => "dt",
172 :child => { :tag => "dt",
173 :attributes => { :class => /issue/ },
173 :attributes => { :class => /issue/ },
174 :child => { :tag => "a",
174 :child => { :tag => "a",
175 :content => /#{Issue.find(1).subject}/,
175 :content => /#{Issue.find(1).subject}/,
176 }
176 }
177 }
177 }
178 }
178 }
179 end
179 end
180
180
181 def test_activity_with_subprojects
181 def test_activity_with_subprojects
182 get :activity, :id => 1, :with_subprojects => 1
182 get :activity, :id => 1, :with_subprojects => 1
183 assert_response :success
183 assert_response :success
184 assert_template 'activity'
184 assert_template 'activity'
185 assert_not_nil assigns(:events)
185 assert_not_nil assigns(:events)
186
186
187 assert assigns(:events).include?(Issue.find(1))
187 assert assigns(:events).include?(Issue.find(1))
188 assert !assigns(:events).include?(Issue.find(4))
188 assert !assigns(:events).include?(Issue.find(4))
189 # subproject issue
189 # subproject issue
190 assert assigns(:events).include?(Issue.find(5))
190 assert assigns(:events).include?(Issue.find(5))
191 end
191 end
192
192
193 def test_global_activity_anonymous
193 def test_global_activity_anonymous
194 get :activity
194 get :activity
195 assert_response :success
195 assert_response :success
196 assert_template 'activity'
196 assert_template 'activity'
197 assert_not_nil assigns(:events)
197 assert_not_nil assigns(:events)
198
198
199 assert assigns(:events).include?(Issue.find(1))
199 assert assigns(:events).include?(Issue.find(1))
200 # Issue of a private project
200 # Issue of a private project
201 assert !assigns(:events).include?(Issue.find(4))
201 assert !assigns(:events).include?(Issue.find(4))
202 end
202 end
203
203
204 def test_global_activity_logged_user
204 def test_global_activity_logged_user
205 @request.session[:user_id] = 2 # manager
205 @request.session[:user_id] = 2 # manager
206 get :activity
206 get :activity
207 assert_response :success
207 assert_response :success
208 assert_template 'activity'
208 assert_template 'activity'
209 assert_not_nil assigns(:events)
209 assert_not_nil assigns(:events)
210
210
211 assert assigns(:events).include?(Issue.find(1))
211 assert assigns(:events).include?(Issue.find(1))
212 # Issue of a private project the user belongs to
212 # Issue of a private project the user belongs to
213 assert assigns(:events).include?(Issue.find(4))
213 assert assigns(:events).include?(Issue.find(4))
214 end
214 end
215
215
216
216
217 def test_global_activity_with_all_types
217 def test_global_activity_with_all_types
218 get :activity, :show_issues => 1, :show_news => 1, :show_files => 1, :show_documents => 1, :show_changesets => 1, :show_wiki_pages => 1, :show_messages => 1
218 get :activity, :show_issues => 1, :show_news => 1, :show_files => 1, :show_documents => 1, :show_changesets => 1, :show_wiki_pages => 1, :show_messages => 1
219 assert_response :success
219 assert_response :success
220 assert_template 'activity'
220 assert_template 'activity'
221 assert_not_nil assigns(:events)
221 assert_not_nil assigns(:events)
222
222
223 assert assigns(:events).include?(Issue.find(1))
223 assert assigns(:events).include?(Issue.find(1))
224 assert !assigns(:events).include?(Issue.find(4))
224 assert !assigns(:events).include?(Issue.find(4))
225 assert assigns(:events).include?(Message.find(5))
225 assert assigns(:events).include?(Message.find(5))
226 end
226 end
227
227
228 def test_calendar
228 def test_calendar
229 get :calendar, :id => 1
229 get :calendar, :id => 1
230 assert_response :success
230 assert_response :success
231 assert_template 'calendar'
231 assert_template 'calendar'
232 assert_not_nil assigns(:calendar)
232 assert_not_nil assigns(:calendar)
233 end
233 end
234
234
235 def test_calendar_with_subprojects_should_not_show_private_subprojects
235 def test_calendar_with_subprojects_should_not_show_private_subprojects
236 get :calendar, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
236 get :calendar, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
237 assert_response :success
237 assert_response :success
238 assert_template 'calendar'
238 assert_template 'calendar'
239 assert_not_nil assigns(:calendar)
239 assert_not_nil assigns(:calendar)
240 assert_no_tag :tag => 'a', :content => /#6/
240 assert_no_tag :tag => 'a', :content => /#6/
241 end
241 end
242
242
243 def test_calendar_with_subprojects_should_show_private_subprojects
243 def test_calendar_with_subprojects_should_show_private_subprojects
244 @request.session[:user_id] = 2
244 @request.session[:user_id] = 2
245 get :calendar, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
245 get :calendar, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
246 assert_response :success
246 assert_response :success
247 assert_template 'calendar'
247 assert_template 'calendar'
248 assert_not_nil assigns(:calendar)
248 assert_not_nil assigns(:calendar)
249 assert_tag :tag => 'a', :content => /#6/
249 assert_tag :tag => 'a', :content => /#6/
250 end
250 end
251
251
252 def test_gantt
252 def test_gantt
253 get :gantt, :id => 1
253 get :gantt, :id => 1
254 assert_response :success
254 assert_response :success
255 assert_template 'gantt.rhtml'
255 assert_template 'gantt.rhtml'
256 events = assigns(:events)
256 events = assigns(:events)
257 assert_not_nil events
257 assert_not_nil events
258 # Issue with start and due dates
258 # Issue with start and due dates
259 i = Issue.find(1)
259 i = Issue.find(1)
260 assert_not_nil i.due_date
260 assert_not_nil i.due_date
261 assert events.include?(Issue.find(1))
261 assert events.include?(Issue.find(1))
262 # Issue with without due date but targeted to a version with date
262 # Issue with without due date but targeted to a version with date
263 i = Issue.find(2)
263 i = Issue.find(2)
264 assert_nil i.due_date
264 assert_nil i.due_date
265 assert events.include?(i)
265 assert events.include?(i)
266 end
266 end
267
267
268 def test_gantt_with_subprojects_should_not_show_private_subprojects
268 def test_gantt_with_subprojects_should_not_show_private_subprojects
269 get :gantt, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
269 get :gantt, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
270 assert_response :success
270 assert_response :success
271 assert_template 'gantt.rhtml'
271 assert_template 'gantt.rhtml'
272 assert_not_nil assigns(:events)
272 assert_not_nil assigns(:events)
273 assert_no_tag :tag => 'a', :content => /#6/
273 assert_no_tag :tag => 'a', :content => /#6/
274 end
274 end
275
275
276 def test_gantt_with_subprojects_should_show_private_subprojects
276 def test_gantt_with_subprojects_should_show_private_subprojects
277 @request.session[:user_id] = 2
277 @request.session[:user_id] = 2
278 get :gantt, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
278 get :gantt, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
279 assert_response :success
279 assert_response :success
280 assert_template 'gantt.rhtml'
280 assert_template 'gantt.rhtml'
281 assert_not_nil assigns(:events)
281 assert_not_nil assigns(:events)
282 assert_tag :tag => 'a', :content => /#6/
282 assert_tag :tag => 'a', :content => /#6/
283 end
283 end
284
284
285 def test_gantt_export_to_pdf
285 def test_gantt_export_to_pdf
286 get :gantt, :id => 1, :format => 'pdf'
286 get :gantt, :id => 1, :format => 'pdf'
287 assert_response :success
287 assert_response :success
288 assert_template 'gantt.rfpdf'
288 assert_template 'gantt.rfpdf'
289 assert_equal 'application/pdf', @response.content_type
289 assert_equal 'application/pdf', @response.content_type
290 assert_not_nil assigns(:events)
290 assert_not_nil assigns(:events)
291 end
291 end
292
292
293 def test_archive
293 def test_archive
294 @request.session[:user_id] = 1 # admin
294 @request.session[:user_id] = 1 # admin
295 post :archive, :id => 1
295 post :archive, :id => 1
296 assert_redirected_to 'admin/projects'
296 assert_redirected_to 'admin/projects'
297 assert !Project.find(1).active?
297 assert !Project.find(1).active?
298 end
298 end
299
299
300 def test_unarchive
300 def test_unarchive
301 @request.session[:user_id] = 1 # admin
301 @request.session[:user_id] = 1 # admin
302 Project.find(1).archive
302 Project.find(1).archive
303 post :unarchive, :id => 1
303 post :unarchive, :id => 1
304 assert_redirected_to 'admin/projects'
304 assert_redirected_to 'admin/projects'
305 assert Project.find(1).active?
305 assert Project.find(1).active?
306 end
306 end
307 end
307 end
@@ -1,89 +1,92
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require "#{File.dirname(__FILE__)}/../test_helper"
18 require "#{File.dirname(__FILE__)}/../test_helper"
19
19
20 class IssuesTest < ActionController::IntegrationTest
20 class IssuesTest < ActionController::IntegrationTest
21 fixtures :projects,
21 fixtures :projects,
22 :users,
22 :users,
23 :roles,
24 :members,
23 :trackers,
25 :trackers,
24 :projects_trackers,
26 :projects_trackers,
27 :enabled_modules,
25 :issue_statuses,
28 :issue_statuses,
26 :issues,
29 :issues,
27 :enumerations,
30 :enumerations,
28 :custom_fields,
31 :custom_fields,
29 :custom_values,
32 :custom_values,
30 :custom_fields_trackers
33 :custom_fields_trackers
31
34
32 # create an issue
35 # create an issue
33 def test_add_issue
36 def test_add_issue
34 log_user('jsmith', 'jsmith')
37 log_user('jsmith', 'jsmith')
35 get 'projects/1/issues/new', :tracker_id => '1'
38 get 'projects/1/issues/new', :tracker_id => '1'
36 assert_response :success
39 assert_response :success
37 assert_template 'issues/new'
40 assert_template 'issues/new'
38
41
39 post 'projects/1/issues/new', :tracker_id => "1",
42 post 'projects/1/issues/new', :tracker_id => "1",
40 :issue => { :start_date => "2006-12-26",
43 :issue => { :start_date => "2006-12-26",
41 :priority_id => "3",
44 :priority_id => "3",
42 :subject => "new test issue",
45 :subject => "new test issue",
43 :category_id => "",
46 :category_id => "",
44 :description => "new issue",
47 :description => "new issue",
45 :done_ratio => "0",
48 :done_ratio => "0",
46 :due_date => "",
49 :due_date => "",
47 :assigned_to_id => "" },
50 :assigned_to_id => "" },
48 :custom_fields => {'2' => 'Value for field 2'}
51 :custom_fields => {'2' => 'Value for field 2'}
49 # find created issue
52 # find created issue
50 issue = Issue.find_by_subject("new test issue")
53 issue = Issue.find_by_subject("new test issue")
51 assert_kind_of Issue, issue
54 assert_kind_of Issue, issue
52
55
53 # check redirection
56 # check redirection
54 assert_redirected_to "issues/show"
57 assert_redirected_to "issues/show"
55 follow_redirect!
58 follow_redirect!
56 assert_equal issue, assigns(:issue)
59 assert_equal issue, assigns(:issue)
57
60
58 # check issue attributes
61 # check issue attributes
59 assert_equal 'jsmith', issue.author.login
62 assert_equal 'jsmith', issue.author.login
60 assert_equal 1, issue.project.id
63 assert_equal 1, issue.project.id
61 assert_equal 1, issue.status.id
64 assert_equal 1, issue.status.id
62 end
65 end
63
66
64 # add then remove 2 attachments to an issue
67 # add then remove 2 attachments to an issue
65 def test_issue_attachements
68 def test_issue_attachements
66 log_user('jsmith', 'jsmith')
69 log_user('jsmith', 'jsmith')
67 set_tmp_attachments_directory
70 set_tmp_attachments_directory
68
71
69 post 'issues/edit/1',
72 post 'issues/edit/1',
70 :notes => 'Some notes',
73 :notes => 'Some notes',
71 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain'), 'description' => 'This is an attachment'}}
74 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain'), 'description' => 'This is an attachment'}}
72 assert_redirected_to "issues/show/1"
75 assert_redirected_to "issues/show/1"
73
76
74 # make sure attachment was saved
77 # make sure attachment was saved
75 attachment = Issue.find(1).attachments.find_by_filename("testfile.txt")
78 attachment = Issue.find(1).attachments.find_by_filename("testfile.txt")
76 assert_kind_of Attachment, attachment
79 assert_kind_of Attachment, attachment
77 assert_equal Issue.find(1), attachment.container
80 assert_equal Issue.find(1), attachment.container
78 assert_equal 'This is an attachment', attachment.description
81 assert_equal 'This is an attachment', attachment.description
79 # verify the size of the attachment stored in db
82 # verify the size of the attachment stored in db
80 #assert_equal file_data_1.length, attachment.filesize
83 #assert_equal file_data_1.length, attachment.filesize
81 # verify that the attachment was written to disk
84 # verify that the attachment was written to disk
82 assert File.exist?(attachment.diskfile)
85 assert File.exist?(attachment.diskfile)
83
86
84 # remove the attachments
87 # remove the attachments
85 Issue.find(1).attachments.each(&:destroy)
88 Issue.find(1).attachments.each(&:destroy)
86 assert_equal 0, Issue.find(1).attachments.length
89 assert_equal 0, Issue.find(1).attachments.length
87 end
90 end
88
91
89 end
92 end
@@ -1,84 +1,67
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 ENV["RAILS_ENV"] ||= "test"
18 ENV["RAILS_ENV"] ||= "test"
19 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
19 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
20 require 'test_help'
20 require 'test_help'
21 require File.expand_path(File.dirname(__FILE__) + '/helper_testcase')
21 require File.expand_path(File.dirname(__FILE__) + '/helper_testcase')
22
22
23 class Test::Unit::TestCase
23 class Test::Unit::TestCase
24 # Transactional fixtures accelerate your tests by wrapping each test method
24 # Transactional fixtures accelerate your tests by wrapping each test method
25 # in a transaction that's rolled back on completion. This ensures that the
25 # in a transaction that's rolled back on completion. This ensures that the
26 # test database remains unchanged so your fixtures don't have to be reloaded
26 # test database remains unchanged so your fixtures don't have to be reloaded
27 # between every test method. Fewer database queries means faster tests.
27 # between every test method. Fewer database queries means faster tests.
28 #
28 #
29 # Read Mike Clark's excellent walkthrough at
29 # Read Mike Clark's excellent walkthrough at
30 # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
30 # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
31 #
31 #
32 # Every Active Record database supports transactions except MyISAM tables
32 # Every Active Record database supports transactions except MyISAM tables
33 # in MySQL. Turn off transactional fixtures in this case; however, if you
33 # in MySQL. Turn off transactional fixtures in this case; however, if you
34 # don't care one way or the other, switching from MyISAM to InnoDB tables
34 # don't care one way or the other, switching from MyISAM to InnoDB tables
35 # is recommended.
35 # is recommended.
36 self.use_transactional_fixtures = true
36 self.use_transactional_fixtures = true
37
37
38 # Instantiated fixtures are slow, but give you @david where otherwise you
38 # Instantiated fixtures are slow, but give you @david where otherwise you
39 # would need people(:david). If you don't want to migrate your existing
39 # would need people(:david). If you don't want to migrate your existing
40 # test cases which use the @david style and don't mind the speed hit (each
40 # test cases which use the @david style and don't mind the speed hit (each
41 # instantiated fixtures translates to a database query per test method),
41 # instantiated fixtures translates to a database query per test method),
42 # then set this back to true.
42 # then set this back to true.
43 self.use_instantiated_fixtures = false
43 self.use_instantiated_fixtures = false
44
44
45 # Add more helper methods to be used by all tests here...
45 # Add more helper methods to be used by all tests here...
46
46
47 def log_user(login, password)
47 def log_user(login, password)
48 get "/account/login"
48 get "/account/login"
49 assert_equal nil, session[:user_id]
49 assert_equal nil, session[:user_id]
50 assert_response :success
50 assert_response :success
51 assert_template "account/login"
51 assert_template "account/login"
52 post "/account/login", :username => login, :password => password
52 post "/account/login", :username => login, :password => password
53 assert_redirected_to "my/page"
53 assert_redirected_to "my/page"
54 assert_equal login, User.find(session[:user_id]).login
54 assert_equal login, User.find(session[:user_id]).login
55 end
55 end
56
56
57 def test_uploaded_file(name, mime)
57 def test_uploaded_file(name, mime)
58 ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + "/files/#{name}", mime)
58 ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + "/files/#{name}", mime)
59 end
59 end
60
60
61 # Use a temporary directory for attachment related tests
61 # Use a temporary directory for attachment related tests
62 def set_tmp_attachments_directory
62 def set_tmp_attachments_directory
63 Dir.mkdir "#{RAILS_ROOT}/tmp/test" unless File.directory?("#{RAILS_ROOT}/tmp/test")
63 Dir.mkdir "#{RAILS_ROOT}/tmp/test" unless File.directory?("#{RAILS_ROOT}/tmp/test")
64 Dir.mkdir "#{RAILS_ROOT}/tmp/test/attachments" unless File.directory?("#{RAILS_ROOT}/tmp/test/attachments")
64 Dir.mkdir "#{RAILS_ROOT}/tmp/test/attachments" unless File.directory?("#{RAILS_ROOT}/tmp/test/attachments")
65 Attachment.storage_path = "#{RAILS_ROOT}/tmp/test/attachments"
65 Attachment.storage_path = "#{RAILS_ROOT}/tmp/test/attachments"
66 end
66 end
67 end
67 end
68
69
70 # ActionController::TestUploadedFile bug
71 # see http://dev.rubyonrails.org/ticket/4635
72 class String
73 def original_filename
74 "testfile.txt"
75 end
76
77 def content_type
78 "text/plain"
79 end
80
81 def read
82 self.to_s
83 end
84 end
@@ -1,33 +1,33
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class RoleTest < Test::Unit::TestCase
20 class RoleTest < Test::Unit::TestCase
21 fixtures :roles, :workflows
21 fixtures :roles, :workflows
22
22
23 def test_copy_workflows
23 def test_copy_workflows
24 source = Role.find(1)
24 source = Role.find(1)
25 assert_equal 90, source.workflows.size
25 assert_equal 90, source.workflows.size
26
26
27 target = Role.new(:name => 'Target')
27 target = Role.new(:name => 'Target')
28 assert target.save
28 assert target.save
29 assert target.workflows.copy(source)
29 target.workflows.copy(source)
30 target.reload
30 target.reload
31 assert_equal 90, target.workflows.size
31 assert_equal 90, target.workflows.size
32 end
32 end
33 end
33 end
@@ -1,33 +1,33
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class TrackerTest < Test::Unit::TestCase
20 class TrackerTest < Test::Unit::TestCase
21 fixtures :trackers, :workflows
21 fixtures :trackers, :workflows
22
22
23 def test_copy_workflows
23 def test_copy_workflows
24 source = Tracker.find(1)
24 source = Tracker.find(1)
25 assert_equal 89, source.workflows.size
25 assert_equal 89, source.workflows.size
26
26
27 target = Tracker.new(:name => 'Target')
27 target = Tracker.new(:name => 'Target')
28 assert target.save
28 assert target.save
29 assert target.workflows.copy(source)
29 target.workflows.copy(source)
30 target.reload
30 target.reload
31 assert_equal 89, target.workflows.size
31 assert_equal 89, target.workflows.size
32 end
32 end
33 end
33 end
1 NO CONTENT: modified file
NO CONTENT: modified file
@@ -1,511 +1,564
1 # Copyright (c) 2005 Rick Olson
1 # Copyright (c) 2005 Rick Olson
2 #
2 #
3 # Permission is hereby granted, free of charge, to any person obtaining
3 # Permission is hereby granted, free of charge, to any person obtaining
4 # a copy of this software and associated documentation files (the
4 # a copy of this software and associated documentation files (the
5 # "Software"), to deal in the Software without restriction, including
5 # "Software"), to deal in the Software without restriction, including
6 # without limitation the rights to use, copy, modify, merge, publish,
6 # without limitation the rights to use, copy, modify, merge, publish,
7 # distribute, sublicense, and/or sell copies of the Software, and to
7 # distribute, sublicense, and/or sell copies of the Software, and to
8 # permit persons to whom the Software is furnished to do so, subject to
8 # permit persons to whom the Software is furnished to do so, subject to
9 # the following conditions:
9 # the following conditions:
10 #
10 #
11 # The above copyright notice and this permission notice shall be
11 # The above copyright notice and this permission notice shall be
12 # included in all copies or substantial portions of the Software.
12 # included in all copies or substantial portions of the Software.
13 #
13 #
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
15 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
17 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
19 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
22 module ActiveRecord #:nodoc:
22 module ActiveRecord #:nodoc:
23 module Acts #:nodoc:
23 module Acts #:nodoc:
24 # Specify this act if you want to save a copy of the row in a versioned table. This assumes there is a
24 # Specify this act if you want to save a copy of the row in a versioned table. This assumes there is a
25 # versioned table ready and that your model has a version field. This works with optimisic locking if the lock_version
25 # versioned table ready and that your model has a version field. This works with optimistic locking if the lock_version
26 # column is present as well.
26 # column is present as well.
27 #
27 #
28 # The class for the versioned model is derived the first time it is seen. Therefore, if you change your database schema you have to restart
28 # The class for the versioned model is derived the first time it is seen. Therefore, if you change your database schema you have to restart
29 # your container for the changes to be reflected. In development mode this usually means restarting WEBrick.
29 # your container for the changes to be reflected. In development mode this usually means restarting WEBrick.
30 #
30 #
31 # class Page < ActiveRecord::Base
31 # class Page < ActiveRecord::Base
32 # # assumes pages_versions table
32 # # assumes pages_versions table
33 # acts_as_versioned
33 # acts_as_versioned
34 # end
34 # end
35 #
35 #
36 # Example:
36 # Example:
37 #
37 #
38 # page = Page.create(:title => 'hello world!')
38 # page = Page.create(:title => 'hello world!')
39 # page.version # => 1
39 # page.version # => 1
40 #
40 #
41 # page.title = 'hello world'
41 # page.title = 'hello world'
42 # page.save
42 # page.save
43 # page.version # => 2
43 # page.version # => 2
44 # page.versions.size # => 2
44 # page.versions.size # => 2
45 #
45 #
46 # page.revert_to(1) # using version number
46 # page.revert_to(1) # using version number
47 # page.title # => 'hello world!'
47 # page.title # => 'hello world!'
48 #
48 #
49 # page.revert_to(page.versions.last) # using versioned instance
49 # page.revert_to(page.versions.last) # using versioned instance
50 # page.title # => 'hello world'
50 # page.title # => 'hello world'
51 #
51 #
52 # page.versions.earliest # efficient query to find the first version
53 # page.versions.latest # efficient query to find the most recently created version
54 #
55 #
56 # Simple Queries to page between versions
57 #
58 # page.versions.before(version)
59 # page.versions.after(version)
60 #
61 # Access the previous/next versions from the versioned model itself
62 #
63 # version = page.versions.latest
64 # version.previous # go back one version
65 # version.next # go forward one version
66 #
52 # See ActiveRecord::Acts::Versioned::ClassMethods#acts_as_versioned for configuration options
67 # See ActiveRecord::Acts::Versioned::ClassMethods#acts_as_versioned for configuration options
53 module Versioned
68 module Versioned
54 CALLBACKS = [:set_new_version, :save_version_on_create, :save_version?, :clear_changed_attributes]
69 CALLBACKS = [:set_new_version, :save_version_on_create, :save_version?, :clear_altered_attributes]
55 def self.included(base) # :nodoc:
70 def self.included(base) # :nodoc:
56 base.extend ClassMethods
71 base.extend ClassMethods
57 end
72 end
58
73
59 module ClassMethods
74 module ClassMethods
60 # == Configuration options
75 # == Configuration options
61 #
76 #
62 # * <tt>class_name</tt> - versioned model class name (default: PageVersion in the above example)
77 # * <tt>class_name</tt> - versioned model class name (default: PageVersion in the above example)
63 # * <tt>table_name</tt> - versioned model table name (default: page_versions in the above example)
78 # * <tt>table_name</tt> - versioned model table name (default: page_versions in the above example)
64 # * <tt>foreign_key</tt> - foreign key used to relate the versioned model to the original model (default: page_id in the above example)
79 # * <tt>foreign_key</tt> - foreign key used to relate the versioned model to the original model (default: page_id in the above example)
65 # * <tt>inheritance_column</tt> - name of the column to save the model's inheritance_column value for STI. (default: versioned_type)
80 # * <tt>inheritance_column</tt> - name of the column to save the model's inheritance_column value for STI. (default: versioned_type)
66 # * <tt>version_column</tt> - name of the column in the model that keeps the version number (default: version)
81 # * <tt>version_column</tt> - name of the column in the model that keeps the version number (default: version)
67 # * <tt>sequence_name</tt> - name of the custom sequence to be used by the versioned model.
82 # * <tt>sequence_name</tt> - name of the custom sequence to be used by the versioned model.
68 # * <tt>limit</tt> - number of revisions to keep, defaults to unlimited
83 # * <tt>limit</tt> - number of revisions to keep, defaults to unlimited
69 # * <tt>if</tt> - symbol of method to check before saving a new version. If this method returns false, a new version is not saved.
84 # * <tt>if</tt> - symbol of method to check before saving a new version. If this method returns false, a new version is not saved.
70 # For finer control, pass either a Proc or modify Model#version_condition_met?
85 # For finer control, pass either a Proc or modify Model#version_condition_met?
71 #
86 #
72 # acts_as_versioned :if => Proc.new { |auction| !auction.expired? }
87 # acts_as_versioned :if => Proc.new { |auction| !auction.expired? }
73 #
88 #
74 # or...
89 # or...
75 #
90 #
76 # class Auction
91 # class Auction
77 # def version_condition_met? # totally bypasses the <tt>:if</tt> option
92 # def version_condition_met? # totally bypasses the <tt>:if</tt> option
78 # !expired?
93 # !expired?
79 # end
94 # end
80 # end
95 # end
81 #
96 #
82 # * <tt>if_changed</tt> - Simple way of specifying attributes that are required to be changed before saving a model. This takes
97 # * <tt>if_changed</tt> - Simple way of specifying attributes that are required to be changed before saving a model. This takes
83 # either a symbol or array of symbols. WARNING - This will attempt to overwrite any attribute setters you may have.
98 # either a symbol or array of symbols. WARNING - This will attempt to overwrite any attribute setters you may have.
84 # Use this instead if you want to write your own attribute setters (and ignore if_changed):
99 # Use this instead if you want to write your own attribute setters (and ignore if_changed):
85 #
100 #
86 # def name=(new_name)
101 # def name=(new_name)
87 # write_changed_attribute :name, new_name
102 # write_changed_attribute :name, new_name
88 # end
103 # end
89 #
104 #
90 # * <tt>extend</tt> - Lets you specify a module to be mixed in both the original and versioned models. You can also just pass a block
105 # * <tt>extend</tt> - Lets you specify a module to be mixed in both the original and versioned models. You can also just pass a block
91 # to create an anonymous mixin:
106 # to create an anonymous mixin:
92 #
107 #
93 # class Auction
108 # class Auction
94 # acts_as_versioned do
109 # acts_as_versioned do
95 # def started?
110 # def started?
96 # !started_at.nil?
111 # !started_at.nil?
97 # end
112 # end
98 # end
113 # end
99 # end
114 # end
100 #
115 #
101 # or...
116 # or...
102 #
117 #
103 # module AuctionExtension
118 # module AuctionExtension
104 # def started?
119 # def started?
105 # !started_at.nil?
120 # !started_at.nil?
106 # end
121 # end
107 # end
122 # end
108 # class Auction
123 # class Auction
109 # acts_as_versioned :extend => AuctionExtension
124 # acts_as_versioned :extend => AuctionExtension
110 # end
125 # end
111 #
126 #
112 # Example code:
127 # Example code:
113 #
128 #
114 # @auction = Auction.find(1)
129 # @auction = Auction.find(1)
115 # @auction.started?
130 # @auction.started?
116 # @auction.versions.first.started?
131 # @auction.versions.first.started?
117 #
132 #
118 # == Database Schema
133 # == Database Schema
119 #
134 #
120 # The model that you're versioning needs to have a 'version' attribute. The model is versioned
135 # The model that you're versioning needs to have a 'version' attribute. The model is versioned
121 # into a table called #{model}_versions where the model name is singlular. The _versions table should
136 # into a table called #{model}_versions where the model name is singlular. The _versions table should
122 # contain all the fields you want versioned, the same version column, and a #{model}_id foreign key field.
137 # contain all the fields you want versioned, the same version column, and a #{model}_id foreign key field.
123 #
138 #
124 # A lock_version field is also accepted if your model uses Optimistic Locking. If your table uses Single Table inheritance,
139 # A lock_version field is also accepted if your model uses Optimistic Locking. If your table uses Single Table inheritance,
125 # then that field is reflected in the versioned model as 'versioned_type' by default.
140 # then that field is reflected in the versioned model as 'versioned_type' by default.
126 #
141 #
127 # Acts_as_versioned comes prepared with the ActiveRecord::Acts::Versioned::ActMethods::ClassMethods#create_versioned_table
142 # Acts_as_versioned comes prepared with the ActiveRecord::Acts::Versioned::ActMethods::ClassMethods#create_versioned_table
128 # method, perfect for a migration. It will also create the version column if the main model does not already have it.
143 # method, perfect for a migration. It will also create the version column if the main model does not already have it.
129 #
144 #
130 # class AddVersions < ActiveRecord::Migration
145 # class AddVersions < ActiveRecord::Migration
131 # def self.up
146 # def self.up
132 # # create_versioned_table takes the same options hash
147 # # create_versioned_table takes the same options hash
133 # # that create_table does
148 # # that create_table does
134 # Post.create_versioned_table
149 # Post.create_versioned_table
135 # end
150 # end
136 #
151 #
137 # def self.down
152 # def self.down
138 # Post.drop_versioned_table
153 # Post.drop_versioned_table
139 # end
154 # end
140 # end
155 # end
141 #
156 #
142 # == Changing What Fields Are Versioned
157 # == Changing What Fields Are Versioned
143 #
158 #
144 # By default, acts_as_versioned will version all but these fields:
159 # By default, acts_as_versioned will version all but these fields:
145 #
160 #
146 # [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column]
161 # [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column]
147 #
162 #
148 # You can add or change those by modifying #non_versioned_columns. Note that this takes strings and not symbols.
163 # You can add or change those by modifying #non_versioned_columns. Note that this takes strings and not symbols.
149 #
164 #
150 # class Post < ActiveRecord::Base
165 # class Post < ActiveRecord::Base
151 # acts_as_versioned
166 # acts_as_versioned
152 # self.non_versioned_columns << 'comments_count'
167 # self.non_versioned_columns << 'comments_count'
153 # end
168 # end
154 #
169 #
155 def acts_as_versioned(options = {}, &extension)
170 def acts_as_versioned(options = {}, &extension)
156 # don't allow multiple calls
171 # don't allow multiple calls
157 return if self.included_modules.include?(ActiveRecord::Acts::Versioned::ActMethods)
172 return if self.included_modules.include?(ActiveRecord::Acts::Versioned::ActMethods)
158
173
159 send :include, ActiveRecord::Acts::Versioned::ActMethods
174 send :include, ActiveRecord::Acts::Versioned::ActMethods
160
175
161 cattr_accessor :versioned_class_name, :versioned_foreign_key, :versioned_table_name, :versioned_inheritance_column,
176 cattr_accessor :versioned_class_name, :versioned_foreign_key, :versioned_table_name, :versioned_inheritance_column,
162 :version_column, :max_version_limit, :track_changed_attributes, :version_condition, :version_sequence_name, :non_versioned_columns,
177 :version_column, :max_version_limit, :track_altered_attributes, :version_condition, :version_sequence_name, :non_versioned_columns,
163 :version_association_options
178 :version_association_options
164
179
165 # legacy
180 # legacy
166 alias_method :non_versioned_fields, :non_versioned_columns
181 alias_method :non_versioned_fields, :non_versioned_columns
167 alias_method :non_versioned_fields=, :non_versioned_columns=
182 alias_method :non_versioned_fields=, :non_versioned_columns=
168
183
169 class << self
184 class << self
170 alias_method :non_versioned_fields, :non_versioned_columns
185 alias_method :non_versioned_fields, :non_versioned_columns
171 alias_method :non_versioned_fields=, :non_versioned_columns=
186 alias_method :non_versioned_fields=, :non_versioned_columns=
172 end
187 end
173
188
174 send :attr_accessor, :changed_attributes
189 send :attr_accessor, :altered_attributes
175
190
176 self.versioned_class_name = options[:class_name] || "Version"
191 self.versioned_class_name = options[:class_name] || "Version"
177 self.versioned_foreign_key = options[:foreign_key] || self.to_s.foreign_key
192 self.versioned_foreign_key = options[:foreign_key] || self.to_s.foreign_key
178 self.versioned_table_name = options[:table_name] || "#{table_name_prefix}#{base_class.name.demodulize.underscore}_versions#{table_name_suffix}"
193 self.versioned_table_name = options[:table_name] || "#{table_name_prefix}#{base_class.name.demodulize.underscore}_versions#{table_name_suffix}"
179 self.versioned_inheritance_column = options[:inheritance_column] || "versioned_#{inheritance_column}"
194 self.versioned_inheritance_column = options[:inheritance_column] || "versioned_#{inheritance_column}"
180 self.version_column = options[:version_column] || 'version'
195 self.version_column = options[:version_column] || 'version'
181 self.version_sequence_name = options[:sequence_name]
196 self.version_sequence_name = options[:sequence_name]
182 self.max_version_limit = options[:limit].to_i
197 self.max_version_limit = options[:limit].to_i
183 self.version_condition = options[:if] || true
198 self.version_condition = options[:if] || true
184 self.non_versioned_columns = [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column]
199 self.non_versioned_columns = [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column]
185 self.version_association_options = {
200 self.version_association_options = {
186 :class_name => "#{self.to_s}::#{versioned_class_name}",
201 :class_name => "#{self.to_s}::#{versioned_class_name}",
187 :foreign_key => "#{versioned_foreign_key}",
202 :foreign_key => versioned_foreign_key,
188 :order => 'version',
189 :dependent => :delete_all
203 :dependent => :delete_all
190 }.merge(options[:association_options] || {})
204 }.merge(options[:association_options] || {})
191
205
192 if block_given?
206 if block_given?
193 extension_module_name = "#{versioned_class_name}Extension"
207 extension_module_name = "#{versioned_class_name}Extension"
194 silence_warnings do
208 silence_warnings do
195 self.const_set(extension_module_name, Module.new(&extension))
209 self.const_set(extension_module_name, Module.new(&extension))
196 end
210 end
197
211
198 options[:extend] = self.const_get(extension_module_name)
212 options[:extend] = self.const_get(extension_module_name)
199 end
213 end
200
214
201 class_eval do
215 class_eval do
202 has_many :versions, version_association_options
216 has_many :versions, version_association_options do
217 # finds earliest version of this record
218 def earliest
219 @earliest ||= find(:first, :order => 'version')
220 end
221
222 # find latest version of this record
223 def latest
224 @latest ||= find(:first, :order => 'version desc')
225 end
226 end
203 before_save :set_new_version
227 before_save :set_new_version
204 after_create :save_version_on_create
228 after_create :save_version_on_create
205 after_update :save_version
229 after_update :save_version
206 after_save :clear_old_versions
230 after_save :clear_old_versions
207 after_save :clear_changed_attributes
231 after_save :clear_altered_attributes
208
232
209 unless options[:if_changed].nil?
233 unless options[:if_changed].nil?
210 self.track_changed_attributes = true
234 self.track_altered_attributes = true
211 options[:if_changed] = [options[:if_changed]] unless options[:if_changed].is_a?(Array)
235 options[:if_changed] = [options[:if_changed]] unless options[:if_changed].is_a?(Array)
212 options[:if_changed].each do |attr_name|
236 options[:if_changed].each do |attr_name|
213 define_method("#{attr_name}=") do |value|
237 define_method("#{attr_name}=") do |value|
214 write_changed_attribute attr_name, value
238 write_changed_attribute attr_name, value
215 end
239 end
216 end
240 end
217 end
241 end
218
242
219 include options[:extend] if options[:extend].is_a?(Module)
243 include options[:extend] if options[:extend].is_a?(Module)
220 end
244 end
221
245
222 # create the dynamic versioned model
246 # create the dynamic versioned model
223 const_set(versioned_class_name, Class.new(ActiveRecord::Base)).class_eval do
247 const_set(versioned_class_name, Class.new(ActiveRecord::Base)).class_eval do
224 def self.reloadable? ; false ; end
248 def self.reloadable? ; false ; end
249 # find first version before the given version
250 def self.before(version)
251 find :first, :order => 'version desc',
252 :conditions => ["#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version]
253 end
254
255 # find first version after the given version.
256 def self.after(version)
257 find :first, :order => 'version',
258 :conditions => ["#{original_class.versioned_foreign_key} = ? and version > ?", version.send(original_class.versioned_foreign_key), version.version]
259 end
260
261 def previous
262 self.class.before(self)
225 end
263 end
226
264
265 def next
266 self.class.after(self)
267 end
268
269 def versions_count
270 page.version
271 end
272 end
273
274 versioned_class.cattr_accessor :original_class
275 versioned_class.original_class = self
227 versioned_class.set_table_name versioned_table_name
276 versioned_class.set_table_name versioned_table_name
228 versioned_class.belongs_to self.to_s.demodulize.underscore.to_sym,
277 versioned_class.belongs_to self.to_s.demodulize.underscore.to_sym,
229 :class_name => "::#{self.to_s}",
278 :class_name => "::#{self.to_s}",
230 :foreign_key => versioned_foreign_key
279 :foreign_key => versioned_foreign_key
231 versioned_class.send :include, options[:extend] if options[:extend].is_a?(Module)
280 versioned_class.send :include, options[:extend] if options[:extend].is_a?(Module)
232 versioned_class.set_sequence_name version_sequence_name if version_sequence_name
281 versioned_class.set_sequence_name version_sequence_name if version_sequence_name
233 end
282 end
234 end
283 end
235
284
236 module ActMethods
285 module ActMethods
237 def self.included(base) # :nodoc:
286 def self.included(base) # :nodoc:
238 base.extend ClassMethods
287 base.extend ClassMethods
239 end
288 end
240
289
290 # Finds a specific version of this record
291 def find_version(version = nil)
292 self.class.find_version(id, version)
293 end
294
241 # Saves a version of the model if applicable
295 # Saves a version of the model if applicable
242 def save_version
296 def save_version
243 save_version_on_create if save_version?
297 save_version_on_create if save_version?
244 end
298 end
245
299
246 # Saves a version of the model in the versioned table. This is called in the after_save callback by default
300 # Saves a version of the model in the versioned table. This is called in the after_save callback by default
247 def save_version_on_create
301 def save_version_on_create
248 rev = self.class.versioned_class.new
302 rev = self.class.versioned_class.new
249 self.clone_versioned_model(self, rev)
303 self.clone_versioned_model(self, rev)
250 rev.version = send(self.class.version_column)
304 rev.version = send(self.class.version_column)
251 rev.send("#{self.class.versioned_foreign_key}=", self.id)
305 rev.send("#{self.class.versioned_foreign_key}=", self.id)
252 rev.save
306 rev.save
253 end
307 end
254
308
255 # Clears old revisions if a limit is set with the :limit option in <tt>acts_as_versioned</tt>.
309 # Clears old revisions if a limit is set with the :limit option in <tt>acts_as_versioned</tt>.
256 # Override this method to set your own criteria for clearing old versions.
310 # Override this method to set your own criteria for clearing old versions.
257 def clear_old_versions
311 def clear_old_versions
258 return if self.class.max_version_limit == 0
312 return if self.class.max_version_limit == 0
259 excess_baggage = send(self.class.version_column).to_i - self.class.max_version_limit
313 excess_baggage = send(self.class.version_column).to_i - self.class.max_version_limit
260 if excess_baggage > 0
314 if excess_baggage > 0
261 sql = "DELETE FROM #{self.class.versioned_table_name} WHERE version <= #{excess_baggage} AND #{self.class.versioned_foreign_key} = #{self.id}"
315 sql = "DELETE FROM #{self.class.versioned_table_name} WHERE version <= #{excess_baggage} AND #{self.class.versioned_foreign_key} = #{self.id}"
262 self.class.versioned_class.connection.execute sql
316 self.class.versioned_class.connection.execute sql
263 end
317 end
264 end
318 end
265
319
266 # Finds a specific version of this model.
320 def versions_count
267 def find_version(version)
321 version
268 return version if version.is_a?(self.class.versioned_class)
269 return nil if version.is_a?(ActiveRecord::Base)
270 find_versions(:conditions => ['version = ?', version], :limit => 1).first
271 end
272
273 # Finds versions of this model. Takes an options hash like <tt>find</tt>
274 def find_versions(options = {})
275 versions.find(:all, options)
276 end
322 end
277
323
278 # Reverts a model to a given version. Takes either a version number or an instance of the versioned model
324 # Reverts a model to a given version. Takes either a version number or an instance of the versioned model
279 def revert_to(version)
325 def revert_to(version)
280 if version.is_a?(self.class.versioned_class)
326 if version.is_a?(self.class.versioned_class)
281 return false unless version.send(self.class.versioned_foreign_key) == self.id and !version.new_record?
327 return false unless version.send(self.class.versioned_foreign_key) == self.id and !version.new_record?
282 else
328 else
283 return false unless version = find_version(version)
329 return false unless version = versions.find_by_version(version)
284 end
330 end
285 self.clone_versioned_model(version, self)
331 self.clone_versioned_model(version, self)
286 self.send("#{self.class.version_column}=", version.version)
332 self.send("#{self.class.version_column}=", version.version)
287 true
333 true
288 end
334 end
289
335
290 # Reverts a model to a given version and saves the model.
336 # Reverts a model to a given version and saves the model.
291 # Takes either a version number or an instance of the versioned model
337 # Takes either a version number or an instance of the versioned model
292 def revert_to!(version)
338 def revert_to!(version)
293 revert_to(version) ? save_without_revision : false
339 revert_to(version) ? save_without_revision : false
294 end
340 end
295
341
296 # Temporarily turns off Optimistic Locking while saving. Used when reverting so that a new version is not created.
342 # Temporarily turns off Optimistic Locking while saving. Used when reverting so that a new version is not created.
297 def save_without_revision
343 def save_without_revision
298 save_without_revision!
344 save_without_revision!
299 true
345 true
300 rescue
346 rescue
301 false
347 false
302 end
348 end
303
349
304 def save_without_revision!
350 def save_without_revision!
305 without_locking do
351 without_locking do
306 without_revision do
352 without_revision do
307 save!
353 save!
308 end
354 end
309 end
355 end
310 end
356 end
311
357
312 # Returns an array of attribute keys that are versioned. See non_versioned_columns
358 # Returns an array of attribute keys that are versioned. See non_versioned_columns
313 def versioned_attributes
359 def versioned_attributes
314 self.attributes.keys.select { |k| !self.class.non_versioned_columns.include?(k) }
360 self.attributes.keys.select { |k| !self.class.non_versioned_columns.include?(k) }
315 end
361 end
316
362
317 # If called with no parameters, gets whether the current model has changed and needs to be versioned.
363 # If called with no parameters, gets whether the current model has changed and needs to be versioned.
318 # If called with a single parameter, gets whether the parameter has changed.
364 # If called with a single parameter, gets whether the parameter has changed.
319 def changed?(attr_name = nil)
365 def changed?(attr_name = nil)
320 attr_name.nil? ?
366 attr_name.nil? ?
321 (!self.class.track_changed_attributes || (changed_attributes && changed_attributes.length > 0)) :
367 (!self.class.track_altered_attributes || (altered_attributes && altered_attributes.length > 0)) :
322 (changed_attributes && changed_attributes.include?(attr_name.to_s))
368 (altered_attributes && altered_attributes.include?(attr_name.to_s))
323 end
369 end
324
370
325 # keep old dirty? method
371 # keep old dirty? method
326 alias_method :dirty?, :changed?
372 alias_method :dirty?, :changed?
327
373
328 # Clones a model. Used when saving a new version or reverting a model's version.
374 # Clones a model. Used when saving a new version or reverting a model's version.
329 def clone_versioned_model(orig_model, new_model)
375 def clone_versioned_model(orig_model, new_model)
330 self.versioned_attributes.each do |key|
376 self.versioned_attributes.each do |key|
331 new_model.send("#{key}=", orig_model.attributes[key]) if orig_model.has_attribute?(key)
377 new_model.send("#{key}=", orig_model.send(key)) if orig_model.has_attribute?(key)
332 end
378 end
333
379
334 if orig_model.is_a?(self.class.versioned_class)
380 if orig_model.is_a?(self.class.versioned_class)
335 new_model[new_model.class.inheritance_column] = orig_model[self.class.versioned_inheritance_column]
381 new_model[new_model.class.inheritance_column] = orig_model[self.class.versioned_inheritance_column]
336 elsif new_model.is_a?(self.class.versioned_class)
382 elsif new_model.is_a?(self.class.versioned_class)
337 new_model[self.class.versioned_inheritance_column] = orig_model[orig_model.class.inheritance_column]
383 new_model[self.class.versioned_inheritance_column] = orig_model[orig_model.class.inheritance_column]
338 end
384 end
339 end
385 end
340
386
341 # Checks whether a new version shall be saved or not. Calls <tt>version_condition_met?</tt> and <tt>changed?</tt>.
387 # Checks whether a new version shall be saved or not. Calls <tt>version_condition_met?</tt> and <tt>changed?</tt>.
342 def save_version?
388 def save_version?
343 version_condition_met? && changed?
389 version_condition_met? && changed?
344 end
390 end
345
391
346 # Checks condition set in the :if option to check whether a revision should be created or not. Override this for
392 # Checks condition set in the :if option to check whether a revision should be created or not. Override this for
347 # custom version condition checking.
393 # custom version condition checking.
348 def version_condition_met?
394 def version_condition_met?
349 case
395 case
350 when version_condition.is_a?(Symbol)
396 when version_condition.is_a?(Symbol)
351 send(version_condition)
397 send(version_condition)
352 when version_condition.respond_to?(:call) && (version_condition.arity == 1 || version_condition.arity == -1)
398 when version_condition.respond_to?(:call) && (version_condition.arity == 1 || version_condition.arity == -1)
353 version_condition.call(self)
399 version_condition.call(self)
354 else
400 else
355 version_condition
401 version_condition
356 end
402 end
357 end
403 end
358
404
359 # Executes the block with the versioning callbacks disabled.
405 # Executes the block with the versioning callbacks disabled.
360 #
406 #
361 # @foo.without_revision do
407 # @foo.without_revision do
362 # @foo.save
408 # @foo.save
363 # end
409 # end
364 #
410 #
365 def without_revision(&block)
411 def without_revision(&block)
366 self.class.without_revision(&block)
412 self.class.without_revision(&block)
367 end
413 end
368
414
369 # Turns off optimistic locking for the duration of the block
415 # Turns off optimistic locking for the duration of the block
370 #
416 #
371 # @foo.without_locking do
417 # @foo.without_locking do
372 # @foo.save
418 # @foo.save
373 # end
419 # end
374 #
420 #
375 def without_locking(&block)
421 def without_locking(&block)
376 self.class.without_locking(&block)
422 self.class.without_locking(&block)
377 end
423 end
378
424
379 def empty_callback() end #:nodoc:
425 def empty_callback() end #:nodoc:
380
426
381 protected
427 protected
382 # sets the new version before saving, unless you're using optimistic locking. In that case, let it take care of the version.
428 # sets the new version before saving, unless you're using optimistic locking. In that case, let it take care of the version.
383 def set_new_version
429 def set_new_version
384 self.send("#{self.class.version_column}=", self.next_version) if new_record? || (!locking_enabled? && save_version?)
430 self.send("#{self.class.version_column}=", self.next_version) if new_record? || (!locking_enabled? && save_version?)
385 end
431 end
386
432
387 # Gets the next available version for the current record, or 1 for a new record
433 # Gets the next available version for the current record, or 1 for a new record
388 def next_version
434 def next_version
389 return 1 if new_record?
435 return 1 if new_record?
390 (versions.calculate(:max, :version) || 0) + 1
436 (versions.calculate(:max, :version) || 0) + 1
391 end
437 end
392
438
393 # clears current changed attributes. Called after save.
439 # clears current changed attributes. Called after save.
394 def clear_changed_attributes
440 def clear_altered_attributes
395 self.changed_attributes = []
441 self.altered_attributes = []
396 end
442 end
397
443
398 def write_changed_attribute(attr_name, attr_value)
444 def write_changed_attribute(attr_name, attr_value)
399 # Convert to db type for comparison. Avoids failing Float<=>String comparisons.
445 # Convert to db type for comparison. Avoids failing Float<=>String comparisons.
400 attr_value_for_db = self.class.columns_hash[attr_name.to_s].type_cast(attr_value)
446 attr_value_for_db = self.class.columns_hash[attr_name.to_s].type_cast(attr_value)
401 (self.changed_attributes ||= []) << attr_name.to_s unless self.changed?(attr_name) || self.send(attr_name) == attr_value_for_db
447 (self.altered_attributes ||= []) << attr_name.to_s unless self.changed?(attr_name) || self.send(attr_name) == attr_value_for_db
402 write_attribute(attr_name, attr_value_for_db)
448 write_attribute(attr_name, attr_value_for_db)
403 end
449 end
404
450
405 private
406 CALLBACKS.each do |attr_name|
407 alias_method "orig_#{attr_name}".to_sym, attr_name
408 end
409
410 module ClassMethods
451 module ClassMethods
411 # Finds a specific version of a specific row of this model
452 # Finds a specific version of a specific row of this model
412 def find_version(id, version)
453 def find_version(id, version = nil)
413 find_versions(id,
454 return find(id) unless version
414 :conditions => ["#{versioned_foreign_key} = ? AND version = ?", id, version],
455
415 :limit => 1).first
456 conditions = ["#{versioned_foreign_key} = ? AND version = ?", id, version]
457 options = { :conditions => conditions, :limit => 1 }
458
459 if result = find_versions(id, options).first
460 result
461 else
462 raise RecordNotFound, "Couldn't find #{name} with ID=#{id} and VERSION=#{version}"
463 end
416 end
464 end
417
465
418 # Finds versions of a specific model. Takes an options hash like <tt>find</tt>
466 # Finds versions of a specific model. Takes an options hash like <tt>find</tt>
419 def find_versions(id, options = {})
467 def find_versions(id, options = {})
420 versioned_class.find :all, {
468 versioned_class.find :all, {
421 :conditions => ["#{versioned_foreign_key} = ?", id],
469 :conditions => ["#{versioned_foreign_key} = ?", id],
422 :order => 'version' }.merge(options)
470 :order => 'version' }.merge(options)
423 end
471 end
424
472
425 # Returns an array of columns that are versioned. See non_versioned_columns
473 # Returns an array of columns that are versioned. See non_versioned_columns
426 def versioned_columns
474 def versioned_columns
427 self.columns.select { |c| !non_versioned_columns.include?(c.name) }
475 self.columns.select { |c| !non_versioned_columns.include?(c.name) }
428 end
476 end
429
477
430 # Returns an instance of the dynamic versioned model
478 # Returns an instance of the dynamic versioned model
431 def versioned_class
479 def versioned_class
432 const_get versioned_class_name
480 const_get versioned_class_name
433 end
481 end
434
482
435 # Rake migration task to create the versioned table using options passed to acts_as_versioned
483 # Rake migration task to create the versioned table using options passed to acts_as_versioned
436 def create_versioned_table(create_table_options = {})
484 def create_versioned_table(create_table_options = {})
437 # create version column in main table if it does not exist
485 # create version column in main table if it does not exist
438 if !self.content_columns.find { |c| %w(version lock_version).include? c.name }
486 if !self.content_columns.find { |c| %w(version lock_version).include? c.name }
439 self.connection.add_column table_name, :version, :integer
487 self.connection.add_column table_name, :version, :integer
440 end
488 end
441
489
442 self.connection.create_table(versioned_table_name, create_table_options) do |t|
490 self.connection.create_table(versioned_table_name, create_table_options) do |t|
443 t.column versioned_foreign_key, :integer
491 t.column versioned_foreign_key, :integer
444 t.column :version, :integer
492 t.column :version, :integer
445 end
493 end
446
494
447 updated_col = nil
495 updated_col = nil
448 self.versioned_columns.each do |col|
496 self.versioned_columns.each do |col|
449 updated_col = col if !updated_col && %(updated_at updated_on).include?(col.name)
497 updated_col = col if !updated_col && %(updated_at updated_on).include?(col.name)
450 self.connection.add_column versioned_table_name, col.name, col.type,
498 self.connection.add_column versioned_table_name, col.name, col.type,
451 :limit => col.limit,
499 :limit => col.limit,
452 :default => col.default
500 :default => col.default,
501 :scale => col.scale,
502 :precision => col.precision
453 end
503 end
454
504
455 if type_col = self.columns_hash[inheritance_column]
505 if type_col = self.columns_hash[inheritance_column]
456 self.connection.add_column versioned_table_name, versioned_inheritance_column, type_col.type,
506 self.connection.add_column versioned_table_name, versioned_inheritance_column, type_col.type,
457 :limit => type_col.limit,
507 :limit => type_col.limit,
458 :default => type_col.default
508 :default => type_col.default,
509 :scale => type_col.scale,
510 :precision => type_col.precision
459 end
511 end
460
512
461 if updated_col.nil?
513 if updated_col.nil?
462 self.connection.add_column versioned_table_name, :updated_at, :timestamp
514 self.connection.add_column versioned_table_name, :updated_at, :timestamp
463 end
515 end
464 end
516 end
465
517
466 # Rake migration task to drop the versioned table
518 # Rake migration task to drop the versioned table
467 def drop_versioned_table
519 def drop_versioned_table
468 self.connection.drop_table versioned_table_name
520 self.connection.drop_table versioned_table_name
469 end
521 end
470
522
471 # Executes the block with the versioning callbacks disabled.
523 # Executes the block with the versioning callbacks disabled.
472 #
524 #
473 # Foo.without_revision do
525 # Foo.without_revision do
474 # @foo.save
526 # @foo.save
475 # end
527 # end
476 #
528 #
477 def without_revision(&block)
529 def without_revision(&block)
478 class_eval do
530 class_eval do
479 CALLBACKS.each do |attr_name|
531 CALLBACKS.each do |attr_name|
532 alias_method "orig_#{attr_name}".to_sym, attr_name
480 alias_method attr_name, :empty_callback
533 alias_method attr_name, :empty_callback
481 end
534 end
482 end
535 end
483 result = block.call
536 block.call
537 ensure
484 class_eval do
538 class_eval do
485 CALLBACKS.each do |attr_name|
539 CALLBACKS.each do |attr_name|
486 alias_method attr_name, "orig_#{attr_name}".to_sym
540 alias_method attr_name, "orig_#{attr_name}".to_sym
487 end
541 end
488 end
542 end
489 result
490 end
543 end
491
544
492 # Turns off optimistic locking for the duration of the block
545 # Turns off optimistic locking for the duration of the block
493 #
546 #
494 # Foo.without_locking do
547 # Foo.without_locking do
495 # @foo.save
548 # @foo.save
496 # end
549 # end
497 #
550 #
498 def without_locking(&block)
551 def without_locking(&block)
499 current = ActiveRecord::Base.lock_optimistically
552 current = ActiveRecord::Base.lock_optimistically
500 ActiveRecord::Base.lock_optimistically = false if current
553 ActiveRecord::Base.lock_optimistically = false if current
501 result = block.call
554 result = block.call
502 ActiveRecord::Base.lock_optimistically = true if current
555 ActiveRecord::Base.lock_optimistically = true if current
503 result
556 result
504 end
557 end
505 end
558 end
506 end
559 end
507 end
560 end
508 end
561 end
509 end
562 end
510
563
511 ActiveRecord::Base.send :include, ActiveRecord::Acts::Versioned No newline at end of file
564 ActiveRecord::Base.send :include, ActiveRecord::Acts::Versioned
@@ -1,40 +1,41
1 $:.unshift(File.dirname(__FILE__) + '/../../../rails/activesupport/lib')
2 $:.unshift(File.dirname(__FILE__) + '/../../../rails/activerecord/lib')
1 $:.unshift(File.dirname(__FILE__) + '/../lib')
3 $:.unshift(File.dirname(__FILE__) + '/../lib')
2
3 require 'test/unit'
4 require 'test/unit'
4 require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb'))
5 begin
6 require 'active_support'
7 require 'active_record'
5 require 'active_record/fixtures'
8 require 'active_record/fixtures'
9 rescue LoadError
10 require 'rubygems'
11 retry
12 end
13 require 'acts_as_versioned'
6
14
7 config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
15 config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
8 ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
16 ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
9 ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite'])
17 ActiveRecord::Base.configurations = {'test' => config[ENV['DB'] || 'sqlite3']}
18 ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
10
19
11 load(File.dirname(__FILE__) + "/schema.rb")
20 load(File.dirname(__FILE__) + "/schema.rb")
12
21
13 # set up custom sequence on widget_versions for DBs that support sequences
22 # set up custom sequence on widget_versions for DBs that support sequences
14 if ENV['DB'] == 'postgresql'
23 if ENV['DB'] == 'postgresql'
15 ActiveRecord::Base.connection.execute "DROP SEQUENCE widgets_seq;" rescue nil
24 ActiveRecord::Base.connection.execute "DROP SEQUENCE widgets_seq;" rescue nil
16 ActiveRecord::Base.connection.remove_column :widget_versions, :id
25 ActiveRecord::Base.connection.remove_column :widget_versions, :id
17 ActiveRecord::Base.connection.execute "CREATE SEQUENCE widgets_seq START 101;"
26 ActiveRecord::Base.connection.execute "CREATE SEQUENCE widgets_seq START 101;"
18 ActiveRecord::Base.connection.execute "ALTER TABLE widget_versions ADD COLUMN id INTEGER PRIMARY KEY DEFAULT nextval('widgets_seq');"
27 ActiveRecord::Base.connection.execute "ALTER TABLE widget_versions ADD COLUMN id INTEGER PRIMARY KEY DEFAULT nextval('widgets_seq');"
19 end
28 end
20
29
21 Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
30 Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
22 $LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path)
31 $:.unshift(Test::Unit::TestCase.fixture_path)
23
32
24 class Test::Unit::TestCase #:nodoc:
33 class Test::Unit::TestCase #:nodoc:
25 def create_fixtures(*table_names)
26 if block_given?
27 Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield }
28 else
29 Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names)
30 end
31 end
32
33 # Turn off transactional fixtures if you're working with MyISAM tables in MySQL
34 # Turn off transactional fixtures if you're working with MyISAM tables in MySQL
34 self.use_transactional_fixtures = true
35 self.use_transactional_fixtures = true
35
36
36 # Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
37 # Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
37 self.use_instantiated_fixtures = false
38 self.use_instantiated_fixtures = false
38
39
39 # Add more helper methods to be used by all tests here...
40 # Add more helper methods to be used by all tests here...
40 end No newline at end of file
41 end
@@ -1,6 +1,6
1 class Widget < ActiveRecord::Base
1 class Widget < ActiveRecord::Base
2 acts_as_versioned :sequence_name => 'widgets_seq', :association_options => {
2 acts_as_versioned :sequence_name => 'widgets_seq', :association_options => {
3 :dependent => nil, :order => 'version desc'
3 :dependent => :nullify, :order => 'version desc'
4 }
4 }
5 non_versioned_columns << 'foo'
5 non_versioned_columns << 'foo'
6 end No newline at end of file
6 end
@@ -1,32 +1,46
1 require File.join(File.dirname(__FILE__), 'abstract_unit')
1 require File.join(File.dirname(__FILE__), 'abstract_unit')
2
2
3 if ActiveRecord::Base.connection.supports_migrations?
3 if ActiveRecord::Base.connection.supports_migrations?
4 class Thing < ActiveRecord::Base
4 class Thing < ActiveRecord::Base
5 attr_accessor :version
5 attr_accessor :version
6 acts_as_versioned
6 acts_as_versioned
7 end
7 end
8
8
9 class MigrationTest < Test::Unit::TestCase
9 class MigrationTest < Test::Unit::TestCase
10 self.use_transactional_fixtures = false
10 self.use_transactional_fixtures = false
11 def teardown
11 def teardown
12 if ActiveRecord::Base.connection.respond_to?(:initialize_schema_information)
12 ActiveRecord::Base.connection.initialize_schema_information
13 ActiveRecord::Base.connection.initialize_schema_information
13 ActiveRecord::Base.connection.update "UPDATE schema_info SET version = 0"
14 ActiveRecord::Base.connection.update "UPDATE schema_info SET version = 0"
15 else
16 ActiveRecord::Base.connection.initialize_schema_migrations_table
17 ActiveRecord::Base.connection.assume_migrated_upto_version(0)
18 end
14
19
15 Thing.connection.drop_table "things" rescue nil
20 Thing.connection.drop_table "things" rescue nil
16 Thing.connection.drop_table "thing_versions" rescue nil
21 Thing.connection.drop_table "thing_versions" rescue nil
17 Thing.reset_column_information
22 Thing.reset_column_information
18 end
23 end
19
24
20 def test_versioned_migration
25 def test_versioned_migration
21 assert_raises(ActiveRecord::StatementInvalid) { Thing.create :title => 'blah blah' }
26 assert_raises(ActiveRecord::StatementInvalid) { Thing.create :title => 'blah blah' }
22 # take 'er up
27 # take 'er up
23 ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/')
28 ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/')
24 t = Thing.create :title => 'blah blah'
29 t = Thing.create :title => 'blah blah', :price => 123.45, :type => 'Thing'
25 assert_equal 1, t.versions.size
30 assert_equal 1, t.versions.size
26
31
32 # check that the price column has remembered its value correctly
33 assert_equal t.price, t.versions.first.price
34 assert_equal t.title, t.versions.first.title
35 assert_equal t[:type], t.versions.first[:type]
36
37 # make sure that the precision of the price column has been preserved
38 assert_equal 7, Thing::Version.columns.find{|c| c.name == "price"}.precision
39 assert_equal 2, Thing::Version.columns.find{|c| c.name == "price"}.scale
40
27 # now lets take 'er back down
41 # now lets take 'er back down
28 ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/')
42 ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/')
29 assert_raises(ActiveRecord::StatementInvalid) { Thing.create :title => 'blah blah' }
43 assert_raises(ActiveRecord::StatementInvalid) { Thing.create :title => 'blah blah' }
30 end
44 end
31 end
45 end
32 end
46 end
@@ -1,313 +1,347
1 require File.join(File.dirname(__FILE__), 'abstract_unit')
1 require File.join(File.dirname(__FILE__), 'abstract_unit')
2 require File.join(File.dirname(__FILE__), 'fixtures/page')
2 require File.join(File.dirname(__FILE__), 'fixtures/page')
3 require File.join(File.dirname(__FILE__), 'fixtures/widget')
3 require File.join(File.dirname(__FILE__), 'fixtures/widget')
4
4
5 class VersionedTest < Test::Unit::TestCase
5 class VersionedTest < Test::Unit::TestCase
6 fixtures :pages, :page_versions, :locked_pages, :locked_pages_revisions, :authors, :landmarks, :landmark_versions
6 fixtures :pages, :page_versions, :locked_pages, :locked_pages_revisions, :authors, :landmarks, :landmark_versions
7 set_fixture_class :page_versions => Page::Version
7
8
8 def test_saves_versioned_copy
9 def test_saves_versioned_copy
9 p = Page.create :title => 'first title', :body => 'first body'
10 p = Page.create! :title => 'first title', :body => 'first body'
10 assert !p.new_record?
11 assert !p.new_record?
11 assert_equal 1, p.versions.size
12 assert_equal 1, p.versions.size
12 assert_equal 1, p.version
13 assert_equal 1, p.version
13 assert_instance_of Page.versioned_class, p.versions.first
14 assert_instance_of Page.versioned_class, p.versions.first
14 end
15 end
15
16
16 def test_saves_without_revision
17 def test_saves_without_revision
17 p = pages(:welcome)
18 p = pages(:welcome)
18 old_versions = p.versions.count
19 old_versions = p.versions.count
19
20
20 p.save_without_revision
21 p.save_without_revision
21
22
22 p.without_revision do
23 p.without_revision do
23 p.update_attributes :title => 'changed'
24 p.update_attributes :title => 'changed'
24 end
25 end
25
26
26 assert_equal old_versions, p.versions.count
27 assert_equal old_versions, p.versions.count
27 end
28 end
28
29
29 def test_rollback_with_version_number
30 def test_rollback_with_version_number
30 p = pages(:welcome)
31 p = pages(:welcome)
31 assert_equal 24, p.version
32 assert_equal 24, p.version
32 assert_equal 'Welcome to the weblog', p.title
33 assert_equal 'Welcome to the weblog', p.title
33
34
34 assert p.revert_to!(p.versions.first.version), "Couldn't revert to 23"
35 assert p.revert_to!(p.versions.first.version), "Couldn't revert to 23"
35 assert_equal 23, p.version
36 assert_equal 23, p.version
36 assert_equal 'Welcome to the weblg', p.title
37 assert_equal 'Welcome to the weblg', p.title
37 end
38 end
38
39
39 def test_versioned_class_name
40 def test_versioned_class_name
40 assert_equal 'Version', Page.versioned_class_name
41 assert_equal 'Version', Page.versioned_class_name
41 assert_equal 'LockedPageRevision', LockedPage.versioned_class_name
42 assert_equal 'LockedPageRevision', LockedPage.versioned_class_name
42 end
43 end
43
44
44 def test_versioned_class
45 def test_versioned_class
45 assert_equal Page::Version, Page.versioned_class
46 assert_equal Page::Version, Page.versioned_class
46 assert_equal LockedPage::LockedPageRevision, LockedPage.versioned_class
47 assert_equal LockedPage::LockedPageRevision, LockedPage.versioned_class
47 end
48 end
48
49
49 def test_special_methods
50 def test_special_methods
50 assert_nothing_raised { pages(:welcome).feeling_good? }
51 assert_nothing_raised { pages(:welcome).feeling_good? }
51 assert_nothing_raised { pages(:welcome).versions.first.feeling_good? }
52 assert_nothing_raised { pages(:welcome).versions.first.feeling_good? }
52 assert_nothing_raised { locked_pages(:welcome).hello_world }
53 assert_nothing_raised { locked_pages(:welcome).hello_world }
53 assert_nothing_raised { locked_pages(:welcome).versions.first.hello_world }
54 assert_nothing_raised { locked_pages(:welcome).versions.first.hello_world }
54 end
55 end
55
56
56 def test_rollback_with_version_class
57 def test_rollback_with_version_class
57 p = pages(:welcome)
58 p = pages(:welcome)
58 assert_equal 24, p.version
59 assert_equal 24, p.version
59 assert_equal 'Welcome to the weblog', p.title
60 assert_equal 'Welcome to the weblog', p.title
60
61
61 assert p.revert_to!(p.versions.first), "Couldn't revert to 23"
62 assert p.revert_to!(p.versions.first), "Couldn't revert to 23"
62 assert_equal 23, p.version
63 assert_equal 23, p.version
63 assert_equal 'Welcome to the weblg', p.title
64 assert_equal 'Welcome to the weblg', p.title
64 end
65 end
65
66
66 def test_rollback_fails_with_invalid_revision
67 def test_rollback_fails_with_invalid_revision
67 p = locked_pages(:welcome)
68 p = locked_pages(:welcome)
68 assert !p.revert_to!(locked_pages(:thinking))
69 assert !p.revert_to!(locked_pages(:thinking))
69 end
70 end
70
71
71 def test_saves_versioned_copy_with_options
72 def test_saves_versioned_copy_with_options
72 p = LockedPage.create :title => 'first title'
73 p = LockedPage.create! :title => 'first title'
73 assert !p.new_record?
74 assert !p.new_record?
74 assert_equal 1, p.versions.size
75 assert_equal 1, p.versions.size
75 assert_instance_of LockedPage.versioned_class, p.versions.first
76 assert_instance_of LockedPage.versioned_class, p.versions.first
76 end
77 end
77
78
78 def test_rollback_with_version_number_with_options
79 def test_rollback_with_version_number_with_options
79 p = locked_pages(:welcome)
80 p = locked_pages(:welcome)
80 assert_equal 'Welcome to the weblog', p.title
81 assert_equal 'Welcome to the weblog', p.title
81 assert_equal 'LockedPage', p.versions.first.version_type
82 assert_equal 'LockedPage', p.versions.first.version_type
82
83
83 assert p.revert_to!(p.versions.first.version), "Couldn't revert to 23"
84 assert p.revert_to!(p.versions.first.version), "Couldn't revert to 23"
84 assert_equal 'Welcome to the weblg', p.title
85 assert_equal 'Welcome to the weblg', p.title
85 assert_equal 'LockedPage', p.versions.first.version_type
86 assert_equal 'LockedPage', p.versions.first.version_type
86 end
87 end
87
88
88 def test_rollback_with_version_class_with_options
89 def test_rollback_with_version_class_with_options
89 p = locked_pages(:welcome)
90 p = locked_pages(:welcome)
90 assert_equal 'Welcome to the weblog', p.title
91 assert_equal 'Welcome to the weblog', p.title
91 assert_equal 'LockedPage', p.versions.first.version_type
92 assert_equal 'LockedPage', p.versions.first.version_type
92
93
93 assert p.revert_to!(p.versions.first), "Couldn't revert to 1"
94 assert p.revert_to!(p.versions.first), "Couldn't revert to 1"
94 assert_equal 'Welcome to the weblg', p.title
95 assert_equal 'Welcome to the weblg', p.title
95 assert_equal 'LockedPage', p.versions.first.version_type
96 assert_equal 'LockedPage', p.versions.first.version_type
96 end
97 end
97
98
98 def test_saves_versioned_copy_with_sti
99 def test_saves_versioned_copy_with_sti
99 p = SpecialLockedPage.create :title => 'first title'
100 p = SpecialLockedPage.create! :title => 'first title'
100 assert !p.new_record?
101 assert !p.new_record?
101 assert_equal 1, p.versions.size
102 assert_equal 1, p.versions.size
102 assert_instance_of LockedPage.versioned_class, p.versions.first
103 assert_instance_of LockedPage.versioned_class, p.versions.first
103 assert_equal 'SpecialLockedPage', p.versions.first.version_type
104 assert_equal 'SpecialLockedPage', p.versions.first.version_type
104 end
105 end
105
106
106 def test_rollback_with_version_number_with_sti
107 def test_rollback_with_version_number_with_sti
107 p = locked_pages(:thinking)
108 p = locked_pages(:thinking)
108 assert_equal 'So I was thinking', p.title
109 assert_equal 'So I was thinking', p.title
109
110
110 assert p.revert_to!(p.versions.first.version), "Couldn't revert to 1"
111 assert p.revert_to!(p.versions.first.version), "Couldn't revert to 1"
111 assert_equal 'So I was thinking!!!', p.title
112 assert_equal 'So I was thinking!!!', p.title
112 assert_equal 'SpecialLockedPage', p.versions.first.version_type
113 assert_equal 'SpecialLockedPage', p.versions.first.version_type
113 end
114 end
114
115
115 def test_lock_version_works_with_versioning
116 def test_lock_version_works_with_versioning
116 p = locked_pages(:thinking)
117 p = locked_pages(:thinking)
117 p2 = LockedPage.find(p.id)
118 p2 = LockedPage.find(p.id)
118
119
119 p.title = 'fresh title'
120 p.title = 'fresh title'
120 p.save
121 p.save
121 assert_equal 2, p.versions.size # limit!
122 assert_equal 2, p.versions.size # limit!
122
123
123 assert_raises(ActiveRecord::StaleObjectError) do
124 assert_raises(ActiveRecord::StaleObjectError) do
124 p2.title = 'stale title'
125 p2.title = 'stale title'
125 p2.save
126 p2.save
126 end
127 end
127 end
128 end
128
129
129 def test_version_if_condition
130 def test_version_if_condition
130 p = Page.create :title => "title"
131 p = Page.create! :title => "title"
131 assert_equal 1, p.version
132 assert_equal 1, p.version
132
133
133 Page.feeling_good = false
134 Page.feeling_good = false
134 p.save
135 p.save
135 assert_equal 1, p.version
136 assert_equal 1, p.version
136 Page.feeling_good = true
137 Page.feeling_good = true
137 end
138 end
138
139
139 def test_version_if_condition2
140 def test_version_if_condition2
140 # set new if condition
141 # set new if condition
141 Page.class_eval do
142 Page.class_eval do
142 def new_feeling_good() title[0..0] == 'a'; end
143 def new_feeling_good() title[0..0] == 'a'; end
143 alias_method :old_feeling_good, :feeling_good?
144 alias_method :old_feeling_good, :feeling_good?
144 alias_method :feeling_good?, :new_feeling_good
145 alias_method :feeling_good?, :new_feeling_good
145 end
146 end
146
147
147 p = Page.create :title => "title"
148 p = Page.create! :title => "title"
148 assert_equal 1, p.version # version does not increment
149 assert_equal 1, p.version # version does not increment
149 assert_equal 1, p.versions(true).size
150 assert_equal 1, p.versions(true).size
150
151
151 p.update_attributes(:title => 'new title')
152 p.update_attributes(:title => 'new title')
152 assert_equal 1, p.version # version does not increment
153 assert_equal 1, p.version # version does not increment
153 assert_equal 1, p.versions(true).size
154 assert_equal 1, p.versions(true).size
154
155
155 p.update_attributes(:title => 'a title')
156 p.update_attributes(:title => 'a title')
156 assert_equal 2, p.version
157 assert_equal 2, p.version
157 assert_equal 2, p.versions(true).size
158 assert_equal 2, p.versions(true).size
158
159
159 # reset original if condition
160 # reset original if condition
160 Page.class_eval { alias_method :feeling_good?, :old_feeling_good }
161 Page.class_eval { alias_method :feeling_good?, :old_feeling_good }
161 end
162 end
162
163
163 def test_version_if_condition_with_block
164 def test_version_if_condition_with_block
164 # set new if condition
165 # set new if condition
165 old_condition = Page.version_condition
166 old_condition = Page.version_condition
166 Page.version_condition = Proc.new { |page| page.title[0..0] == 'b' }
167 Page.version_condition = Proc.new { |page| page.title[0..0] == 'b' }
167
168
168 p = Page.create :title => "title"
169 p = Page.create! :title => "title"
169 assert_equal 1, p.version # version does not increment
170 assert_equal 1, p.version # version does not increment
170 assert_equal 1, p.versions(true).size
171 assert_equal 1, p.versions(true).size
171
172
172 p.update_attributes(:title => 'a title')
173 p.update_attributes(:title => 'a title')
173 assert_equal 1, p.version # version does not increment
174 assert_equal 1, p.version # version does not increment
174 assert_equal 1, p.versions(true).size
175 assert_equal 1, p.versions(true).size
175
176
176 p.update_attributes(:title => 'b title')
177 p.update_attributes(:title => 'b title')
177 assert_equal 2, p.version
178 assert_equal 2, p.version
178 assert_equal 2, p.versions(true).size
179 assert_equal 2, p.versions(true).size
179
180
180 # reset original if condition
181 # reset original if condition
181 Page.version_condition = old_condition
182 Page.version_condition = old_condition
182 end
183 end
183
184
184 def test_version_no_limit
185 def test_version_no_limit
185 p = Page.create :title => "title", :body => 'first body'
186 p = Page.create! :title => "title", :body => 'first body'
186 p.save
187 p.save
187 p.save
188 p.save
188 5.times do |i|
189 5.times do |i|
189 assert_page_title p, i
190 assert_page_title p, i
190 end
191 end
191 end
192 end
192
193
193 def test_version_max_limit
194 def test_version_max_limit
194 p = LockedPage.create :title => "title"
195 p = LockedPage.create! :title => "title"
195 p.update_attributes(:title => "title1")
196 p.update_attributes(:title => "title1")
196 p.update_attributes(:title => "title2")
197 p.update_attributes(:title => "title2")
197 5.times do |i|
198 5.times do |i|
198 assert_page_title p, i, :lock_version
199 assert_page_title p, i, :lock_version
199 assert p.versions(true).size <= 2, "locked version can only store 2 versions"
200 assert p.versions(true).size <= 2, "locked version can only store 2 versions"
200 end
201 end
201 end
202 end
202
203
203 def test_track_changed_attributes_default_value
204 def test_track_altered_attributes_default_value
204 assert !Page.track_changed_attributes
205 assert !Page.track_altered_attributes
205 assert LockedPage.track_changed_attributes
206 assert LockedPage.track_altered_attributes
206 assert SpecialLockedPage.track_changed_attributes
207 assert SpecialLockedPage.track_altered_attributes
207 end
208 end
208
209
209 def test_version_order
210 def test_version_order
210 assert_equal 23, pages(:welcome).versions.first.version
211 assert_equal 23, pages(:welcome).versions.first.version
211 assert_equal 24, pages(:welcome).versions.last.version
212 assert_equal 24, pages(:welcome).versions.last.version
212 assert_equal 23, pages(:welcome).find_versions.first.version
213 assert_equal 24, pages(:welcome).find_versions.last.version
214 end
213 end
215
214
216 def test_track_changed_attributes
215 def test_track_altered_attributes
217 p = LockedPage.create :title => "title"
216 p = LockedPage.create! :title => "title"
218 assert_equal 1, p.lock_version
217 assert_equal 1, p.lock_version
219 assert_equal 1, p.versions(true).size
218 assert_equal 1, p.versions(true).size
220
219
221 p.title = 'title'
220 p.title = 'title'
222 assert !p.save_version?
221 assert !p.save_version?
223 p.save
222 p.save
224 assert_equal 2, p.lock_version # still increments version because of optimistic locking
223 assert_equal 2, p.lock_version # still increments version because of optimistic locking
225 assert_equal 1, p.versions(true).size
224 assert_equal 1, p.versions(true).size
226
225
227 p.title = 'updated title'
226 p.title = 'updated title'
228 assert p.save_version?
227 assert p.save_version?
229 p.save
228 p.save
230 assert_equal 3, p.lock_version
229 assert_equal 3, p.lock_version
231 assert_equal 1, p.versions(true).size # version 1 deleted
230 assert_equal 1, p.versions(true).size # version 1 deleted
232
231
233 p.title = 'updated title!'
232 p.title = 'updated title!'
234 assert p.save_version?
233 assert p.save_version?
235 p.save
234 p.save
236 assert_equal 4, p.lock_version
235 assert_equal 4, p.lock_version
237 assert_equal 2, p.versions(true).size # version 1 deleted
236 assert_equal 2, p.versions(true).size # version 1 deleted
238 end
237 end
239
238
240 def assert_page_title(p, i, version_field = :version)
239 def assert_page_title(p, i, version_field = :version)
241 p.title = "title#{i}"
240 p.title = "title#{i}"
242 p.save
241 p.save
243 assert_equal "title#{i}", p.title
242 assert_equal "title#{i}", p.title
244 assert_equal (i+4), p.send(version_field)
243 assert_equal (i+4), p.send(version_field)
245 end
244 end
246
245
247 def test_find_versions
246 def test_find_versions
248 assert_equal 2, locked_pages(:welcome).versions.size
247 assert_equal 2, locked_pages(:welcome).versions.size
249 assert_equal 1, locked_pages(:welcome).find_versions(:conditions => ['title LIKE ?', '%weblog%']).length
248 assert_equal 1, locked_pages(:welcome).versions.find(:all, :conditions => ['title LIKE ?', '%weblog%']).length
250 assert_equal 2, locked_pages(:welcome).find_versions(:conditions => ['title LIKE ?', '%web%']).length
249 assert_equal 2, locked_pages(:welcome).versions.find(:all, :conditions => ['title LIKE ?', '%web%']).length
251 assert_equal 0, locked_pages(:thinking).find_versions(:conditions => ['title LIKE ?', '%web%']).length
250 assert_equal 0, locked_pages(:thinking).versions.find(:all, :conditions => ['title LIKE ?', '%web%']).length
252 assert_equal 2, locked_pages(:welcome).find_versions.length
251 assert_equal 2, locked_pages(:welcome).versions.length
252 end
253
254 def test_find_version
255 assert_equal page_versions(:welcome_1), Page.find_version(pages(:welcome).id, 23)
256 assert_equal page_versions(:welcome_2), Page.find_version(pages(:welcome).id, 24)
257 assert_equal pages(:welcome), Page.find_version(pages(:welcome).id)
258
259 assert_equal page_versions(:welcome_1), pages(:welcome).find_version(23)
260 assert_equal page_versions(:welcome_2), pages(:welcome).find_version(24)
261 assert_equal pages(:welcome), pages(:welcome).find_version
262
263 assert_raise(ActiveRecord::RecordNotFound) { Page.find_version(pages(:welcome).id, 1) }
264 assert_raise(ActiveRecord::RecordNotFound) { Page.find_version(0, 23) }
253 end
265 end
254
266
255 def test_with_sequence
267 def test_with_sequence
256 assert_equal 'widgets_seq', Widget.versioned_class.sequence_name
268 assert_equal 'widgets_seq', Widget.versioned_class.sequence_name
257 Widget.create :name => 'new widget'
269 3.times { Widget.create! :name => 'new widget' }
258 Widget.create :name => 'new widget'
259 Widget.create :name => 'new widget'
260 assert_equal 3, Widget.count
270 assert_equal 3, Widget.count
261 assert_equal 3, Widget.versioned_class.count
271 assert_equal 3, Widget.versioned_class.count
262 end
272 end
263
273
264 def test_has_many_through
274 def test_has_many_through
265 assert_equal [authors(:caged), authors(:mly)], pages(:welcome).authors
275 assert_equal [authors(:caged), authors(:mly)], pages(:welcome).authors
266 end
276 end
267
277
268 def test_has_many_through_with_custom_association
278 def test_has_many_through_with_custom_association
269 assert_equal [authors(:caged), authors(:mly)], pages(:welcome).revisors
279 assert_equal [authors(:caged), authors(:mly)], pages(:welcome).revisors
270 end
280 end
271
281
272 def test_referential_integrity
282 def test_referential_integrity
273 pages(:welcome).destroy
283 pages(:welcome).destroy
274 assert_equal 0, Page.count
284 assert_equal 0, Page.count
275 assert_equal 0, Page::Version.count
285 assert_equal 0, Page::Version.count
276 end
286 end
277
287
278 def test_association_options
288 def test_association_options
279 association = Page.reflect_on_association(:versions)
289 association = Page.reflect_on_association(:versions)
280 options = association.options
290 options = association.options
281 assert_equal :delete_all, options[:dependent]
291 assert_equal :delete_all, options[:dependent]
282 assert_equal 'version', options[:order]
292 assert_equal 'version', options[:order]
283
293
284 association = Widget.reflect_on_association(:versions)
294 association = Widget.reflect_on_association(:versions)
285 options = association.options
295 options = association.options
286 assert_nil options[:dependent]
296 assert_equal :nullify, options[:dependent]
287 assert_equal 'version desc', options[:order]
297 assert_equal 'version desc', options[:order]
288 assert_equal 'widget_id', options[:foreign_key]
298 assert_equal 'widget_id', options[:foreign_key]
289
299
290 widget = Widget.create :name => 'new widget'
300 widget = Widget.create! :name => 'new widget'
291 assert_equal 1, Widget.count
301 assert_equal 1, Widget.count
292 assert_equal 1, Widget.versioned_class.count
302 assert_equal 1, Widget.versioned_class.count
293 widget.destroy
303 widget.destroy
294 assert_equal 0, Widget.count
304 assert_equal 0, Widget.count
295 assert_equal 1, Widget.versioned_class.count
305 assert_equal 1, Widget.versioned_class.count
296 end
306 end
297
307
298 def test_versioned_records_should_belong_to_parent
308 def test_versioned_records_should_belong_to_parent
299 page = pages(:welcome)
309 page = pages(:welcome)
300 page_version = page.versions.last
310 page_version = page.versions.last
301 assert_equal page, page_version.page
311 assert_equal page, page_version.page
302 end
312 end
303
313
304 def test_unchanged_attributes
314 def test_unaltered_attributes
305 landmarks(:washington).attributes = landmarks(:washington).attributes
315 landmarks(:washington).attributes = landmarks(:washington).attributes.except("id")
306 assert !landmarks(:washington).changed?
316 assert !landmarks(:washington).changed?
307 end
317 end
308
318
309 def test_unchanged_string_attributes
319 def test_unchanged_string_attributes
310 landmarks(:washington).attributes = landmarks(:washington).attributes.inject({}) { |params, (key, value)| params.update key => value.to_s }
320 landmarks(:washington).attributes = landmarks(:washington).attributes.except("id").inject({}) { |params, (key, value)| params.update(key => value.to_s) }
311 assert !landmarks(:washington).changed?
321 assert !landmarks(:washington).changed?
312 end
322 end
323
324 def test_should_find_earliest_version
325 assert_equal page_versions(:welcome_1), pages(:welcome).versions.earliest
326 end
327
328 def test_should_find_latest_version
329 assert_equal page_versions(:welcome_2), pages(:welcome).versions.latest
330 end
331
332 def test_should_find_previous_version
333 assert_equal page_versions(:welcome_1), page_versions(:welcome_2).previous
334 assert_equal page_versions(:welcome_1), pages(:welcome).versions.before(page_versions(:welcome_2))
335 end
336
337 def test_should_find_next_version
338 assert_equal page_versions(:welcome_2), page_versions(:welcome_1).next
339 assert_equal page_versions(:welcome_2), pages(:welcome).versions.after(page_versions(:welcome_1))
340 end
341
342 def test_should_find_version_count
343 assert_equal 24, pages(:welcome).versions_count
344 assert_equal 24, page_versions(:welcome_1).versions_count
345 assert_equal 24, page_versions(:welcome_2).versions_count
346 end
313 end No newline at end of file
347 end
@@ -1,3 +1,9
1 require 'rfpdf'
1 require 'rfpdf'
2
2
3 begin
4 ActionView::Template::register_template_handler 'rfpdf', RFPDF::View
5 rescue NameError
6 # Rails < 2.1
7 RFPDF::View.backward_compatibility_mode = true
3 ActionView::Base::register_template_handler 'rfpdf', RFPDF::View
8 ActionView::Base::register_template_handler 'rfpdf', RFPDF::View
9 end
@@ -1,75 +1,85
1 # Copyright (c) 2006 4ssoM LLC <www.4ssoM.com>
1 # Copyright (c) 2006 4ssoM LLC <www.4ssoM.com>
2 #
2 #
3 # The MIT License
3 # The MIT License
4 #
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to deal
6 # of this software and associated documentation files (the "Software"), to deal
7 # in the Software without restriction, including without limitation the rights
7 # in the Software without restriction, including without limitation the rights
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # copies of the Software, and to permit persons to whom the Software is
9 # copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
10 # furnished to do so, subject to the following conditions:
11 #
11 #
12 # The above copyright notice and this permission notice shall be included in
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
13 # all copies or substantial portions of the Software.
14 #
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 # THE SOFTWARE.
21 # THE SOFTWARE.
22 #
22 #
23 # Thanks go out to Bruce Williams of codefluency who created RTex. This
23 # Thanks go out to Bruce Williams of codefluency who created RTex. This
24 # template handler is modification of his work.
24 # template handler is modification of his work.
25 #
25 #
26 # Example Registration
26 # Example Registration
27 #
27 #
28 # ActionView::Base::register_template_handler 'rfpdf', RFpdfView
28 # ActionView::Base::register_template_handler 'rfpdf', RFpdfView
29
29
30 module RFPDF
30 module RFPDF
31
31
32 class View
32 class View
33 @@backward_compatibility_mode = false
34 cattr_accessor :backward_compatibility_mode
33
35
34 def initialize(action_view)
36 def initialize(action_view)
35 @action_view = action_view
37 @action_view = action_view
36 # Override with @options_for_rfpdf Hash in your controller
38 # Override with @options_for_rfpdf Hash in your controller
37 @options = {
39 @options = {
38 # Run through latex first? (for table of contents, etc)
40 # Run through latex first? (for table of contents, etc)
39 :pre_process => false,
41 :pre_process => false,
40 # Debugging mode; raises exception
42 # Debugging mode; raises exception
41 :debug => false,
43 :debug => false,
42 # Filename of pdf to generate
44 # Filename of pdf to generate
43 :file_name => "#{@action_view.controller.action_name}.pdf",
45 :file_name => "#{@action_view.controller.action_name}.pdf",
44 # Temporary Directory
46 # Temporary Directory
45 :temp_dir => "#{File.expand_path(RAILS_ROOT)}/tmp"
47 :temp_dir => "#{File.expand_path(RAILS_ROOT)}/tmp"
46 }.merge(@action_view.controller.instance_eval{ @options_for_rfpdf } || {}).with_indifferent_access
48 }.merge(@action_view.controller.instance_eval{ @options_for_rfpdf } || {}).with_indifferent_access
47 end
49 end
48
50
51 def self.compilable?
52 false
53 end
54
55 def compilable?
56 self.class.compilable?
57 end
58
49 def render(template, local_assigns = {})
59 def render(template, local_assigns = {})
50 @pdf_name = "Default.pdf" if @pdf_name.nil?
60 @pdf_name = "Default.pdf" if @pdf_name.nil?
51 unless @action_view.controller.headers["Content-Type"] == 'application/pdf'
61 unless @action_view.controller.headers["Content-Type"] == 'application/pdf'
52 @generate = true
62 @generate = true
53 @action_view.controller.headers["Content-Type"] = 'application/pdf'
63 @action_view.controller.headers["Content-Type"] = 'application/pdf'
54 @action_view.controller.headers["Content-disposition:"] = "inline; filename=\"#{@options[:file_name]}\""
64 @action_view.controller.headers["Content-disposition:"] = "inline; filename=\"#{@options[:file_name]}\""
55 end
65 end
56 assigns = @action_view.assigns.dup
66 assigns = @action_view.assigns.dup
57
67
58 if content_for_layout = @action_view.instance_variable_get("@content_for_layout")
68 if content_for_layout = @action_view.instance_variable_get("@content_for_layout")
59 assigns['content_for_layout'] = content_for_layout
69 assigns['content_for_layout'] = content_for_layout
60 end
70 end
61
71
62 result = @action_view.instance_eval do
72 result = @action_view.instance_eval do
63 assigns.each do |key,val|
73 assigns.each do |key,val|
64 instance_variable_set "@#{key}", val
74 instance_variable_set "@#{key}", val
65 end
75 end
66 local_assigns.each do |key,val|
76 local_assigns.each do |key,val|
67 class << self; self; end.send(:define_method,key){ val }
77 class << self; self; end.send(:define_method,key){ val }
68 end
78 end
69 ERB.new(template).result(binding)
79 ERB.new(@@backward_compatibility_mode == true ? template : template.source).result(binding)
70 end
80 end
71 end
81 end
72
82
73 end
83 end
74
84
75 end No newline at end of file
85 end
General Comments 0
You need to be logged in to leave comments. Login now