@@ -0,0 +1,111 | |||||
|
1 | # redMine - project management software | |||
|
2 | # Copyright (C) 2006-2007 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | class WikiController < ApplicationController | |||
|
19 | layout 'base' | |||
|
20 | before_filter :find_wiki, :check_project_privacy, :except => [:preview] | |||
|
21 | ||||
|
22 | # display a page (in editing mode if it doesn't exist) | |||
|
23 | def index | |||
|
24 | page_title = params[:page] | |||
|
25 | @page = @wiki.find_or_new_page(page_title) | |||
|
26 | if @page.new_record? | |||
|
27 | edit | |||
|
28 | render :action => 'edit' and return | |||
|
29 | end | |||
|
30 | @content = (params[:version] ? @page.content.versions.find_by_version(params[:version]) : @page.content) | |||
|
31 | if params[:export] == 'html' | |||
|
32 | export = render_to_string :action => 'export', :layout => false | |||
|
33 | send_data(export, :type => 'text/html', :filename => "#{@page.title}.html") | |||
|
34 | return | |||
|
35 | elsif params[:export] == 'txt' | |||
|
36 | send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt") | |||
|
37 | return | |||
|
38 | end | |||
|
39 | render :action => 'show' | |||
|
40 | end | |||
|
41 | ||||
|
42 | # edit an existing page or a new one | |||
|
43 | def edit | |||
|
44 | @page = @wiki.find_or_new_page(params[:page]) | |||
|
45 | @page.content = WikiContent.new(:page => @page) if @page.new_record? | |||
|
46 | @content = @page.content | |||
|
47 | @content.text = "h1. #{@page.pretty_title}" if @content.text.empty? | |||
|
48 | # don't keep previous comment | |||
|
49 | @content.comment = nil | |||
|
50 | if request.post? | |||
|
51 | if @content.text == params[:content][:text] | |||
|
52 | # don't save if text wasn't changed | |||
|
53 | redirect_to :action => 'index', :id => @project, :page => @page.title | |||
|
54 | return | |||
|
55 | end | |||
|
56 | @content.text = params[:content][:text] | |||
|
57 | @content.comment = params[:content][:comment] | |||
|
58 | @content.author = logged_in_user | |||
|
59 | # if page is new @page.save will also save content, but not if page isn't a new record | |||
|
60 | if (@page.new_record? ? @page.save : @content.save) | |||
|
61 | redirect_to :action => 'index', :id => @project, :page => @page.title | |||
|
62 | end | |||
|
63 | end | |||
|
64 | end | |||
|
65 | ||||
|
66 | # show page history | |||
|
67 | def history | |||
|
68 | @page = @wiki.find_page(params[:page]) | |||
|
69 | # don't load text | |||
|
70 | @versions = @page.content.versions.find :all, | |||
|
71 | :select => "id, author_id, comment, updated_on, version", | |||
|
72 | :order => 'version DESC' | |||
|
73 | end | |||
|
74 | ||||
|
75 | # display special pages | |||
|
76 | def special | |||
|
77 | page_title = params[:page].downcase | |||
|
78 | case page_title | |||
|
79 | # show pages index, sorted by title | |||
|
80 | when 'page_index' | |||
|
81 | # eager load information about last updates, without loading text | |||
|
82 | @pages = @wiki.pages.find :all, :select => "wiki_pages.*, wiki_contents.updated_on", | |||
|
83 | :joins => "LEFT JOIN wiki_contents ON wiki_contents.page_id = wiki_pages.id", | |||
|
84 | :order => 'title' | |||
|
85 | # export wiki to a single html file | |||
|
86 | when 'export' | |||
|
87 | @pages = @wiki.pages.find :all, :order => 'title' | |||
|
88 | export = render_to_string :action => 'export_multiple', :layout => false | |||
|
89 | send_data(export, :type => 'text/html', :filename => "wiki.html") | |||
|
90 | return | |||
|
91 | else | |||
|
92 | # requested special page doesn't exist, redirect to default page | |||
|
93 | redirect_to :action => 'index', :id => @project, :page => nil and return | |||
|
94 | end | |||
|
95 | render :action => "special_#{page_title}" | |||
|
96 | end | |||
|
97 | ||||
|
98 | def preview | |||
|
99 | @text = params[:content][:text] | |||
|
100 | render :partial => 'preview' | |||
|
101 | end | |||
|
102 | ||||
|
103 | private | |||
|
104 | ||||
|
105 | def find_wiki | |||
|
106 | @project = Project.find(params[:id]) | |||
|
107 | @wiki = @project.wiki | |||
|
108 | rescue ActiveRecord::RecordNotFound | |||
|
109 | render_404 | |||
|
110 | end | |||
|
111 | end |
@@ -0,0 +1,19 | |||||
|
1 | # redMine - project management software | |||
|
2 | # Copyright (C) 2006-2007 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | module WikiHelper | |||
|
19 | end |
@@ -0,0 +1,44 | |||||
|
1 | # redMine - project management software | |||
|
2 | # Copyright (C) 2006-2007 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | class Wiki < ActiveRecord::Base | |||
|
19 | belongs_to :project | |||
|
20 | has_many :pages, :class_name => 'WikiPage', :dependent => :destroy | |||
|
21 | ||||
|
22 | validates_presence_of :project_id, :start_page | |||
|
23 | ||||
|
24 | # find the page with the given title | |||
|
25 | # if page doesn't exist, return a new page | |||
|
26 | def find_or_new_page(title) | |||
|
27 | title = Wiki.titleize(title || start_page) | |||
|
28 | find_page(title) || WikiPage.new(:wiki => self, :title => title) | |||
|
29 | end | |||
|
30 | ||||
|
31 | # find the page with the given title | |||
|
32 | def find_page(title) | |||
|
33 | pages.find_by_title(Wiki.titleize(title || start_page)) | |||
|
34 | end | |||
|
35 | ||||
|
36 | # turn a string into a valid page title | |||
|
37 | def self.titleize(title) | |||
|
38 | # replace spaces with _ and remove unwanted caracters | |||
|
39 | title = title.gsub(/\s+/, '_').delete(',;|') if title | |||
|
40 | # upcase the first letter | |||
|
41 | title = title[0..0].upcase + title[1..-1] if title | |||
|
42 | title | |||
|
43 | end | |||
|
44 | end |
@@ -0,0 +1,58 | |||||
|
1 | # redMine - project management software | |||
|
2 | # Copyright (C) 2006-2007 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | require 'zlib' | |||
|
19 | ||||
|
20 | class WikiContent < ActiveRecord::Base | |||
|
21 | belongs_to :page, :class_name => 'WikiPage', :foreign_key => 'page_id' | |||
|
22 | belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' | |||
|
23 | validates_presence_of :text | |||
|
24 | ||||
|
25 | acts_as_versioned | |||
|
26 | class Version | |||
|
27 | belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' | |||
|
28 | attr_protected :data | |||
|
29 | ||||
|
30 | def text=(plain) | |||
|
31 | case Setting.wiki_compression | |||
|
32 | when 'gzip' | |||
|
33 | begin | |||
|
34 | self.data = Zlib::Deflate.deflate(plain, Zlib::BEST_COMPRESSION) | |||
|
35 | self.compression = 'gzip' | |||
|
36 | rescue | |||
|
37 | self.data = plain | |||
|
38 | self.compression = '' | |||
|
39 | end | |||
|
40 | else | |||
|
41 | self.data = plain | |||
|
42 | self.compression = '' | |||
|
43 | end | |||
|
44 | plain | |||
|
45 | end | |||
|
46 | ||||
|
47 | def text | |||
|
48 | @text ||= case compression | |||
|
49 | when 'gzip' | |||
|
50 | Zlib::Inflate.inflate(data) | |||
|
51 | else | |||
|
52 | # uncompressed data | |||
|
53 | data | |||
|
54 | end | |||
|
55 | end | |||
|
56 | end | |||
|
57 | ||||
|
58 | end |
@@ -0,0 +1,34 | |||||
|
1 | # redMine - project management software | |||
|
2 | # Copyright (C) 2006-2007 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | class WikiPage < ActiveRecord::Base | |||
|
19 | belongs_to :wiki | |||
|
20 | has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy | |||
|
21 | ||||
|
22 | validates_presence_of :title | |||
|
23 | validates_format_of :title, :with => /^[^,\s]*$/ | |||
|
24 | validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false | |||
|
25 | validates_associated :content | |||
|
26 | ||||
|
27 | def before_save | |||
|
28 | self.title = Wiki.titleize(title) | |||
|
29 | end | |||
|
30 | ||||
|
31 | def pretty_title | |||
|
32 | title.tr '_', ' ' | |||
|
33 | end | |||
|
34 | end |
@@ -0,0 +1,3 | |||||
|
1 | <fieldset class="preview"><legend><%= l(:label_preview) %></legend> | |||
|
2 | <%= textilizable @text %> | |||
|
3 | </fieldset> |
@@ -0,0 +1,39 | |||||
|
1 | <div class="contextual"> | |||
|
2 | <%= link_to(l(:label_page_index), {:action => 'special', :page => 'Page_index'}, :class => 'icon icon-index') %> | |||
|
3 | </div> | |||
|
4 | ||||
|
5 | <h2><%= @page.pretty_title %></h2> | |||
|
6 | ||||
|
7 | <% form_for :content, @content, :url => {:action => 'edit', :page => @page.title}, :html => {:id => 'wiki_form'} do |f| %> | |||
|
8 | <%= error_messages_for 'content' %> | |||
|
9 | <p><%= f.text_area :text, :cols => 100, :rows => 25, :style => "width:99%;" %></p> | |||
|
10 | <p><label><%= l(:field_comment) %></label><br /><%= f.text_field :comment, :size => 120 %></p> | |||
|
11 | <p><%= submit_tag l(:button_save) %> | |||
|
12 | <%= link_to_remote l(:label_preview), | |||
|
13 | { :url => { :controller => 'wiki', :action => 'preview' }, | |||
|
14 | :method => 'get', | |||
|
15 | :update => 'preview', | |||
|
16 | :with => "Form.serialize('wiki_form')", | |||
|
17 | :loading => "Element.show('indicator')", | |||
|
18 | :loaded => "Element.hide('indicator')" | |||
|
19 | } %> | |||
|
20 | <span id="indicator" style="display:none"><%= image_tag "loading.gif", :align => "absmiddle" %></span> | |||
|
21 | </p> | |||
|
22 | ||||
|
23 | <% end %> | |||
|
24 | ||||
|
25 | <% if Setting.text_formatting == 'textile' %> | |||
|
26 | <%= javascript_include_tag 'jstoolbar' %> | |||
|
27 | <script type="text/javascript"> | |||
|
28 | //<![CDATA[ | |||
|
29 | if (document.getElementById) { | |||
|
30 | if (document.getElementById('content_text')) { | |||
|
31 | var commentTb = new jsToolBar(document.getElementById('content_text')); | |||
|
32 | commentTb.draw(); | |||
|
33 | } | |||
|
34 | } | |||
|
35 | //]]> | |||
|
36 | </script> | |||
|
37 | <% end %> | |||
|
38 | ||||
|
39 | <div id="preview" class="wiki"></div> No newline at end of file |
@@ -0,0 +1,14 | |||||
|
1 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> | |||
|
2 | <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> | |||
|
3 | <head> | |||
|
4 | <title><%=h @page.pretty_title %></title> | |||
|
5 | <meta http-equiv="content-type" content="text/html; charset=utf-8" /> | |||
|
6 | <style> | |||
|
7 | body { font:80% Verdana,Tahoma,Arial,sans-serif; } | |||
|
8 | h1, h2, h3, h4 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; } | |||
|
9 | </style> | |||
|
10 | </head> | |||
|
11 | <body> | |||
|
12 | <%= textilizable @content.text, :wiki_links => :local %> | |||
|
13 | </body> | |||
|
14 | </html> |
@@ -0,0 +1,26 | |||||
|
1 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> | |||
|
2 | <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> | |||
|
3 | <head> | |||
|
4 | <title><%=h @wiki.project.name %></title> | |||
|
5 | <meta http-equiv="content-type" content="text/html; charset=utf-8" /> | |||
|
6 | <style> | |||
|
7 | body { font:80% Verdana,Tahoma,Arial,sans-serif; } | |||
|
8 | h1, h2, h3, h4 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; } | |||
|
9 | </style> | |||
|
10 | </head> | |||
|
11 | <body> | |||
|
12 | ||||
|
13 | <strong><%= l(:label_page_index) %></strong> | |||
|
14 | <ul> | |||
|
15 | <% @pages.each do |page| %> | |||
|
16 | <li><a href="#<%= page.title %>"><%= page.pretty_title %></a></li> | |||
|
17 | <% end %> | |||
|
18 | </ul> | |||
|
19 | ||||
|
20 | <% @pages.each do |page| %> | |||
|
21 | <hr /> | |||
|
22 | <%= textilizable page.content.text, :wiki_links => :anchor %> | |||
|
23 | <% end %> | |||
|
24 | ||||
|
25 | </body> | |||
|
26 | </html> |
@@ -0,0 +1,28 | |||||
|
1 | <div class="contextual"> | |||
|
2 | <%= link_to(l(:label_page_index), {:action => 'special', :page => 'Page_index'}, :class => 'icon icon-index') %> | |||
|
3 | </div> | |||
|
4 | ||||
|
5 | <h2><%= @page.pretty_title %></h2> | |||
|
6 | ||||
|
7 | <h3><%= l(:label_history) %></h3> | |||
|
8 | ||||
|
9 | <table class="list"> | |||
|
10 | <thead><tr> | |||
|
11 | <th>#</th> | |||
|
12 | <th><%= l(:field_updated_on) %></th> | |||
|
13 | <th><%= l(:field_author) %></th> | |||
|
14 | <th><%= l(:field_comment) %></th> | |||
|
15 | </tr></thead> | |||
|
16 | <tbody> | |||
|
17 | <% @versions.each do |ver| %> | |||
|
18 | <tr class="<%= cycle("odd", "even") %>"> | |||
|
19 | <th align="center"><%= link_to ver.version, :action => 'index', :page => @page.title, :version => ver.version %></th> | |||
|
20 | <td align="center"><%= format_time(ver.updated_on) %></td> | |||
|
21 | <td><em><%= ver.author ? ver.author.name : "anonyme" %></em></td> | |||
|
22 | <td><%=h ver.comment %></td> | |||
|
23 | </tr> | |||
|
24 | <% end %> | |||
|
25 | </tbody> | |||
|
26 | </table> | |||
|
27 | ||||
|
28 | <p><%= link_to l(:button_back), :action => 'index', :page => @page.title %></p> No newline at end of file |
@@ -0,0 +1,30 | |||||
|
1 | <div class="contextual"> | |||
|
2 | <%= link_to(l(:button_edit), {:action => 'edit', :page => @page.title}, :class => 'icon icon-edit') if @content.version == @page.content.version %> | |||
|
3 | <%= link_to(l(:label_history), {:action => 'history', :page => @page.title}, :class => 'icon icon-history') %> | |||
|
4 | <%= link_to(l(:label_page_index), {:action => 'special', :page => 'Page_index'}, :class => 'icon icon-index') %> | |||
|
5 | </div> | |||
|
6 | ||||
|
7 | <% if @content.version != @page.content.version %> | |||
|
8 | <p> | |||
|
9 | <%= link_to(('« ' + l(:label_previous)), :action => 'index', :page => @page.title, :version => (@content.version - 1)) + " - " if @content.version > 1 %> | |||
|
10 | <%= "#{l(:label_version)} #{@content.version}/#{@page.content.version}" %> - | |||
|
11 | <%= link_to((l(:label_next) + ' »'), :action => 'index', :page => @page.title, :version => (@content.version + 1)) + " - " if @content.version < @page.content.version %> | |||
|
12 | <%= link_to(l(:label_current_version), :action => 'index', :page => @page.title) %> | |||
|
13 | <br /> | |||
|
14 | <em><%= @content.author ? @content.author.name : "anonyme" %>, <%= format_time(@content.updated_on) %> </em><br /> | |||
|
15 | <%=h @content.comment %> | |||
|
16 | </p> | |||
|
17 | <hr /> | |||
|
18 | <% end %> | |||
|
19 | ||||
|
20 | <div class="wiki"> | |||
|
21 | <% cache "wiki/show/#{@page.id}/#{@content.version}" do %> | |||
|
22 | <%= textilizable @content.text %> | |||
|
23 | <% end %> | |||
|
24 | </div> | |||
|
25 | ||||
|
26 | <div class="contextual"> | |||
|
27 | <%= l(:label_export_to) %> | |||
|
28 | <%= link_to 'HTML', {:export => 'html', :version => @content.version}, :class => 'icon icon-html' %>, | |||
|
29 | <%= link_to 'TXT', {:export => 'txt', :version => @content.version}, :class => 'icon icon-txt' %> | |||
|
30 | </div> No newline at end of file |
@@ -0,0 +1,13 | |||||
|
1 | <div class="contextual"> | |||
|
2 | <% unless @pages.empty? %> | |||
|
3 | <%= l(:label_export_to) %> <%= link_to 'HTML', {:action => 'special', :page => 'export'}, :class => 'icon icon-html' %> | |||
|
4 | <% end %> | |||
|
5 | </div> | |||
|
6 | ||||
|
7 | <h2><%= l(:label_page_index) %></h2> | |||
|
8 | ||||
|
9 | <% if @pages.empty? %><p><i><%= l(:label_no_data) %></i></p><% end %> | |||
|
10 | <ul><% @pages.each do |page| %> | |||
|
11 | <li><%= link_to page.pretty_title, :action => 'index', :page => page.title %> - | |||
|
12 | <%= l(:label_last_updates) %>: <%= format_time(page.updated_on) %></li> | |||
|
13 | <% end %></ul> No newline at end of file |
@@ -0,0 +1,14 | |||||
|
1 | class CreateWikis < ActiveRecord::Migration | |||
|
2 | def self.up | |||
|
3 | create_table :wikis do |t| | |||
|
4 | t.column :project_id, :integer, :null => false | |||
|
5 | t.column :start_page, :string, :limit => 255, :null => false | |||
|
6 | t.column :status, :integer, :default => 1, :null => false | |||
|
7 | end | |||
|
8 | add_index :wikis, :project_id, :name => :wikis_project_id | |||
|
9 | end | |||
|
10 | ||||
|
11 | def self.down | |||
|
12 | drop_table :wikis | |||
|
13 | end | |||
|
14 | end |
@@ -0,0 +1,14 | |||||
|
1 | class CreateWikiPages < ActiveRecord::Migration | |||
|
2 | def self.up | |||
|
3 | create_table :wiki_pages do |t| | |||
|
4 | t.column :wiki_id, :integer, :null => false | |||
|
5 | t.column :title, :string, :limit => 255, :null => false | |||
|
6 | t.column :created_on, :datetime, :null => false | |||
|
7 | end | |||
|
8 | add_index :wiki_pages, [:wiki_id, :title], :name => :wiki_pages_wiki_id_title | |||
|
9 | end | |||
|
10 | ||||
|
11 | def self.down | |||
|
12 | drop_table :wiki_pages | |||
|
13 | end | |||
|
14 | end |
@@ -0,0 +1,30 | |||||
|
1 | class CreateWikiContents < ActiveRecord::Migration | |||
|
2 | def self.up | |||
|
3 | create_table :wiki_contents do |t| | |||
|
4 | t.column :page_id, :integer, :null => false | |||
|
5 | t.column :author_id, :integer | |||
|
6 | t.column :text, :text, :default => "", :null => false | |||
|
7 | t.column :comment, :string, :limit => 255, :default => "" | |||
|
8 | t.column :updated_on, :datetime, :null => false | |||
|
9 | t.column :version, :integer, :null => false | |||
|
10 | end | |||
|
11 | add_index :wiki_contents, :page_id, :name => :wiki_contents_page_id | |||
|
12 | ||||
|
13 | create_table :wiki_content_versions do |t| | |||
|
14 | t.column :wiki_content_id, :integer, :null => false | |||
|
15 | t.column :page_id, :integer, :null => false | |||
|
16 | t.column :author_id, :integer | |||
|
17 | t.column :data, :binary | |||
|
18 | t.column :compression, :string, :limit => 6, :default => "" | |||
|
19 | t.column :comment, :string, :limit => 255, :default => "" | |||
|
20 | t.column :updated_on, :datetime, :null => false | |||
|
21 | t.column :version, :integer, :null => false | |||
|
22 | end | |||
|
23 | add_index :wiki_content_versions, :wiki_content_id, :name => :wiki_content_versions_wcid | |||
|
24 | end | |||
|
25 | ||||
|
26 | def self.down | |||
|
27 | drop_table :wiki_contents | |||
|
28 | drop_table :wiki_content_versions | |||
|
29 | end | |||
|
30 | end |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
@@ -0,0 +1,40 | |||||
|
1 | --- | |||
|
2 | wiki_content_versions_001: | |||
|
3 | updated_on: 2007-03-07 00:08:07 +01:00 | |||
|
4 | page_id: 1 | |||
|
5 | id: 1 | |||
|
6 | version: 1 | |||
|
7 | author_id: 1 | |||
|
8 | comment: Page creation | |||
|
9 | wiki_content_id: 1 | |||
|
10 | compression: "" | |||
|
11 | data: |- | |||
|
12 | h1. CookBook documentation | |||
|
13 | ||||
|
14 | ||||
|
15 | ||||
|
16 | Some [[documentation]] here... | |||
|
17 | wiki_content_versions_002: | |||
|
18 | updated_on: 2007-03-07 00:08:34 +01:00 | |||
|
19 | page_id: 1 | |||
|
20 | id: 2 | |||
|
21 | version: 2 | |||
|
22 | author_id: 1 | |||
|
23 | comment: Small update | |||
|
24 | wiki_content_id: 1 | |||
|
25 | compression: "" | |||
|
26 | data: |- | |||
|
27 | h1. CookBook documentation | |||
|
28 | ||||
|
29 | ||||
|
30 | ||||
|
31 | Some updated [[documentation]] here... | |||
|
32 | wiki_content_versions_003: | |||
|
33 | updated_on: 2007-03-07 00:10:51 +01:00 | |||
|
34 | page_id: 1 | |||
|
35 | id: 3 | |||
|
36 | version: 3 | |||
|
37 | author_id: 1 | |||
|
38 | comment: "" | |||
|
39 | wiki_content_id: 1 | |||
|
40 | compression: "" |
@@ -0,0 +1,12 | |||||
|
1 | --- | |||
|
2 | wiki_contents_001: | |||
|
3 | text: |- | |||
|
4 | h1. CookBook documentation | |||
|
5 | ||||
|
6 | ||||
|
7 | ||||
|
8 | Some updated [[documentation]] here with gzipped history | |||
|
9 | updated_on: 2007-03-07 00:10:51 +01:00 | |||
|
10 | page_id: 1 | |||
|
11 | id: 1 | |||
|
12 | version: 3 |
@@ -0,0 +1,6 | |||||
|
1 | --- | |||
|
2 | wiki_pages_001: | |||
|
3 | created_on: 2007-03-07 00:08:07 +01:00 | |||
|
4 | title: CookBook_documentation | |||
|
5 | id: 1 | |||
|
6 | wiki_id: 1 |
@@ -0,0 +1,6 | |||||
|
1 | --- | |||
|
2 | wikis_001: | |||
|
3 | status: 1 | |||
|
4 | start_page: CookBook documentation | |||
|
5 | project_id: 1 | |||
|
6 | id: 1 |
@@ -0,0 +1,60 | |||||
|
1 | # redMine - project management software | |||
|
2 | # Copyright (C) 2006-2007 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | require File.dirname(__FILE__) + '/../test_helper' | |||
|
19 | ||||
|
20 | class WikiContentTest < Test::Unit::TestCase | |||
|
21 | fixtures :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions, :users | |||
|
22 | ||||
|
23 | def setup | |||
|
24 | @wiki = Wiki.find(1) | |||
|
25 | @page = @wiki.pages.first | |||
|
26 | end | |||
|
27 | ||||
|
28 | def test_create | |||
|
29 | page = WikiPage.new(:wiki => @wiki, :title => "Page") | |||
|
30 | page.content = WikiContent.new(:text => "Content text", :author => User.find(1), :comment => "My comment") | |||
|
31 | assert page.save | |||
|
32 | page.reload | |||
|
33 | ||||
|
34 | content = page.content | |||
|
35 | assert_kind_of WikiContent, content | |||
|
36 | assert_equal 1, content.version | |||
|
37 | assert_equal 1, content.versions.length | |||
|
38 | assert_equal "Content text", content.text | |||
|
39 | assert_equal "My comment", content.comment | |||
|
40 | assert_equal User.find(1), content.author | |||
|
41 | assert_equal content.text, content.versions.last.text | |||
|
42 | end | |||
|
43 | ||||
|
44 | def test_update | |||
|
45 | content = @page.content | |||
|
46 | version_count = content.version | |||
|
47 | content.text = "My new content" | |||
|
48 | assert content.save | |||
|
49 | content.reload | |||
|
50 | assert_equal version_count+1, content.version | |||
|
51 | assert_equal version_count+1, content.versions.length | |||
|
52 | end | |||
|
53 | ||||
|
54 | def test_fetch_history | |||
|
55 | assert !@page.content.versions.empty? | |||
|
56 | @page.content.versions.each do |version| | |||
|
57 | assert_kind_of String, version.text | |||
|
58 | end | |||
|
59 | end | |||
|
60 | end |
@@ -0,0 +1,50 | |||||
|
1 | # redMine - project management software | |||
|
2 | # Copyright (C) 2006-2007 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | require File.dirname(__FILE__) + '/../test_helper' | |||
|
19 | ||||
|
20 | class WikiPageTest < Test::Unit::TestCase | |||
|
21 | fixtures :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions | |||
|
22 | ||||
|
23 | def setup | |||
|
24 | @wiki = Wiki.find(1) | |||
|
25 | @page = @wiki.pages.first | |||
|
26 | end | |||
|
27 | ||||
|
28 | def test_create | |||
|
29 | page = WikiPage.new(:wiki => @wiki) | |||
|
30 | assert !page.save | |||
|
31 | assert_equal 1, page.errors.count | |||
|
32 | ||||
|
33 | page.title = "Page" | |||
|
34 | assert page.save | |||
|
35 | page.reload | |||
|
36 | ||||
|
37 | @wiki.reload | |||
|
38 | assert @wiki.pages.include?(page) | |||
|
39 | end | |||
|
40 | ||||
|
41 | def test_find_or_new_page | |||
|
42 | page = @wiki.find_or_new_page("CookBook documentation") | |||
|
43 | assert_kind_of WikiPage, page | |||
|
44 | assert !page.new_record? | |||
|
45 | ||||
|
46 | page = @wiki.find_or_new_page("Non existing page") | |||
|
47 | assert_kind_of WikiPage, page | |||
|
48 | assert page.new_record? | |||
|
49 | end | |||
|
50 | end |
@@ -0,0 +1,39 | |||||
|
1 | # redMine - project management software | |||
|
2 | # Copyright (C) 2006-2007 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | require File.dirname(__FILE__) + '/../test_helper' | |||
|
19 | ||||
|
20 | class WikiTest < Test::Unit::TestCase | |||
|
21 | fixtures :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions | |||
|
22 | ||||
|
23 | def test_create | |||
|
24 | wiki = Wiki.new(:project => Project.find(2)) | |||
|
25 | assert !wiki.save | |||
|
26 | assert_equal 1, wiki.errors.count | |||
|
27 | ||||
|
28 | wiki.start_page = "Start page" | |||
|
29 | assert wiki.save | |||
|
30 | end | |||
|
31 | ||||
|
32 | def test_update | |||
|
33 | @wiki = Wiki.find(1) | |||
|
34 | @wiki.start_page = "Another start page" | |||
|
35 | assert @wiki.save | |||
|
36 | @wiki.reload | |||
|
37 | assert_equal "Another start page", @wiki.start_page | |||
|
38 | end | |||
|
39 | end |
@@ -0,0 +1,74 | |||||
|
1 | *SVN* (version numbers are overrated) | |||
|
2 | ||||
|
3 | * (5 Oct 2006) Allow customization of #versions association options [Dan Peterson] | |||
|
4 | ||||
|
5 | *0.5.1* | |||
|
6 | ||||
|
7 | * (8 Aug 2006) Versioned models now belong to the unversioned model. @article_version.article.class => Article [Aslak Hellesoy] | |||
|
8 | ||||
|
9 | *0.5* # do versions even matter for plugins? | |||
|
10 | ||||
|
11 | * (21 Apr 2006) Added without_locking and without_revision methods. | |||
|
12 | ||||
|
13 | Foo.without_revision do | |||
|
14 | @foo.update_attributes ... | |||
|
15 | end | |||
|
16 | ||||
|
17 | *0.4* | |||
|
18 | ||||
|
19 | * (28 March 2006) Rename non_versioned_fields to non_versioned_columns (old one is kept for compatibility). | |||
|
20 | * (28 March 2006) Made explicit documentation note that string column names are required for non_versioned_columns. | |||
|
21 | ||||
|
22 | *0.3.1* | |||
|
23 | ||||
|
24 | * (7 Jan 2006) explicitly set :foreign_key option for the versioned model's belongs_to assocation for STI [Caged] | |||
|
25 | * (7 Jan 2006) added tests to prove has_many :through joins work | |||
|
26 | ||||
|
27 | *0.3* | |||
|
28 | ||||
|
29 | * (2 Jan 2006) added ability to share a mixin with versioned class | |||
|
30 | * (2 Jan 2006) changed the dynamic version model to MyModel::Version | |||
|
31 | ||||
|
32 | *0.2.4* | |||
|
33 | ||||
|
34 | * (27 Nov 2005) added note about possible destructive behavior of if_changed? [Michael Schuerig] | |||
|
35 | ||||
|
36 | *0.2.3* | |||
|
37 | ||||
|
38 | * (12 Nov 2005) fixed bug with old behavior of #blank? [Michael Schuerig] | |||
|
39 | * (12 Nov 2005) updated tests to use ActiveRecord Schema | |||
|
40 | ||||
|
41 | *0.2.2* | |||
|
42 | ||||
|
43 | * (3 Nov 2005) added documentation note to #acts_as_versioned [Martin Jul] | |||
|
44 | ||||
|
45 | *0.2.1* | |||
|
46 | ||||
|
47 | * (6 Oct 2005) renamed dirty? to changed? to keep it uniform. it was aliased to keep it backwards compatible. | |||
|
48 | ||||
|
49 | *0.2* | |||
|
50 | ||||
|
51 | * (6 Oct 2005) added find_versions and find_version class methods. | |||
|
52 | ||||
|
53 | * (6 Oct 2005) removed transaction from create_versioned_table(). | |||
|
54 | this way you can specify your own transaction around a group of operations. | |||
|
55 | ||||
|
56 | * (30 Sep 2005) fixed bug where find_versions() would order by 'version' twice. (found by Joe Clark) | |||
|
57 | ||||
|
58 | * (26 Sep 2005) added :sequence_name option to acts_as_versioned to set the sequence name on the versioned model | |||
|
59 | ||||
|
60 | *0.1.3* (18 Sep 2005) | |||
|
61 | ||||
|
62 | * First RubyForge release | |||
|
63 | ||||
|
64 | *0.1.2* | |||
|
65 | ||||
|
66 | * check if module is already included when acts_as_versioned is called | |||
|
67 | ||||
|
68 | *0.1.1* | |||
|
69 | ||||
|
70 | * Adding tests and rdocs | |||
|
71 | ||||
|
72 | *0.1* | |||
|
73 | ||||
|
74 | * Initial transfer from Rails ticket: http://dev.rubyonrails.com/ticket/1974 No newline at end of file |
@@ -0,0 +1,20 | |||||
|
1 | Copyright (c) 2005 Rick Olson | |||
|
2 | ||||
|
3 | Permission is hereby granted, free of charge, to any person obtaining | |||
|
4 | a copy of this software and associated documentation files (the | |||
|
5 | "Software"), to deal in the Software without restriction, including | |||
|
6 | without limitation the rights to use, copy, modify, merge, publish, | |||
|
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 | |||
|
9 | the following conditions: | |||
|
10 | ||||
|
11 | The above copyright notice and this permission notice shall be | |||
|
12 | included in all copies or substantial portions of the Software. | |||
|
13 | ||||
|
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||
|
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||
|
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||
|
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 | |||
|
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. No newline at end of file |
@@ -0,0 +1,28 | |||||
|
1 | = acts_as_versioned | |||
|
2 | ||||
|
3 | This library adds simple versioning to an ActiveRecord module. ActiveRecord is required. | |||
|
4 | ||||
|
5 | == Resources | |||
|
6 | ||||
|
7 | Install | |||
|
8 | ||||
|
9 | * gem install acts_as_versioned | |||
|
10 | ||||
|
11 | Rubyforge project | |||
|
12 | ||||
|
13 | * http://rubyforge.org/projects/ar-versioned | |||
|
14 | ||||
|
15 | RDocs | |||
|
16 | ||||
|
17 | * http://ar-versioned.rubyforge.org | |||
|
18 | ||||
|
19 | Subversion | |||
|
20 | ||||
|
21 | * http://techno-weenie.net/svn/projects/acts_as_versioned | |||
|
22 | ||||
|
23 | Collaboa | |||
|
24 | ||||
|
25 | * http://collaboa.techno-weenie.net/repository/browse/acts_as_versioned | |||
|
26 | ||||
|
27 | Special thanks to Dreamer on ##rubyonrails for help in early testing. His ServerSideWiki (http://serversidewiki.com) | |||
|
28 | was the first project to use acts_as_versioned <em>in the wild</em>. No newline at end of file |
@@ -0,0 +1,41 | |||||
|
1 | == Creating the test database | |||
|
2 | ||||
|
3 | The default name for the test databases is "activerecord_versioned". If you | |||
|
4 | want to use another database name then be sure to update the connection | |||
|
5 | adapter setups you want to test with in test/connections/<your database>/connection.rb. | |||
|
6 | When you have the database online, you can import the fixture tables with | |||
|
7 | the test/fixtures/db_definitions/*.sql files. | |||
|
8 | ||||
|
9 | Make sure that you create database objects with the same user that you specified in i | |||
|
10 | connection.rb otherwise (on Postgres, at least) tests for default values will fail. | |||
|
11 | ||||
|
12 | == Running with Rake | |||
|
13 | ||||
|
14 | The easiest way to run the unit tests is through Rake. The default task runs | |||
|
15 | the entire test suite for all the adapters. You can also run the suite on just | |||
|
16 | one adapter by using the tasks test_mysql_ruby, test_ruby_mysql, test_sqlite, | |||
|
17 | or test_postresql. For more information, checkout the full array of rake tasks with "rake -T" | |||
|
18 | ||||
|
19 | Rake can be found at http://rake.rubyforge.org | |||
|
20 | ||||
|
21 | == Running by hand | |||
|
22 | ||||
|
23 | Unit tests are located in test directory. If you only want to run a single test suite, | |||
|
24 | or don't want to bother with Rake, you can do so with something like: | |||
|
25 | ||||
|
26 | cd test; ruby -I "connections/native_mysql" base_test.rb | |||
|
27 | ||||
|
28 | That'll run the base suite using the MySQL-Ruby adapter. Change the adapter | |||
|
29 | and test suite name as needed. | |||
|
30 | ||||
|
31 | == Faster tests | |||
|
32 | ||||
|
33 | If you are using a database that supports transactions, you can set the | |||
|
34 | "AR_TX_FIXTURES" environment variable to "yes" to use transactional fixtures. | |||
|
35 | This gives a very large speed boost. With rake: | |||
|
36 | ||||
|
37 | rake AR_TX_FIXTURES=yes | |||
|
38 | ||||
|
39 | Or, by hand: | |||
|
40 | ||||
|
41 | AR_TX_FIXTURES=yes ruby -I connections/native_sqlite3 base_test.rb |
@@ -0,0 +1,182 | |||||
|
1 | require 'rubygems' | |||
|
2 | ||||
|
3 | Gem::manage_gems | |||
|
4 | ||||
|
5 | require 'rake/rdoctask' | |||
|
6 | require 'rake/packagetask' | |||
|
7 | require 'rake/gempackagetask' | |||
|
8 | require 'rake/testtask' | |||
|
9 | require 'rake/contrib/rubyforgepublisher' | |||
|
10 | ||||
|
11 | PKG_NAME = 'acts_as_versioned' | |||
|
12 | PKG_VERSION = '0.3.1' | |||
|
13 | PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" | |||
|
14 | PROD_HOST = "technoweenie@bidwell.textdrive.com" | |||
|
15 | RUBY_FORGE_PROJECT = 'ar-versioned' | |||
|
16 | RUBY_FORGE_USER = 'technoweenie' | |||
|
17 | ||||
|
18 | desc 'Default: run unit tests.' | |||
|
19 | task :default => :test | |||
|
20 | ||||
|
21 | desc 'Test the calculations plugin.' | |||
|
22 | Rake::TestTask.new(:test) do |t| | |||
|
23 | t.libs << 'lib' | |||
|
24 | t.pattern = 'test/**/*_test.rb' | |||
|
25 | t.verbose = true | |||
|
26 | end | |||
|
27 | ||||
|
28 | desc 'Generate documentation for the calculations plugin.' | |||
|
29 | Rake::RDocTask.new(:rdoc) do |rdoc| | |||
|
30 | rdoc.rdoc_dir = 'rdoc' | |||
|
31 | rdoc.title = "#{PKG_NAME} -- Simple versioning with active record models" | |||
|
32 | rdoc.options << '--line-numbers --inline-source' | |||
|
33 | rdoc.rdoc_files.include('README', 'CHANGELOG', 'RUNNING_UNIT_TESTS') | |||
|
34 | rdoc.rdoc_files.include('lib/**/*.rb') | |||
|
35 | end | |||
|
36 | ||||
|
37 | spec = Gem::Specification.new do |s| | |||
|
38 | s.name = PKG_NAME | |||
|
39 | s.version = PKG_VERSION | |||
|
40 | s.platform = Gem::Platform::RUBY | |||
|
41 | s.summary = "Simple versioning with active record models" | |||
|
42 | s.files = FileList["{lib,test}/**/*"].to_a + %w(README MIT-LICENSE CHANGELOG RUNNING_UNIT_TESTS) | |||
|
43 | s.files.delete "acts_as_versioned_plugin.sqlite.db" | |||
|
44 | s.files.delete "acts_as_versioned_plugin.sqlite3.db" | |||
|
45 | s.files.delete "test/debug.log" | |||
|
46 | s.require_path = 'lib' | |||
|
47 | s.autorequire = 'acts_as_versioned' | |||
|
48 | s.has_rdoc = true | |||
|
49 | s.test_files = Dir['test/**/*_test.rb'] | |||
|
50 | s.add_dependency 'activerecord', '>= 1.10.1' | |||
|
51 | s.add_dependency 'activesupport', '>= 1.1.1' | |||
|
52 | s.author = "Rick Olson" | |||
|
53 | s.email = "technoweenie@gmail.com" | |||
|
54 | s.homepage = "http://techno-weenie.net" | |||
|
55 | end | |||
|
56 | ||||
|
57 | Rake::GemPackageTask.new(spec) do |pkg| | |||
|
58 | pkg.need_tar = true | |||
|
59 | end | |||
|
60 | ||||
|
61 | desc "Publish the API documentation" | |||
|
62 | task :pdoc => [:rdoc] do | |||
|
63 | Rake::RubyForgePublisher.new(RUBY_FORGE_PROJECT, RUBY_FORGE_USER).upload | |||
|
64 | end | |||
|
65 | ||||
|
66 | desc 'Publish the gem and API docs' | |||
|
67 | task :publish => [:pdoc, :rubyforge_upload] | |||
|
68 | ||||
|
69 | desc "Publish the release files to RubyForge." | |||
|
70 | task :rubyforge_upload => :package do | |||
|
71 | files = %w(gem tgz).map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" } | |||
|
72 | ||||
|
73 | if RUBY_FORGE_PROJECT then | |||
|
74 | require 'net/http' | |||
|
75 | require 'open-uri' | |||
|
76 | ||||
|
77 | project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/" | |||
|
78 | project_data = open(project_uri) { |data| data.read } | |||
|
79 | group_id = project_data[/[?&]group_id=(\d+)/, 1] | |||
|
80 | raise "Couldn't get group id" unless group_id | |||
|
81 | ||||
|
82 | # This echos password to shell which is a bit sucky | |||
|
83 | if ENV["RUBY_FORGE_PASSWORD"] | |||
|
84 | password = ENV["RUBY_FORGE_PASSWORD"] | |||
|
85 | else | |||
|
86 | print "#{RUBY_FORGE_USER}@rubyforge.org's password: " | |||
|
87 | password = STDIN.gets.chomp | |||
|
88 | end | |||
|
89 | ||||
|
90 | login_response = Net::HTTP.start("rubyforge.org", 80) do |http| | |||
|
91 | data = [ | |||
|
92 | "login=1", | |||
|
93 | "form_loginname=#{RUBY_FORGE_USER}", | |||
|
94 | "form_pw=#{password}" | |||
|
95 | ].join("&") | |||
|
96 | http.post("/account/login.php", data) | |||
|
97 | end | |||
|
98 | ||||
|
99 | cookie = login_response["set-cookie"] | |||
|
100 | raise "Login failed" unless cookie | |||
|
101 | headers = { "Cookie" => cookie } | |||
|
102 | ||||
|
103 | release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}" | |||
|
104 | release_data = open(release_uri, headers) { |data| data.read } | |||
|
105 | package_id = release_data[/[?&]package_id=(\d+)/, 1] | |||
|
106 | raise "Couldn't get package id" unless package_id | |||
|
107 | ||||
|
108 | first_file = true | |||
|
109 | release_id = "" | |||
|
110 | ||||
|
111 | files.each do |filename| | |||
|
112 | basename = File.basename(filename) | |||
|
113 | file_ext = File.extname(filename) | |||
|
114 | file_data = File.open(filename, "rb") { |file| file.read } | |||
|
115 | ||||
|
116 | puts "Releasing #{basename}..." | |||
|
117 | ||||
|
118 | release_response = Net::HTTP.start("rubyforge.org", 80) do |http| | |||
|
119 | release_date = Time.now.strftime("%Y-%m-%d %H:%M") | |||
|
120 | type_map = { | |||
|
121 | ".zip" => "3000", | |||
|
122 | ".tgz" => "3110", | |||
|
123 | ".gz" => "3110", | |||
|
124 | ".gem" => "1400" | |||
|
125 | }; type_map.default = "9999" | |||
|
126 | type = type_map[file_ext] | |||
|
127 | boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor" | |||
|
128 | ||||
|
129 | query_hash = if first_file then | |||
|
130 | { | |||
|
131 | "group_id" => group_id, | |||
|
132 | "package_id" => package_id, | |||
|
133 | "release_name" => PKG_FILE_NAME, | |||
|
134 | "release_date" => release_date, | |||
|
135 | "type_id" => type, | |||
|
136 | "processor_id" => "8000", # Any | |||
|
137 | "release_notes" => "", | |||
|
138 | "release_changes" => "", | |||
|
139 | "preformatted" => "1", | |||
|
140 | "submit" => "1" | |||
|
141 | } | |||
|
142 | else | |||
|
143 | { | |||
|
144 | "group_id" => group_id, | |||
|
145 | "release_id" => release_id, | |||
|
146 | "package_id" => package_id, | |||
|
147 | "step2" => "1", | |||
|
148 | "type_id" => type, | |||
|
149 | "processor_id" => "8000", # Any | |||
|
150 | "submit" => "Add This File" | |||
|
151 | } | |||
|
152 | end | |||
|
153 | ||||
|
154 | query = "?" + query_hash.map do |(name, value)| | |||
|
155 | [name, URI.encode(value)].join("=") | |||
|
156 | end.join("&") | |||
|
157 | ||||
|
158 | data = [ | |||
|
159 | "--" + boundary, | |||
|
160 | "Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"", | |||
|
161 | "Content-Type: application/octet-stream", | |||
|
162 | "Content-Transfer-Encoding: binary", | |||
|
163 | "", file_data, "" | |||
|
164 | ].join("\x0D\x0A") | |||
|
165 | ||||
|
166 | release_headers = headers.merge( | |||
|
167 | "Content-Type" => "multipart/form-data; boundary=#{boundary}" | |||
|
168 | ) | |||
|
169 | ||||
|
170 | target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php" | |||
|
171 | http.post(target + query, data, release_headers) | |||
|
172 | end | |||
|
173 | ||||
|
174 | if first_file then | |||
|
175 | release_id = release_response.body[/release_id=(\d+)/, 1] | |||
|
176 | raise("Couldn't get release id") unless release_id | |||
|
177 | end | |||
|
178 | ||||
|
179 | first_file = false | |||
|
180 | end | |||
|
181 | end | |||
|
182 | end No newline at end of file |
@@ -0,0 +1,1 | |||||
|
1 | require 'acts_as_versioned' No newline at end of file |
This diff has been collapsed as it changes many lines, (511 lines changed) Show them Hide them | |||||
@@ -0,0 +1,511 | |||||
|
1 | # Copyright (c) 2005 Rick Olson | |||
|
2 | # | |||
|
3 | # Permission is hereby granted, free of charge, to any person obtaining | |||
|
4 | # a copy of this software and associated documentation files (the | |||
|
5 | # "Software"), to deal in the Software without restriction, including | |||
|
6 | # without limitation the rights to use, copy, modify, merge, publish, | |||
|
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 | |||
|
9 | # the following conditions: | |||
|
10 | # | |||
|
11 | # The above copyright notice and this permission notice shall be | |||
|
12 | # included in all copies or substantial portions of the Software. | |||
|
13 | # | |||
|
14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||
|
15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||
|
16 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||
|
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 | |||
|
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. | |||
|
21 | ||||
|
22 | module ActiveRecord #: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 | |||
|
25 | # versioned table ready and that your model has a version field. This works with optimisic locking if the lock_version | |||
|
26 | # column is present as well. | |||
|
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 | |||
|
29 | # your container for the changes to be reflected. In development mode this usually means restarting WEBrick. | |||
|
30 | # | |||
|
31 | # class Page < ActiveRecord::Base | |||
|
32 | # # assumes pages_versions table | |||
|
33 | # acts_as_versioned | |||
|
34 | # end | |||
|
35 | # | |||
|
36 | # Example: | |||
|
37 | # | |||
|
38 | # page = Page.create(:title => 'hello world!') | |||
|
39 | # page.version # => 1 | |||
|
40 | # | |||
|
41 | # page.title = 'hello world' | |||
|
42 | # page.save | |||
|
43 | # page.version # => 2 | |||
|
44 | # page.versions.size # => 2 | |||
|
45 | # | |||
|
46 | # page.revert_to(1) # using version number | |||
|
47 | # page.title # => 'hello world!' | |||
|
48 | # | |||
|
49 | # page.revert_to(page.versions.last) # using versioned instance | |||
|
50 | # page.title # => 'hello world' | |||
|
51 | # | |||
|
52 | # See ActiveRecord::Acts::Versioned::ClassMethods#acts_as_versioned for configuration options | |||
|
53 | module Versioned | |||
|
54 | CALLBACKS = [:set_new_version, :save_version_on_create, :save_version?, :clear_changed_attributes] | |||
|
55 | def self.included(base) # :nodoc: | |||
|
56 | base.extend ClassMethods | |||
|
57 | end | |||
|
58 | ||||
|
59 | module ClassMethods | |||
|
60 | # == Configuration options | |||
|
61 | # | |||
|
62 | # * <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) | |||
|
64 | # * <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) | |||
|
66 | # * <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. | |||
|
68 | # * <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. | |||
|
70 | # For finer control, pass either a Proc or modify Model#version_condition_met? | |||
|
71 | # | |||
|
72 | # acts_as_versioned :if => Proc.new { |auction| !auction.expired? } | |||
|
73 | # | |||
|
74 | # or... | |||
|
75 | # | |||
|
76 | # class Auction | |||
|
77 | # def version_condition_met? # totally bypasses the <tt>:if</tt> option | |||
|
78 | # !expired? | |||
|
79 | # end | |||
|
80 | # end | |||
|
81 | # | |||
|
82 | # * <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. | |||
|
84 | # Use this instead if you want to write your own attribute setters (and ignore if_changed): | |||
|
85 | # | |||
|
86 | # def name=(new_name) | |||
|
87 | # write_changed_attribute :name, new_name | |||
|
88 | # end | |||
|
89 | # | |||
|
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 | |||
|
91 | # to create an anonymous mixin: | |||
|
92 | # | |||
|
93 | # class Auction | |||
|
94 | # acts_as_versioned do | |||
|
95 | # def started? | |||
|
96 | # !started_at.nil? | |||
|
97 | # end | |||
|
98 | # end | |||
|
99 | # end | |||
|
100 | # | |||
|
101 | # or... | |||
|
102 | # | |||
|
103 | # module AuctionExtension | |||
|
104 | # def started? | |||
|
105 | # !started_at.nil? | |||
|
106 | # end | |||
|
107 | # end | |||
|
108 | # class Auction | |||
|
109 | # acts_as_versioned :extend => AuctionExtension | |||
|
110 | # end | |||
|
111 | # | |||
|
112 | # Example code: | |||
|
113 | # | |||
|
114 | # @auction = Auction.find(1) | |||
|
115 | # @auction.started? | |||
|
116 | # @auction.versions.first.started? | |||
|
117 | # | |||
|
118 | # == Database Schema | |||
|
119 | # | |||
|
120 | # 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 | |||
|
122 | # contain all the fields you want versioned, the same version column, and a #{model}_id foreign key field. | |||
|
123 | # | |||
|
124 | # 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. | |||
|
126 | # | |||
|
127 | # 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. | |||
|
129 | # | |||
|
130 | # class AddVersions < ActiveRecord::Migration | |||
|
131 | # def self.up | |||
|
132 | # # create_versioned_table takes the same options hash | |||
|
133 | # # that create_table does | |||
|
134 | # Post.create_versioned_table | |||
|
135 | # end | |||
|
136 | # | |||
|
137 | # def self.down | |||
|
138 | # Post.drop_versioned_table | |||
|
139 | # end | |||
|
140 | # end | |||
|
141 | # | |||
|
142 | # == Changing What Fields Are Versioned | |||
|
143 | # | |||
|
144 | # By default, acts_as_versioned will version all but these fields: | |||
|
145 | # | |||
|
146 | # [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column] | |||
|
147 | # | |||
|
148 | # You can add or change those by modifying #non_versioned_columns. Note that this takes strings and not symbols. | |||
|
149 | # | |||
|
150 | # class Post < ActiveRecord::Base | |||
|
151 | # acts_as_versioned | |||
|
152 | # self.non_versioned_columns << 'comments_count' | |||
|
153 | # end | |||
|
154 | # | |||
|
155 | def acts_as_versioned(options = {}, &extension) | |||
|
156 | # don't allow multiple calls | |||
|
157 | return if self.included_modules.include?(ActiveRecord::Acts::Versioned::ActMethods) | |||
|
158 | ||||
|
159 | send :include, ActiveRecord::Acts::Versioned::ActMethods | |||
|
160 | ||||
|
161 | 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, | |||
|
163 | :version_association_options | |||
|
164 | ||||
|
165 | # legacy | |||
|
166 | alias_method :non_versioned_fields, :non_versioned_columns | |||
|
167 | alias_method :non_versioned_fields=, :non_versioned_columns= | |||
|
168 | ||||
|
169 | class << self | |||
|
170 | alias_method :non_versioned_fields, :non_versioned_columns | |||
|
171 | alias_method :non_versioned_fields=, :non_versioned_columns= | |||
|
172 | end | |||
|
173 | ||||
|
174 | send :attr_accessor, :changed_attributes | |||
|
175 | ||||
|
176 | self.versioned_class_name = options[:class_name] || "Version" | |||
|
177 | 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}" | |||
|
179 | self.versioned_inheritance_column = options[:inheritance_column] || "versioned_#{inheritance_column}" | |||
|
180 | self.version_column = options[:version_column] || 'version' | |||
|
181 | self.version_sequence_name = options[:sequence_name] | |||
|
182 | self.max_version_limit = options[:limit].to_i | |||
|
183 | self.version_condition = options[:if] || true | |||
|
184 | self.non_versioned_columns = [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column] | |||
|
185 | self.version_association_options = { | |||
|
186 | :class_name => "#{self.to_s}::#{versioned_class_name}", | |||
|
187 | :foreign_key => "#{versioned_foreign_key}", | |||
|
188 | :order => 'version', | |||
|
189 | :dependent => :delete_all | |||
|
190 | }.merge(options[:association_options] || {}) | |||
|
191 | ||||
|
192 | if block_given? | |||
|
193 | extension_module_name = "#{versioned_class_name}Extension" | |||
|
194 | silence_warnings do | |||
|
195 | self.const_set(extension_module_name, Module.new(&extension)) | |||
|
196 | end | |||
|
197 | ||||
|
198 | options[:extend] = self.const_get(extension_module_name) | |||
|
199 | end | |||
|
200 | ||||
|
201 | class_eval do | |||
|
202 | has_many :versions, version_association_options | |||
|
203 | before_save :set_new_version | |||
|
204 | after_create :save_version_on_create | |||
|
205 | after_update :save_version | |||
|
206 | after_save :clear_old_versions | |||
|
207 | after_save :clear_changed_attributes | |||
|
208 | ||||
|
209 | unless options[:if_changed].nil? | |||
|
210 | self.track_changed_attributes = true | |||
|
211 | options[:if_changed] = [options[:if_changed]] unless options[:if_changed].is_a?(Array) | |||
|
212 | options[:if_changed].each do |attr_name| | |||
|
213 | define_method("#{attr_name}=") do |value| | |||
|
214 | write_changed_attribute attr_name, value | |||
|
215 | end | |||
|
216 | end | |||
|
217 | end | |||
|
218 | ||||
|
219 | include options[:extend] if options[:extend].is_a?(Module) | |||
|
220 | end | |||
|
221 | ||||
|
222 | # create the dynamic versioned model | |||
|
223 | const_set(versioned_class_name, Class.new(ActiveRecord::Base)).class_eval do | |||
|
224 | def self.reloadable? ; false ; end | |||
|
225 | end | |||
|
226 | ||||
|
227 | versioned_class.set_table_name versioned_table_name | |||
|
228 | versioned_class.belongs_to self.to_s.demodulize.underscore.to_sym, | |||
|
229 | :class_name => "::#{self.to_s}", | |||
|
230 | :foreign_key => versioned_foreign_key | |||
|
231 | 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 | |||
|
233 | end | |||
|
234 | end | |||
|
235 | ||||
|
236 | module ActMethods | |||
|
237 | def self.included(base) # :nodoc: | |||
|
238 | base.extend ClassMethods | |||
|
239 | end | |||
|
240 | ||||
|
241 | # Saves a version of the model if applicable | |||
|
242 | def save_version | |||
|
243 | save_version_on_create if save_version? | |||
|
244 | end | |||
|
245 | ||||
|
246 | # 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 | |||
|
248 | rev = self.class.versioned_class.new | |||
|
249 | self.clone_versioned_model(self, rev) | |||
|
250 | rev.version = send(self.class.version_column) | |||
|
251 | rev.send("#{self.class.versioned_foreign_key}=", self.id) | |||
|
252 | rev.save | |||
|
253 | end | |||
|
254 | ||||
|
255 | # 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. | |||
|
257 | def clear_old_versions | |||
|
258 | return if self.class.max_version_limit == 0 | |||
|
259 | excess_baggage = send(self.class.version_column).to_i - self.class.max_version_limit | |||
|
260 | 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}" | |||
|
262 | self.class.versioned_class.connection.execute sql | |||
|
263 | end | |||
|
264 | end | |||
|
265 | ||||
|
266 | # Finds a specific version of this model. | |||
|
267 | def find_version(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 | |||
|
277 | ||||
|
278 | # Reverts a model to a given version. Takes either a version number or an instance of the versioned model | |||
|
279 | def revert_to(version) | |||
|
280 | 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? | |||
|
282 | else | |||
|
283 | return false unless version = find_version(version) | |||
|
284 | end | |||
|
285 | self.clone_versioned_model(version, self) | |||
|
286 | self.send("#{self.class.version_column}=", version.version) | |||
|
287 | true | |||
|
288 | end | |||
|
289 | ||||
|
290 | # Reverts a model to a given version and saves the model. | |||
|
291 | # Takes either a version number or an instance of the versioned model | |||
|
292 | def revert_to!(version) | |||
|
293 | revert_to(version) ? save_without_revision : false | |||
|
294 | end | |||
|
295 | ||||
|
296 | # Temporarily turns off Optimistic Locking while saving. Used when reverting so that a new version is not created. | |||
|
297 | def save_without_revision | |||
|
298 | save_without_revision! | |||
|
299 | true | |||
|
300 | rescue | |||
|
301 | false | |||
|
302 | end | |||
|
303 | ||||
|
304 | def save_without_revision! | |||
|
305 | without_locking do | |||
|
306 | without_revision do | |||
|
307 | save! | |||
|
308 | end | |||
|
309 | end | |||
|
310 | end | |||
|
311 | ||||
|
312 | # Returns an array of attribute keys that are versioned. See non_versioned_columns | |||
|
313 | def versioned_attributes | |||
|
314 | self.attributes.keys.select { |k| !self.class.non_versioned_columns.include?(k) } | |||
|
315 | end | |||
|
316 | ||||
|
317 | # 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. | |||
|
319 | def changed?(attr_name = nil) | |||
|
320 | attr_name.nil? ? | |||
|
321 | (!self.class.track_changed_attributes || (changed_attributes && changed_attributes.length > 0)) : | |||
|
322 | (changed_attributes && changed_attributes.include?(attr_name.to_s)) | |||
|
323 | end | |||
|
324 | ||||
|
325 | # keep old dirty? method | |||
|
326 | alias_method :dirty?, :changed? | |||
|
327 | ||||
|
328 | # Clones a model. Used when saving a new version or reverting a model's version. | |||
|
329 | def clone_versioned_model(orig_model, new_model) | |||
|
330 | self.versioned_attributes.each do |key| | |||
|
331 | new_model.send("#{key}=", orig_model.attributes[key]) if orig_model.has_attribute?(key) | |||
|
332 | end | |||
|
333 | ||||
|
334 | if orig_model.is_a?(self.class.versioned_class) | |||
|
335 | new_model[new_model.class.inheritance_column] = orig_model[self.class.versioned_inheritance_column] | |||
|
336 | elsif new_model.is_a?(self.class.versioned_class) | |||
|
337 | new_model[self.class.versioned_inheritance_column] = orig_model[orig_model.class.inheritance_column] | |||
|
338 | end | |||
|
339 | end | |||
|
340 | ||||
|
341 | # Checks whether a new version shall be saved or not. Calls <tt>version_condition_met?</tt> and <tt>changed?</tt>. | |||
|
342 | def save_version? | |||
|
343 | version_condition_met? && changed? | |||
|
344 | end | |||
|
345 | ||||
|
346 | # 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. | |||
|
348 | def version_condition_met? | |||
|
349 | case | |||
|
350 | when version_condition.is_a?(Symbol) | |||
|
351 | send(version_condition) | |||
|
352 | when version_condition.respond_to?(:call) && (version_condition.arity == 1 || version_condition.arity == -1) | |||
|
353 | version_condition.call(self) | |||
|
354 | else | |||
|
355 | version_condition | |||
|
356 | end | |||
|
357 | end | |||
|
358 | ||||
|
359 | # Executes the block with the versioning callbacks disabled. | |||
|
360 | # | |||
|
361 | # @foo.without_revision do | |||
|
362 | # @foo.save | |||
|
363 | # end | |||
|
364 | # | |||
|
365 | def without_revision(&block) | |||
|
366 | self.class.without_revision(&block) | |||
|
367 | end | |||
|
368 | ||||
|
369 | # Turns off optimistic locking for the duration of the block | |||
|
370 | # | |||
|
371 | # @foo.without_locking do | |||
|
372 | # @foo.save | |||
|
373 | # end | |||
|
374 | # | |||
|
375 | def without_locking(&block) | |||
|
376 | self.class.without_locking(&block) | |||
|
377 | end | |||
|
378 | ||||
|
379 | def empty_callback() end #:nodoc: | |||
|
380 | ||||
|
381 | protected | |||
|
382 | # 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 | |||
|
384 | self.send("#{self.class.version_column}=", self.next_version) if new_record? || (!locking_enabled? && save_version?) | |||
|
385 | end | |||
|
386 | ||||
|
387 | # Gets the next available version for the current record, or 1 for a new record | |||
|
388 | def next_version | |||
|
389 | return 1 if new_record? | |||
|
390 | (versions.calculate(:max, :version) || 0) + 1 | |||
|
391 | end | |||
|
392 | ||||
|
393 | # clears current changed attributes. Called after save. | |||
|
394 | def clear_changed_attributes | |||
|
395 | self.changed_attributes = [] | |||
|
396 | end | |||
|
397 | ||||
|
398 | def write_changed_attribute(attr_name, attr_value) | |||
|
399 | # 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) | |||
|
401 | (self.changed_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) | |||
|
403 | end | |||
|
404 | ||||
|
405 | private | |||
|
406 | CALLBACKS.each do |attr_name| | |||
|
407 | alias_method "orig_#{attr_name}".to_sym, attr_name | |||
|
408 | end | |||
|
409 | ||||
|
410 | module ClassMethods | |||
|
411 | # Finds a specific version of a specific row of this model | |||
|
412 | def find_version(id, version) | |||
|
413 | find_versions(id, | |||
|
414 | :conditions => ["#{versioned_foreign_key} = ? AND version = ?", id, version], | |||
|
415 | :limit => 1).first | |||
|
416 | end | |||
|
417 | ||||
|
418 | # Finds versions of a specific model. Takes an options hash like <tt>find</tt> | |||
|
419 | def find_versions(id, options = {}) | |||
|
420 | versioned_class.find :all, { | |||
|
421 | :conditions => ["#{versioned_foreign_key} = ?", id], | |||
|
422 | :order => 'version' }.merge(options) | |||
|
423 | end | |||
|
424 | ||||
|
425 | # Returns an array of columns that are versioned. See non_versioned_columns | |||
|
426 | def versioned_columns | |||
|
427 | self.columns.select { |c| !non_versioned_columns.include?(c.name) } | |||
|
428 | end | |||
|
429 | ||||
|
430 | # Returns an instance of the dynamic versioned model | |||
|
431 | def versioned_class | |||
|
432 | const_get versioned_class_name | |||
|
433 | end | |||
|
434 | ||||
|
435 | # Rake migration task to create the versioned table using options passed to acts_as_versioned | |||
|
436 | def create_versioned_table(create_table_options = {}) | |||
|
437 | # 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 } | |||
|
439 | self.connection.add_column table_name, :version, :integer | |||
|
440 | end | |||
|
441 | ||||
|
442 | self.connection.create_table(versioned_table_name, create_table_options) do |t| | |||
|
443 | t.column versioned_foreign_key, :integer | |||
|
444 | t.column :version, :integer | |||
|
445 | end | |||
|
446 | ||||
|
447 | updated_col = nil | |||
|
448 | self.versioned_columns.each do |col| | |||
|
449 | 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, | |||
|
451 | :limit => col.limit, | |||
|
452 | :default => col.default | |||
|
453 | end | |||
|
454 | ||||
|
455 | if type_col = self.columns_hash[inheritance_column] | |||
|
456 | self.connection.add_column versioned_table_name, versioned_inheritance_column, type_col.type, | |||
|
457 | :limit => type_col.limit, | |||
|
458 | :default => type_col.default | |||
|
459 | end | |||
|
460 | ||||
|
461 | if updated_col.nil? | |||
|
462 | self.connection.add_column versioned_table_name, :updated_at, :timestamp | |||
|
463 | end | |||
|
464 | end | |||
|
465 | ||||
|
466 | # Rake migration task to drop the versioned table | |||
|
467 | def drop_versioned_table | |||
|
468 | self.connection.drop_table versioned_table_name | |||
|
469 | end | |||
|
470 | ||||
|
471 | # Executes the block with the versioning callbacks disabled. | |||
|
472 | # | |||
|
473 | # Foo.without_revision do | |||
|
474 | # @foo.save | |||
|
475 | # end | |||
|
476 | # | |||
|
477 | def without_revision(&block) | |||
|
478 | class_eval do | |||
|
479 | CALLBACKS.each do |attr_name| | |||
|
480 | alias_method attr_name, :empty_callback | |||
|
481 | end | |||
|
482 | end | |||
|
483 | result = block.call | |||
|
484 | class_eval do | |||
|
485 | CALLBACKS.each do |attr_name| | |||
|
486 | alias_method attr_name, "orig_#{attr_name}".to_sym | |||
|
487 | end | |||
|
488 | end | |||
|
489 | result | |||
|
490 | end | |||
|
491 | ||||
|
492 | # Turns off optimistic locking for the duration of the block | |||
|
493 | # | |||
|
494 | # Foo.without_locking do | |||
|
495 | # @foo.save | |||
|
496 | # end | |||
|
497 | # | |||
|
498 | def without_locking(&block) | |||
|
499 | current = ActiveRecord::Base.lock_optimistically | |||
|
500 | ActiveRecord::Base.lock_optimistically = false if current | |||
|
501 | result = block.call | |||
|
502 | ActiveRecord::Base.lock_optimistically = true if current | |||
|
503 | result | |||
|
504 | end | |||
|
505 | end | |||
|
506 | end | |||
|
507 | end | |||
|
508 | end | |||
|
509 | end | |||
|
510 | ||||
|
511 | ActiveRecord::Base.send :include, ActiveRecord::Acts::Versioned No newline at end of file |
@@ -0,0 +1,40 | |||||
|
1 | $:.unshift(File.dirname(__FILE__) + '/../lib') | |||
|
2 | ||||
|
3 | require 'test/unit' | |||
|
4 | require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb')) | |||
|
5 | require 'active_record/fixtures' | |||
|
6 | ||||
|
7 | config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml')) | |||
|
8 | ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log") | |||
|
9 | ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite']) | |||
|
10 | ||||
|
11 | load(File.dirname(__FILE__) + "/schema.rb") | |||
|
12 | ||||
|
13 | # set up custom sequence on widget_versions for DBs that support sequences | |||
|
14 | if ENV['DB'] == 'postgresql' | |||
|
15 | ActiveRecord::Base.connection.execute "DROP SEQUENCE widgets_seq;" rescue nil | |||
|
16 | ActiveRecord::Base.connection.remove_column :widget_versions, :id | |||
|
17 | 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');" | |||
|
19 | end | |||
|
20 | ||||
|
21 | Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/" | |||
|
22 | $LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path) | |||
|
23 | ||||
|
24 | 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 | self.use_transactional_fixtures = true | |||
|
35 | ||||
|
36 | # Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david) | |||
|
37 | self.use_instantiated_fixtures = false | |||
|
38 | ||||
|
39 | # Add more helper methods to be used by all tests here... | |||
|
40 | end No newline at end of file |
@@ -0,0 +1,18 | |||||
|
1 | sqlite: | |||
|
2 | :adapter: sqlite | |||
|
3 | :dbfile: acts_as_versioned_plugin.sqlite.db | |||
|
4 | sqlite3: | |||
|
5 | :adapter: sqlite3 | |||
|
6 | :dbfile: acts_as_versioned_plugin.sqlite3.db | |||
|
7 | postgresql: | |||
|
8 | :adapter: postgresql | |||
|
9 | :username: postgres | |||
|
10 | :password: postgres | |||
|
11 | :database: acts_as_versioned_plugin_test | |||
|
12 | :min_messages: ERROR | |||
|
13 | mysql: | |||
|
14 | :adapter: mysql | |||
|
15 | :host: localhost | |||
|
16 | :username: rails | |||
|
17 | :password: | |||
|
18 | :database: acts_as_versioned_plugin_test No newline at end of file |
@@ -0,0 +1,3 | |||||
|
1 | class Landmark < ActiveRecord::Base | |||
|
2 | acts_as_versioned :if_changed => [ :name, :longitude, :latitude ] | |||
|
3 | end |
@@ -0,0 +1,7 | |||||
|
1 | washington: | |||
|
2 | id: 1 | |||
|
3 | landmark_id: 1 | |||
|
4 | version: 1 | |||
|
5 | name: Washington, D.C. | |||
|
6 | latitude: 38.895 | |||
|
7 | longitude: -77.036667 |
@@ -0,0 +1,6 | |||||
|
1 | washington: | |||
|
2 | id: 1 | |||
|
3 | name: Washington, D.C. | |||
|
4 | latitude: 38.895 | |||
|
5 | longitude: -77.036667 | |||
|
6 | version: 1 |
@@ -0,0 +1,10 | |||||
|
1 | welcome: | |||
|
2 | id: 1 | |||
|
3 | title: Welcome to the weblog | |||
|
4 | lock_version: 24 | |||
|
5 | type: LockedPage | |||
|
6 | thinking: | |||
|
7 | id: 2 | |||
|
8 | title: So I was thinking | |||
|
9 | lock_version: 24 | |||
|
10 | type: SpecialLockedPage |
@@ -0,0 +1,27 | |||||
|
1 | welcome_1: | |||
|
2 | id: 1 | |||
|
3 | page_id: 1 | |||
|
4 | title: Welcome to the weblg | |||
|
5 | version: 23 | |||
|
6 | version_type: LockedPage | |||
|
7 | ||||
|
8 | welcome_2: | |||
|
9 | id: 2 | |||
|
10 | page_id: 1 | |||
|
11 | title: Welcome to the weblog | |||
|
12 | version: 24 | |||
|
13 | version_type: LockedPage | |||
|
14 | ||||
|
15 | thinking_1: | |||
|
16 | id: 3 | |||
|
17 | page_id: 2 | |||
|
18 | title: So I was thinking!!! | |||
|
19 | version: 23 | |||
|
20 | version_type: SpecialLockedPage | |||
|
21 | ||||
|
22 | thinking_2: | |||
|
23 | id: 4 | |||
|
24 | page_id: 2 | |||
|
25 | title: So I was thinking | |||
|
26 | version: 24 | |||
|
27 | version_type: SpecialLockedPage |
@@ -0,0 +1,13 | |||||
|
1 | class AddVersionedTables < ActiveRecord::Migration | |||
|
2 | def self.up | |||
|
3 | create_table("things") do |t| | |||
|
4 | t.column :title, :text | |||
|
5 | end | |||
|
6 | Thing.create_versioned_table | |||
|
7 | end | |||
|
8 | ||||
|
9 | def self.down | |||
|
10 | Thing.drop_versioned_table | |||
|
11 | drop_table "things" rescue nil | |||
|
12 | end | |||
|
13 | end No newline at end of file |
@@ -0,0 +1,43 | |||||
|
1 | class Page < ActiveRecord::Base | |||
|
2 | belongs_to :author | |||
|
3 | has_many :authors, :through => :versions, :order => 'name' | |||
|
4 | belongs_to :revisor, :class_name => 'Author' | |||
|
5 | has_many :revisors, :class_name => 'Author', :through => :versions, :order => 'name' | |||
|
6 | acts_as_versioned :if => :feeling_good? do | |||
|
7 | def self.included(base) | |||
|
8 | base.cattr_accessor :feeling_good | |||
|
9 | base.feeling_good = true | |||
|
10 | base.belongs_to :author | |||
|
11 | base.belongs_to :revisor, :class_name => 'Author' | |||
|
12 | end | |||
|
13 | ||||
|
14 | def feeling_good? | |||
|
15 | @@feeling_good == true | |||
|
16 | end | |||
|
17 | end | |||
|
18 | end | |||
|
19 | ||||
|
20 | module LockedPageExtension | |||
|
21 | def hello_world | |||
|
22 | 'hello_world' | |||
|
23 | end | |||
|
24 | end | |||
|
25 | ||||
|
26 | class LockedPage < ActiveRecord::Base | |||
|
27 | acts_as_versioned \ | |||
|
28 | :inheritance_column => :version_type, | |||
|
29 | :foreign_key => :page_id, | |||
|
30 | :table_name => :locked_pages_revisions, | |||
|
31 | :class_name => 'LockedPageRevision', | |||
|
32 | :version_column => :lock_version, | |||
|
33 | :limit => 2, | |||
|
34 | :if_changed => :title, | |||
|
35 | :extend => LockedPageExtension | |||
|
36 | end | |||
|
37 | ||||
|
38 | class SpecialLockedPage < LockedPage | |||
|
39 | end | |||
|
40 | ||||
|
41 | class Author < ActiveRecord::Base | |||
|
42 | has_many :pages | |||
|
43 | end No newline at end of file |
@@ -0,0 +1,16 | |||||
|
1 | welcome_2: | |||
|
2 | id: 1 | |||
|
3 | page_id: 1 | |||
|
4 | title: Welcome to the weblog | |||
|
5 | body: Such a lovely day | |||
|
6 | version: 24 | |||
|
7 | author_id: 1 | |||
|
8 | revisor_id: 1 | |||
|
9 | welcome_1: | |||
|
10 | id: 2 | |||
|
11 | page_id: 1 | |||
|
12 | title: Welcome to the weblg | |||
|
13 | body: Such a lovely day | |||
|
14 | version: 23 | |||
|
15 | author_id: 2 | |||
|
16 | revisor_id: 2 |
@@ -0,0 +1,7 | |||||
|
1 | welcome: | |||
|
2 | id: 1 | |||
|
3 | title: Welcome to the weblog | |||
|
4 | body: Such a lovely day | |||
|
5 | version: 24 | |||
|
6 | author_id: 1 | |||
|
7 | revisor_id: 1 No newline at end of file |
@@ -0,0 +1,6 | |||||
|
1 | class Widget < ActiveRecord::Base | |||
|
2 | acts_as_versioned :sequence_name => 'widgets_seq', :association_options => { | |||
|
3 | :dependent => nil, :order => 'version desc' | |||
|
4 | } | |||
|
5 | non_versioned_columns << 'foo' | |||
|
6 | end No newline at end of file |
@@ -0,0 +1,32 | |||||
|
1 | require File.join(File.dirname(__FILE__), 'abstract_unit') | |||
|
2 | ||||
|
3 | if ActiveRecord::Base.connection.supports_migrations? | |||
|
4 | class Thing < ActiveRecord::Base | |||
|
5 | attr_accessor :version | |||
|
6 | acts_as_versioned | |||
|
7 | end | |||
|
8 | ||||
|
9 | class MigrationTest < Test::Unit::TestCase | |||
|
10 | self.use_transactional_fixtures = false | |||
|
11 | def teardown | |||
|
12 | ActiveRecord::Base.connection.initialize_schema_information | |||
|
13 | ActiveRecord::Base.connection.update "UPDATE schema_info SET version = 0" | |||
|
14 | ||||
|
15 | Thing.connection.drop_table "things" rescue nil | |||
|
16 | Thing.connection.drop_table "thing_versions" rescue nil | |||
|
17 | Thing.reset_column_information | |||
|
18 | end | |||
|
19 | ||||
|
20 | def test_versioned_migration | |||
|
21 | assert_raises(ActiveRecord::StatementInvalid) { Thing.create :title => 'blah blah' } | |||
|
22 | # take 'er up | |||
|
23 | ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/') | |||
|
24 | t = Thing.create :title => 'blah blah' | |||
|
25 | assert_equal 1, t.versions.size | |||
|
26 | ||||
|
27 | # now lets take 'er back down | |||
|
28 | ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/') | |||
|
29 | assert_raises(ActiveRecord::StatementInvalid) { Thing.create :title => 'blah blah' } | |||
|
30 | end | |||
|
31 | end | |||
|
32 | end |
@@ -0,0 +1,68 | |||||
|
1 | ActiveRecord::Schema.define(:version => 0) do | |||
|
2 | create_table :pages, :force => true do |t| | |||
|
3 | t.column :version, :integer | |||
|
4 | t.column :title, :string, :limit => 255 | |||
|
5 | t.column :body, :text | |||
|
6 | t.column :updated_on, :datetime | |||
|
7 | t.column :author_id, :integer | |||
|
8 | t.column :revisor_id, :integer | |||
|
9 | end | |||
|
10 | ||||
|
11 | create_table :page_versions, :force => true do |t| | |||
|
12 | t.column :page_id, :integer | |||
|
13 | t.column :version, :integer | |||
|
14 | t.column :title, :string, :limit => 255 | |||
|
15 | t.column :body, :text | |||
|
16 | t.column :updated_on, :datetime | |||
|
17 | t.column :author_id, :integer | |||
|
18 | t.column :revisor_id, :integer | |||
|
19 | end | |||
|
20 | ||||
|
21 | create_table :authors, :force => true do |t| | |||
|
22 | t.column :page_id, :integer | |||
|
23 | t.column :name, :string | |||
|
24 | end | |||
|
25 | ||||
|
26 | create_table :locked_pages, :force => true do |t| | |||
|
27 | t.column :lock_version, :integer | |||
|
28 | t.column :title, :string, :limit => 255 | |||
|
29 | t.column :type, :string, :limit => 255 | |||
|
30 | end | |||
|
31 | ||||
|
32 | create_table :locked_pages_revisions, :force => true do |t| | |||
|
33 | t.column :page_id, :integer | |||
|
34 | t.column :version, :integer | |||
|
35 | t.column :title, :string, :limit => 255 | |||
|
36 | t.column :version_type, :string, :limit => 255 | |||
|
37 | t.column :updated_at, :datetime | |||
|
38 | end | |||
|
39 | ||||
|
40 | create_table :widgets, :force => true do |t| | |||
|
41 | t.column :name, :string, :limit => 50 | |||
|
42 | t.column :foo, :string | |||
|
43 | t.column :version, :integer | |||
|
44 | t.column :updated_at, :datetime | |||
|
45 | end | |||
|
46 | ||||
|
47 | create_table :widget_versions, :force => true do |t| | |||
|
48 | t.column :widget_id, :integer | |||
|
49 | t.column :name, :string, :limit => 50 | |||
|
50 | t.column :version, :integer | |||
|
51 | t.column :updated_at, :datetime | |||
|
52 | end | |||
|
53 | ||||
|
54 | create_table :landmarks, :force => true do |t| | |||
|
55 | t.column :name, :string | |||
|
56 | t.column :latitude, :float | |||
|
57 | t.column :longitude, :float | |||
|
58 | t.column :version, :integer | |||
|
59 | end | |||
|
60 | ||||
|
61 | create_table :landmark_versions, :force => true do |t| | |||
|
62 | t.column :landmark_id, :integer | |||
|
63 | t.column :name, :string | |||
|
64 | t.column :latitude, :float | |||
|
65 | t.column :longitude, :float | |||
|
66 | t.column :version, :integer | |||
|
67 | end | |||
|
68 | end |
@@ -0,0 +1,313 | |||||
|
1 | require File.join(File.dirname(__FILE__), 'abstract_unit') | |||
|
2 | require File.join(File.dirname(__FILE__), 'fixtures/page') | |||
|
3 | require File.join(File.dirname(__FILE__), 'fixtures/widget') | |||
|
4 | ||||
|
5 | class VersionedTest < Test::Unit::TestCase | |||
|
6 | fixtures :pages, :page_versions, :locked_pages, :locked_pages_revisions, :authors, :landmarks, :landmark_versions | |||
|
7 | ||||
|
8 | def test_saves_versioned_copy | |||
|
9 | p = Page.create :title => 'first title', :body => 'first body' | |||
|
10 | assert !p.new_record? | |||
|
11 | assert_equal 1, p.versions.size | |||
|
12 | assert_equal 1, p.version | |||
|
13 | assert_instance_of Page.versioned_class, p.versions.first | |||
|
14 | end | |||
|
15 | ||||
|
16 | def test_saves_without_revision | |||
|
17 | p = pages(:welcome) | |||
|
18 | old_versions = p.versions.count | |||
|
19 | ||||
|
20 | p.save_without_revision | |||
|
21 | ||||
|
22 | p.without_revision do | |||
|
23 | p.update_attributes :title => 'changed' | |||
|
24 | end | |||
|
25 | ||||
|
26 | assert_equal old_versions, p.versions.count | |||
|
27 | end | |||
|
28 | ||||
|
29 | def test_rollback_with_version_number | |||
|
30 | p = pages(:welcome) | |||
|
31 | assert_equal 24, p.version | |||
|
32 | assert_equal 'Welcome to the weblog', p.title | |||
|
33 | ||||
|
34 | assert p.revert_to!(p.versions.first.version), "Couldn't revert to 23" | |||
|
35 | assert_equal 23, p.version | |||
|
36 | assert_equal 'Welcome to the weblg', p.title | |||
|
37 | end | |||
|
38 | ||||
|
39 | def test_versioned_class_name | |||
|
40 | assert_equal 'Version', Page.versioned_class_name | |||
|
41 | assert_equal 'LockedPageRevision', LockedPage.versioned_class_name | |||
|
42 | end | |||
|
43 | ||||
|
44 | def test_versioned_class | |||
|
45 | assert_equal Page::Version, Page.versioned_class | |||
|
46 | assert_equal LockedPage::LockedPageRevision, LockedPage.versioned_class | |||
|
47 | end | |||
|
48 | ||||
|
49 | def test_special_methods | |||
|
50 | assert_nothing_raised { pages(:welcome).feeling_good? } | |||
|
51 | 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).versions.first.hello_world } | |||
|
54 | end | |||
|
55 | ||||
|
56 | def test_rollback_with_version_class | |||
|
57 | p = pages(:welcome) | |||
|
58 | assert_equal 24, p.version | |||
|
59 | assert_equal 'Welcome to the weblog', p.title | |||
|
60 | ||||
|
61 | assert p.revert_to!(p.versions.first), "Couldn't revert to 23" | |||
|
62 | assert_equal 23, p.version | |||
|
63 | assert_equal 'Welcome to the weblg', p.title | |||
|
64 | end | |||
|
65 | ||||
|
66 | def test_rollback_fails_with_invalid_revision | |||
|
67 | p = locked_pages(:welcome) | |||
|
68 | assert !p.revert_to!(locked_pages(:thinking)) | |||
|
69 | end | |||
|
70 | ||||
|
71 | def test_saves_versioned_copy_with_options | |||
|
72 | p = LockedPage.create :title => 'first title' | |||
|
73 | assert !p.new_record? | |||
|
74 | assert_equal 1, p.versions.size | |||
|
75 | assert_instance_of LockedPage.versioned_class, p.versions.first | |||
|
76 | end | |||
|
77 | ||||
|
78 | def test_rollback_with_version_number_with_options | |||
|
79 | p = locked_pages(:welcome) | |||
|
80 | assert_equal 'Welcome to the weblog', p.title | |||
|
81 | assert_equal 'LockedPage', p.versions.first.version_type | |||
|
82 | ||||
|
83 | 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 'LockedPage', p.versions.first.version_type | |||
|
86 | end | |||
|
87 | ||||
|
88 | def test_rollback_with_version_class_with_options | |||
|
89 | p = locked_pages(:welcome) | |||
|
90 | assert_equal 'Welcome to the weblog', p.title | |||
|
91 | assert_equal 'LockedPage', p.versions.first.version_type | |||
|
92 | ||||
|
93 | assert p.revert_to!(p.versions.first), "Couldn't revert to 1" | |||
|
94 | assert_equal 'Welcome to the weblg', p.title | |||
|
95 | assert_equal 'LockedPage', p.versions.first.version_type | |||
|
96 | end | |||
|
97 | ||||
|
98 | def test_saves_versioned_copy_with_sti | |||
|
99 | p = SpecialLockedPage.create :title => 'first title' | |||
|
100 | assert !p.new_record? | |||
|
101 | assert_equal 1, p.versions.size | |||
|
102 | assert_instance_of LockedPage.versioned_class, p.versions.first | |||
|
103 | assert_equal 'SpecialLockedPage', p.versions.first.version_type | |||
|
104 | end | |||
|
105 | ||||
|
106 | def test_rollback_with_version_number_with_sti | |||
|
107 | p = locked_pages(:thinking) | |||
|
108 | assert_equal 'So I was thinking', p.title | |||
|
109 | ||||
|
110 | 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 'SpecialLockedPage', p.versions.first.version_type | |||
|
113 | end | |||
|
114 | ||||
|
115 | def test_lock_version_works_with_versioning | |||
|
116 | p = locked_pages(:thinking) | |||
|
117 | p2 = LockedPage.find(p.id) | |||
|
118 | ||||
|
119 | p.title = 'fresh title' | |||
|
120 | p.save | |||
|
121 | assert_equal 2, p.versions.size # limit! | |||
|
122 | ||||
|
123 | assert_raises(ActiveRecord::StaleObjectError) do | |||
|
124 | p2.title = 'stale title' | |||
|
125 | p2.save | |||
|
126 | end | |||
|
127 | end | |||
|
128 | ||||
|
129 | def test_version_if_condition | |||
|
130 | p = Page.create :title => "title" | |||
|
131 | assert_equal 1, p.version | |||
|
132 | ||||
|
133 | Page.feeling_good = false | |||
|
134 | p.save | |||
|
135 | assert_equal 1, p.version | |||
|
136 | Page.feeling_good = true | |||
|
137 | end | |||
|
138 | ||||
|
139 | def test_version_if_condition2 | |||
|
140 | # set new if condition | |||
|
141 | Page.class_eval do | |||
|
142 | def new_feeling_good() title[0..0] == 'a'; end | |||
|
143 | alias_method :old_feeling_good, :feeling_good? | |||
|
144 | alias_method :feeling_good?, :new_feeling_good | |||
|
145 | end | |||
|
146 | ||||
|
147 | p = Page.create :title => "title" | |||
|
148 | assert_equal 1, p.version # version does not increment | |||
|
149 | assert_equal 1, p.versions(true).size | |||
|
150 | ||||
|
151 | p.update_attributes(:title => 'new title') | |||
|
152 | assert_equal 1, p.version # version does not increment | |||
|
153 | assert_equal 1, p.versions(true).size | |||
|
154 | ||||
|
155 | p.update_attributes(:title => 'a title') | |||
|
156 | assert_equal 2, p.version | |||
|
157 | assert_equal 2, p.versions(true).size | |||
|
158 | ||||
|
159 | # reset original if condition | |||
|
160 | Page.class_eval { alias_method :feeling_good?, :old_feeling_good } | |||
|
161 | end | |||
|
162 | ||||
|
163 | def test_version_if_condition_with_block | |||
|
164 | # set new if condition | |||
|
165 | old_condition = Page.version_condition | |||
|
166 | Page.version_condition = Proc.new { |page| page.title[0..0] == 'b' } | |||
|
167 | ||||
|
168 | p = Page.create :title => "title" | |||
|
169 | assert_equal 1, p.version # version does not increment | |||
|
170 | assert_equal 1, p.versions(true).size | |||
|
171 | ||||
|
172 | p.update_attributes(:title => 'a title') | |||
|
173 | assert_equal 1, p.version # version does not increment | |||
|
174 | assert_equal 1, p.versions(true).size | |||
|
175 | ||||
|
176 | p.update_attributes(:title => 'b title') | |||
|
177 | assert_equal 2, p.version | |||
|
178 | assert_equal 2, p.versions(true).size | |||
|
179 | ||||
|
180 | # reset original if condition | |||
|
181 | Page.version_condition = old_condition | |||
|
182 | end | |||
|
183 | ||||
|
184 | def test_version_no_limit | |||
|
185 | p = Page.create :title => "title", :body => 'first body' | |||
|
186 | p.save | |||
|
187 | p.save | |||
|
188 | 5.times do |i| | |||
|
189 | assert_page_title p, i | |||
|
190 | end | |||
|
191 | end | |||
|
192 | ||||
|
193 | def test_version_max_limit | |||
|
194 | p = LockedPage.create :title => "title" | |||
|
195 | p.update_attributes(:title => "title1") | |||
|
196 | p.update_attributes(:title => "title2") | |||
|
197 | 5.times do |i| | |||
|
198 | assert_page_title p, i, :lock_version | |||
|
199 | assert p.versions(true).size <= 2, "locked version can only store 2 versions" | |||
|
200 | end | |||
|
201 | end | |||
|
202 | ||||
|
203 | def test_track_changed_attributes_default_value | |||
|
204 | assert !Page.track_changed_attributes | |||
|
205 | assert LockedPage.track_changed_attributes | |||
|
206 | assert SpecialLockedPage.track_changed_attributes | |||
|
207 | end | |||
|
208 | ||||
|
209 | def test_version_order | |||
|
210 | assert_equal 23, pages(:welcome).versions.first.version | |||
|
211 | 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 | |||
|
215 | ||||
|
216 | def test_track_changed_attributes | |||
|
217 | p = LockedPage.create :title => "title" | |||
|
218 | assert_equal 1, p.lock_version | |||
|
219 | assert_equal 1, p.versions(true).size | |||
|
220 | ||||
|
221 | p.title = 'title' | |||
|
222 | assert !p.save_version? | |||
|
223 | p.save | |||
|
224 | assert_equal 2, p.lock_version # still increments version because of optimistic locking | |||
|
225 | assert_equal 1, p.versions(true).size | |||
|
226 | ||||
|
227 | p.title = 'updated title' | |||
|
228 | assert p.save_version? | |||
|
229 | p.save | |||
|
230 | assert_equal 3, p.lock_version | |||
|
231 | assert_equal 1, p.versions(true).size # version 1 deleted | |||
|
232 | ||||
|
233 | p.title = 'updated title!' | |||
|
234 | assert p.save_version? | |||
|
235 | p.save | |||
|
236 | assert_equal 4, p.lock_version | |||
|
237 | assert_equal 2, p.versions(true).size # version 1 deleted | |||
|
238 | end | |||
|
239 | ||||
|
240 | def assert_page_title(p, i, version_field = :version) | |||
|
241 | p.title = "title#{i}" | |||
|
242 | p.save | |||
|
243 | assert_equal "title#{i}", p.title | |||
|
244 | assert_equal (i+4), p.send(version_field) | |||
|
245 | end | |||
|
246 | ||||
|
247 | def test_find_versions | |||
|
248 | assert_equal 2, locked_pages(:welcome).versions.size | |||
|
249 | assert_equal 1, locked_pages(:welcome).find_versions(:conditions => ['title LIKE ?', '%weblog%']).length | |||
|
250 | assert_equal 2, locked_pages(:welcome).find_versions(:conditions => ['title LIKE ?', '%web%']).length | |||
|
251 | assert_equal 0, locked_pages(:thinking).find_versions(:conditions => ['title LIKE ?', '%web%']).length | |||
|
252 | assert_equal 2, locked_pages(:welcome).find_versions.length | |||
|
253 | end | |||
|
254 | ||||
|
255 | def test_with_sequence | |||
|
256 | assert_equal 'widgets_seq', Widget.versioned_class.sequence_name | |||
|
257 | Widget.create :name => 'new widget' | |||
|
258 | Widget.create :name => 'new widget' | |||
|
259 | Widget.create :name => 'new widget' | |||
|
260 | assert_equal 3, Widget.count | |||
|
261 | assert_equal 3, Widget.versioned_class.count | |||
|
262 | end | |||
|
263 | ||||
|
264 | def test_has_many_through | |||
|
265 | assert_equal [authors(:caged), authors(:mly)], pages(:welcome).authors | |||
|
266 | end | |||
|
267 | ||||
|
268 | def test_has_many_through_with_custom_association | |||
|
269 | assert_equal [authors(:caged), authors(:mly)], pages(:welcome).revisors | |||
|
270 | end | |||
|
271 | ||||
|
272 | def test_referential_integrity | |||
|
273 | pages(:welcome).destroy | |||
|
274 | assert_equal 0, Page.count | |||
|
275 | assert_equal 0, Page::Version.count | |||
|
276 | end | |||
|
277 | ||||
|
278 | def test_association_options | |||
|
279 | association = Page.reflect_on_association(:versions) | |||
|
280 | options = association.options | |||
|
281 | assert_equal :delete_all, options[:dependent] | |||
|
282 | assert_equal 'version', options[:order] | |||
|
283 | ||||
|
284 | association = Widget.reflect_on_association(:versions) | |||
|
285 | options = association.options | |||
|
286 | assert_nil options[:dependent] | |||
|
287 | assert_equal 'version desc', options[:order] | |||
|
288 | assert_equal 'widget_id', options[:foreign_key] | |||
|
289 | ||||
|
290 | widget = Widget.create :name => 'new widget' | |||
|
291 | assert_equal 1, Widget.count | |||
|
292 | assert_equal 1, Widget.versioned_class.count | |||
|
293 | widget.destroy | |||
|
294 | assert_equal 0, Widget.count | |||
|
295 | assert_equal 1, Widget.versioned_class.count | |||
|
296 | end | |||
|
297 | ||||
|
298 | def test_versioned_records_should_belong_to_parent | |||
|
299 | page = pages(:welcome) | |||
|
300 | page_version = page.versions.last | |||
|
301 | assert_equal page, page_version.page | |||
|
302 | end | |||
|
303 | ||||
|
304 | def test_unchanged_attributes | |||
|
305 | landmarks(:washington).attributes = landmarks(:washington).attributes | |||
|
306 | assert !landmarks(:washington).changed? | |||
|
307 | end | |||
|
308 | ||||
|
309 | def test_unchanged_string_attributes | |||
|
310 | landmarks(:washington).attributes = landmarks(:washington).attributes.inject({}) { |params, (key, value)| params.update key => value.to_s } | |||
|
311 | assert !landmarks(:washington).changed? | |||
|
312 | end | |||
|
313 | end |
@@ -32,6 +32,10 class ApplicationController < ActionController::Base | |||||
32 | end |
|
32 | end | |
33 | end |
|
33 | end | |
34 |
|
34 | |||
|
35 | def logged_in_user_membership | |||
|
36 | @user_membership ||= Member.find(:first, :conditions => ["user_id=? and project_id=?", self.logged_in_user.id, @project.id]) | |||
|
37 | end | |||
|
38 | ||||
35 | # check if login is globally required to access the application |
|
39 | # check if login is globally required to access the application | |
36 | def check_if_login_required |
|
40 | def check_if_login_required | |
37 | require_login if Setting.login_required? |
|
41 | require_login if Setting.login_required? | |
@@ -89,6 +93,16 class ApplicationController < ActionController::Base | |||||
89 | render :nothing => true, :status => 403 |
|
93 | render :nothing => true, :status => 403 | |
90 | false |
|
94 | false | |
91 | end |
|
95 | end | |
|
96 | ||||
|
97 | # make sure that the user is a member of the project (or admin) if project is private | |||
|
98 | # used as a before_filter for actions that do not require any particular permission on the project | |||
|
99 | def check_project_privacy | |||
|
100 | return true if @project.is_public? | |||
|
101 | return false unless logged_in_user | |||
|
102 | return true if logged_in_user.admin? || logged_in_user_membership | |||
|
103 | render :nothing => true, :status => 403 | |||
|
104 | false | |||
|
105 | end | |||
92 |
|
106 | |||
93 | # store current uri in session. |
|
107 | # store current uri in session. | |
94 | # return to this location by calling redirect_back_or_default |
|
108 | # return to this location by calling redirect_back_or_default |
@@ -68,6 +68,10 class ProjectsController < ApplicationController | |||||
68 | @project.repository = Repository.new |
|
68 | @project.repository = Repository.new | |
69 | @project.repository.attributes = params[:repository] |
|
69 | @project.repository.attributes = params[:repository] | |
70 | end |
|
70 | end | |
|
71 | if "1" == params[:wiki_enabled] | |||
|
72 | @project.wiki = Wiki.new | |||
|
73 | @project.wiki.attributes = params[:wiki] | |||
|
74 | end | |||
71 | if @project.save |
|
75 | if @project.save | |
72 | flash[:notice] = l(:notice_successful_create) |
|
76 | flash[:notice] = l(:notice_successful_create) | |
73 | redirect_to :controller => 'admin', :action => 'projects' |
|
77 | redirect_to :controller => 'admin', :action => 'projects' | |
@@ -113,6 +117,15 class ProjectsController < ApplicationController | |||||
113 | @project.repository.update_attributes params[:repository] |
|
117 | @project.repository.update_attributes params[:repository] | |
114 | end |
|
118 | end | |
115 | end |
|
119 | end | |
|
120 | if params[:wiki_enabled] | |||
|
121 | case params[:wiki_enabled] | |||
|
122 | when "0" | |||
|
123 | @project.wiki.destroy | |||
|
124 | when "1" | |||
|
125 | @project.wiki ||= Wiki.new | |||
|
126 | @project.wiki.update_attributes params[:wiki] | |||
|
127 | end | |||
|
128 | end | |||
116 | @project.attributes = params[:project] |
|
129 | @project.attributes = params[:project] | |
117 | if @project.save |
|
130 | if @project.save | |
118 | flash[:notice] = l(:notice_successful_update) |
|
131 | flash[:notice] = l(:notice_successful_update) |
@@ -63,7 +63,7 module ApplicationHelper | |||||
63 | end |
|
63 | end | |
64 |
|
64 | |||
65 | def format_time(time) |
|
65 | def format_time(time) | |
66 | l_datetime(time) if time |
|
66 | l_datetime((time.is_a? String) ? time.to_time : time) if time | |
67 | end |
|
67 | end | |
68 |
|
68 | |||
69 | def day_name(day) |
|
69 | def day_name(day) | |
@@ -92,10 +92,42 module ApplicationHelper | |||||
92 | html |
|
92 | html | |
93 | end |
|
93 | end | |
94 |
|
94 | |||
95 | def textilizable(text) |
|
95 | # textilize text according to system settings and RedCloth availability | |
96 | text = (Setting.text_formatting == 'textile') && (ActionView::Helpers::TextHelper.method_defined? "textilize") ? RedCloth.new(h(text)).to_html : simple_format(auto_link(h(text))) |
|
96 | def textilizable(text, options = {}) | |
97 | # turn "#id" patterns into links to issues |
|
97 | # different methods for formatting wiki links | |
98 | text = text.gsub(/#(\d+)([^;\d])/, "<a href='/issues/show/\\1'>#\\1</a>\\2") |
|
98 | case options[:wiki_links] | |
|
99 | when :local | |||
|
100 | # used for local links to html files | |||
|
101 | format_wiki_link = Proc.new {|title| "#{title}.html" } | |||
|
102 | when :anchor | |||
|
103 | # used for single-file wiki export | |||
|
104 | format_wiki_link = Proc.new {|title| "##{title}" } | |||
|
105 | else | |||
|
106 | if @project | |||
|
107 | format_wiki_link = Proc.new {|title| url_for :controller => 'wiki', :action => 'index', :id => @project, :page => title } | |||
|
108 | else | |||
|
109 | format_wiki_link = Proc.new {|title| title } | |||
|
110 | end | |||
|
111 | end | |||
|
112 | ||||
|
113 | # turn wiki links into textile links: | |||
|
114 | # example: | |||
|
115 | # [[link]] -> "link":link | |||
|
116 | # [[link|title]] -> "title":link | |||
|
117 | text = text.gsub(/\[\[([^\]\|]+)(\|([^\]\|]+))?\]\]/) {|m| "\"#{$3 || $1}\":" + format_wiki_link.call(Wiki.titleize($1)) } | |||
|
118 | ||||
|
119 | # turn issue ids to textile links | |||
|
120 | # example: | |||
|
121 | # #52 -> "#52":/issues/show/52 | |||
|
122 | text = text.gsub(/#(\d+)([\s\.\(\)\-,:;])/) {|m| "\"##{$1}\":" + url_for(:controller => 'issues', :action => 'show', :id => $1) + $2 } | |||
|
123 | ||||
|
124 | # turn revision ids to textile links (@project needed) | |||
|
125 | # example: | |||
|
126 | # r52 -> "r52":/repositories/revision/6?rev=52 (@project.id is 6) | |||
|
127 | text = text.gsub(/r(\d+)([\s\.\(\)\-,:;])/) {|m| "\"r#{$1}\":" + url_for(:controller => 'repositories', :action => 'revision', :id => @project.id, :rev => $1) + $2 } if @project | |||
|
128 | ||||
|
129 | # finally textilize text | |||
|
130 | text = (Setting.text_formatting == 'textile') && (ActionView::Helpers::TextHelper.method_defined? "textilize") ? auto_link(RedCloth.new(text, [:filter_html]).to_html) : simple_format(auto_link(h(text))) | |||
99 | end |
|
131 | end | |
100 |
|
132 | |||
101 | def error_messages_for(object_name, options = {}) |
|
133 | def error_messages_for(object_name, options = {}) |
@@ -26,13 +26,14 class Project < ActiveRecord::Base | |||||
26 | has_many :news, :dependent => :delete_all, :include => :author |
|
26 | has_many :news, :dependent => :delete_all, :include => :author | |
27 | has_many :issue_categories, :dependent => :delete_all, :order => "issue_categories.name" |
|
27 | has_many :issue_categories, :dependent => :delete_all, :order => "issue_categories.name" | |
28 | has_one :repository, :dependent => :destroy |
|
28 | has_one :repository, :dependent => :destroy | |
|
29 | has_one :wiki, :dependent => :destroy | |||
29 | has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => 'custom_fields_projects', :association_foreign_key => 'custom_field_id' |
|
30 | has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => 'custom_fields_projects', :association_foreign_key => 'custom_field_id' | |
30 | acts_as_tree :order => "name", :counter_cache => true |
|
31 | acts_as_tree :order => "name", :counter_cache => true | |
31 |
|
32 | |||
32 | validates_presence_of :name, :description |
|
33 | validates_presence_of :name, :description | |
33 | validates_uniqueness_of :name |
|
34 | validates_uniqueness_of :name | |
34 | validates_associated :custom_values, :on => :update |
|
35 | validates_associated :custom_values, :on => :update | |
35 | validates_associated :repository |
|
36 | validates_associated :repository, :wiki | |
36 | validates_format_of :name, :with => /^[\w\s\'\-]*$/i |
|
37 | validates_format_of :name, :with => /^[\w\s\'\-]*$/i | |
37 |
|
38 | |||
38 | # returns latest created projects |
|
39 | # returns latest created projects |
@@ -91,6 +91,7 | |||||
91 | <%= link_to l(:label_change_log), {:controller => 'projects', :action => 'changelog', :id => @project }, :class => "menuItem" %> |
|
91 | <%= link_to l(:label_change_log), {:controller => 'projects', :action => 'changelog', :id => @project }, :class => "menuItem" %> | |
92 | <%= link_to l(:label_roadmap), {:controller => 'projects', :action => 'roadmap', :id => @project }, :class => "menuItem" %> |
|
92 | <%= link_to l(:label_roadmap), {:controller => 'projects', :action => 'roadmap', :id => @project }, :class => "menuItem" %> | |
93 | <%= link_to l(:label_document_plural), {:controller => 'projects', :action => 'list_documents', :id => @project }, :class => "menuItem" %> |
|
93 | <%= link_to l(:label_document_plural), {:controller => 'projects', :action => 'list_documents', :id => @project }, :class => "menuItem" %> | |
|
94 | <%= link_to l(:label_wiki), {:controller => 'wiki', :id => @project, :page => nil }, :class => "menuItem" if @project.wiki and !@project.wiki.new_record? %> | |||
94 | <%= link_to l(:label_member_plural), {:controller => 'projects', :action => 'list_members', :id => @project }, :class => "menuItem" %> |
|
95 | <%= link_to l(:label_member_plural), {:controller => 'projects', :action => 'list_members', :id => @project }, :class => "menuItem" %> | |
95 | <%= link_to l(:label_attachment_plural), {:controller => 'projects', :action => 'list_files', :id => @project }, :class => "menuItem" %> |
|
96 | <%= link_to l(:label_attachment_plural), {:controller => 'projects', :action => 'list_files', :id => @project }, :class => "menuItem" %> | |
96 | <%= link_to l(:label_search), {:controller => 'projects', :action => 'search', :id => @project }, :class => "menuItem" %> |
|
97 | <%= link_to l(:label_search), {:controller => 'projects', :action => 'search', :id => @project }, :class => "menuItem" %> | |
@@ -115,6 +116,7 | |||||
115 | <li><%= link_to l(:label_change_log), :controller => 'projects', :action => 'changelog', :id => @project %></li> |
|
116 | <li><%= link_to l(:label_change_log), :controller => 'projects', :action => 'changelog', :id => @project %></li> | |
116 | <li><%= link_to l(:label_roadmap), :controller => 'projects', :action => 'roadmap', :id => @project %></li> |
|
117 | <li><%= link_to l(:label_roadmap), :controller => 'projects', :action => 'roadmap', :id => @project %></li> | |
117 | <li><%= link_to l(:label_document_plural), :controller => 'projects', :action => 'list_documents', :id => @project %></li> |
|
118 | <li><%= link_to l(:label_document_plural), :controller => 'projects', :action => 'list_documents', :id => @project %></li> | |
|
119 | <li><%= link_to l(:label_wiki), :controller => 'wiki', :id => @project, :page => nil if @project.wiki and !@project.wiki.new_record? %></li> | |||
118 | <li><%= link_to l(:label_member_plural), :controller => 'projects', :action => 'list_members', :id => @project %></li> |
|
120 | <li><%= link_to l(:label_member_plural), :controller => 'projects', :action => 'list_members', :id => @project %></li> | |
119 | <li><%= link_to l(:label_attachment_plural), :controller => 'projects', :action => 'list_files', :id => @project %></li> |
|
121 | <li><%= link_to l(:label_attachment_plural), :controller => 'projects', :action => 'list_files', :id => @project %></li> | |
120 | <li><%= link_to l(:label_search), :controller => 'projects', :action => 'search', :id => @project %></li> |
|
122 | <li><%= link_to l(:label_search), :controller => 'projects', :action => 'search', :id => @project %></li> |
@@ -38,6 +38,20 | |||||
38 | <%= javascript_tag "Element.hide('repository');" if @project.repository.nil? %> |
|
38 | <%= javascript_tag "Element.hide('repository');" if @project.repository.nil? %> | |
39 | </div> |
|
39 | </div> | |
40 |
|
40 | |||
|
41 | <div class="box"> | |||
|
42 | <h3><%= check_box_tag "wiki_enabled", 1, !@project.wiki.nil?, :onclick => "Element.toggle('wiki');" %> <%= l(:label_wiki) %></h3> | |||
|
43 | <%= hidden_field_tag "wiki_enabled", 0 %> | |||
|
44 | <div id="wiki"> | |||
|
45 | <% fields_for :wiki, @project.wiki, { :builder => TabularFormBuilder, :lang => current_language} do |wiki| %> | |||
|
46 | <p><%= wiki.text_field :start_page, :size => 60, :required => true %></p> | |||
|
47 | <% # content_tag("div", "", :id => "wiki_start_page_auto_complete", :class => "auto_complete") + | |||
|
48 | # auto_complete_field("wiki_start_page", { :url => { :controller => 'wiki', :action => 'auto_complete_for_wiki_page', :id => @project } }) | |||
|
49 | %> | |||
|
50 | <% end %> | |||
|
51 | </div> | |||
|
52 | <%= javascript_tag "Element.hide('wiki');" if @project.wiki.nil? %> | |||
|
53 | </div> | |||
|
54 | ||||
41 | <% content_for :header_tags do %> |
|
55 | <% content_for :header_tags do %> | |
42 | <%= javascript_include_tag 'calendar/calendar' %> |
|
56 | <%= javascript_include_tag 'calendar/calendar' %> | |
43 | <%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %> |
|
57 | <%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %> |
@@ -38,6 +38,9 | |||||
38 | <p><label><%= l(:setting_text_formatting) %></label> |
|
38 | <p><label><%= l(:setting_text_formatting) %></label> | |
39 | <%= select_tag 'settings[text_formatting]', options_for_select( [[l(:label_none), 0], ["textile", "textile"]], Setting.text_formatting) %></p> |
|
39 | <%= select_tag 'settings[text_formatting]', options_for_select( [[l(:label_none), 0], ["textile", "textile"]], Setting.text_formatting) %></p> | |
40 |
|
40 | |||
|
41 | <p><label><%= l(:setting_wiki_compression) %></label> | |||
|
42 | <%= select_tag 'settings[wiki_compression]', options_for_select( [[l(:label_none), 0], ["gzip", "gzip"]], Setting.wiki_compression) %></p> | |||
|
43 | ||||
41 | </div> |
|
44 | </div> | |
42 | <%= submit_tag l(:button_save) %> |
|
45 | <%= submit_tag l(:button_save) %> | |
43 | <% end %> No newline at end of file |
|
46 | <% end %> |
@@ -9,7 +9,8 ActionController::Routing::Routes.draw do |map| | |||||
9 | # You can have the root of your site routed by hooking up '' |
|
9 | # You can have the root of your site routed by hooking up '' | |
10 | # -- just remember to delete public/index.html. |
|
10 | # -- just remember to delete public/index.html. | |
11 | map.connect '', :controller => "welcome" |
|
11 | map.connect '', :controller => "welcome" | |
12 |
|
12 | |||
|
13 | map.connect 'wiki/:id/:page/:action', :controller => 'wiki', :page => nil | |||
13 | map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow' |
|
14 | map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow' | |
14 | map.connect 'help/:ctrl/:page', :controller => 'help' |
|
15 | map.connect 'help/:ctrl/:page', :controller => 'help' | |
15 | #map.connect ':controller/:action/:id/:sort_key/:sort_order' |
|
16 | #map.connect ':controller/:action/:id/:sort_key/:sort_order' |
@@ -41,6 +41,8 mail_from: | |||||
41 | default: redmine@somenet.foo |
|
41 | default: redmine@somenet.foo | |
42 | text_formatting: |
|
42 | text_formatting: | |
43 | default: textile |
|
43 | default: textile | |
|
44 | wiki_compression: | |||
|
45 | default: "" | |||
44 | default_language: |
|
46 | default_language: | |
45 | default: en |
|
47 | default: en | |
46 | host_name: |
|
48 | host_name: |
@@ -142,6 +142,7 field_auth_source: Authentisierung Modus | |||||
142 | field_hide_mail: Mein email address verstecken |
|
142 | field_hide_mail: Mein email address verstecken | |
143 | field_comment: Anmerkung |
|
143 | field_comment: Anmerkung | |
144 | field_url: URL |
|
144 | field_url: URL | |
|
145 | field_start_page: Hauptseite | |||
145 |
|
146 | |||
146 | setting_app_title: Applikation Titel |
|
147 | setting_app_title: Applikation Titel | |
147 | setting_app_subtitle: Applikation Untertitel |
|
148 | setting_app_subtitle: Applikation Untertitel | |
@@ -154,6 +155,7 setting_issues_export_limit: Issues export limit | |||||
154 | setting_mail_from: Emission address |
|
155 | setting_mail_from: Emission address | |
155 | setting_host_name: Host Name |
|
156 | setting_host_name: Host Name | |
156 | setting_text_formatting: Textformatierung |
|
157 | setting_text_formatting: Textformatierung | |
|
158 | setting_wiki_compression: Wiki Geschichte Kompression | |||
157 |
|
159 | |||
158 | label_user: Benutzer |
|
160 | label_user: Benutzer | |
159 | label_user_plural: Benutzer |
|
161 | label_user_plural: Benutzer | |
@@ -322,6 +324,10 label_search: Suche | |||||
322 | label_result: %d Resultat |
|
324 | label_result: %d Resultat | |
323 | label_result_plural: %d Resultate |
|
325 | label_result_plural: %d Resultate | |
324 | label_all_words: Alle Wörter |
|
326 | label_all_words: Alle Wörter | |
|
327 | label_wiki: Wiki | |||
|
328 | label_page_index: Index | |||
|
329 | label_current_version: Gegenwärtige Version | |||
|
330 | label_preview: Vorbetrachtung | |||
325 |
|
331 | |||
326 | button_login: Einloggen |
|
332 | button_login: Einloggen | |
327 | button_submit: Einreichen |
|
333 | button_submit: Einreichen |
@@ -142,6 +142,7 field_auth_source: Authentication mode | |||||
142 | field_hide_mail: Hide my email address |
|
142 | field_hide_mail: Hide my email address | |
143 | field_comment: Comment |
|
143 | field_comment: Comment | |
144 | field_url: URL |
|
144 | field_url: URL | |
|
145 | field_start_page: Start page | |||
145 |
|
146 | |||
146 | setting_app_title: Application title |
|
147 | setting_app_title: Application title | |
147 | setting_app_subtitle: Application subtitle |
|
148 | setting_app_subtitle: Application subtitle | |
@@ -154,6 +155,7 setting_issues_export_limit: Issues export limit | |||||
154 | setting_mail_from: Emission mail address |
|
155 | setting_mail_from: Emission mail address | |
155 | setting_host_name: Host name |
|
156 | setting_host_name: Host name | |
156 | setting_text_formatting: Text formatting |
|
157 | setting_text_formatting: Text formatting | |
|
158 | setting_wiki_compression: Wiki history compression | |||
157 |
|
159 | |||
158 | label_user: User |
|
160 | label_user: User | |
159 | label_user_plural: Users |
|
161 | label_user_plural: Users | |
@@ -322,6 +324,10 label_search: Search | |||||
322 | label_result: %d result |
|
324 | label_result: %d result | |
323 | label_result_plural: %d results |
|
325 | label_result_plural: %d results | |
324 | label_all_words: All words |
|
326 | label_all_words: All words | |
|
327 | label_wiki: Wiki | |||
|
328 | label_page_index: Index | |||
|
329 | label_current_version: Current version | |||
|
330 | label_preview: Preview | |||
325 |
|
331 | |||
326 | button_login: Login |
|
332 | button_login: Login | |
327 | button_submit: Submit |
|
333 | button_submit: Submit |
@@ -142,6 +142,7 field_auth_source: Modo de la autentificación | |||||
142 | field_hide_mail: Ocultar mi email address |
|
142 | field_hide_mail: Ocultar mi email address | |
143 | field_comment: Comentario |
|
143 | field_comment: Comentario | |
144 | field_url: URL |
|
144 | field_url: URL | |
|
145 | field_start_page: Página principal | |||
145 |
|
146 | |||
146 | setting_app_title: Título del aplicación |
|
147 | setting_app_title: Título del aplicación | |
147 | setting_app_subtitle: Subtítulo del aplicación |
|
148 | setting_app_subtitle: Subtítulo del aplicación | |
@@ -154,6 +155,7 setting_issues_export_limit: Issues export limit | |||||
154 | setting_mail_from: Email de la emisión |
|
155 | setting_mail_from: Email de la emisión | |
155 | setting_host_name: Nombre de anfitrión |
|
156 | setting_host_name: Nombre de anfitrión | |
156 | setting_text_formatting: Formato de texto |
|
157 | setting_text_formatting: Formato de texto | |
|
158 | setting_wiki_compression: Compresión de la historia de Wiki | |||
157 |
|
159 | |||
158 | label_user: Usuario |
|
160 | label_user: Usuario | |
159 | label_user_plural: Usuarios |
|
161 | label_user_plural: Usuarios | |
@@ -322,6 +324,10 label_search: Búsqueda | |||||
322 | label_result: %d resultado |
|
324 | label_result: %d resultado | |
323 | label_result_plural: %d resultados |
|
325 | label_result_plural: %d resultados | |
324 | label_all_words: Todas las palabras |
|
326 | label_all_words: Todas las palabras | |
|
327 | label_wiki: Wiki | |||
|
328 | label_page_index: Índice | |||
|
329 | label_current_version: Versión actual | |||
|
330 | label_preview: Previo | |||
325 |
|
331 | |||
326 | button_login: Conexión |
|
332 | button_login: Conexión | |
327 | button_submit: Someter |
|
333 | button_submit: Someter |
@@ -142,6 +142,7 field_auth_source: Mode d'authentification | |||||
142 | field_hide_mail: Cacher mon adresse mail |
|
142 | field_hide_mail: Cacher mon adresse mail | |
143 | field_comment: Commentaire |
|
143 | field_comment: Commentaire | |
144 | field_url: URL |
|
144 | field_url: URL | |
|
145 | field_start_page: Page de démarrage | |||
145 |
|
146 | |||
146 | setting_app_title: Titre de l'application |
|
147 | setting_app_title: Titre de l'application | |
147 | setting_app_subtitle: Sous-titre de l'application |
|
148 | setting_app_subtitle: Sous-titre de l'application | |
@@ -154,6 +155,7 setting_issues_export_limit: Limite export demandes | |||||
154 | setting_mail_from: Adresse d'émission |
|
155 | setting_mail_from: Adresse d'émission | |
155 | setting_host_name: Nom d'hôte |
|
156 | setting_host_name: Nom d'hôte | |
156 | setting_text_formatting: Formatage du texte |
|
157 | setting_text_formatting: Formatage du texte | |
|
158 | setting_wiki_compression: Compression historique wiki | |||
157 |
|
159 | |||
158 | label_user: Utilisateur |
|
160 | label_user: Utilisateur | |
159 | label_user_plural: Utilisateurs |
|
161 | label_user_plural: Utilisateurs | |
@@ -322,6 +324,10 label_search: Recherche | |||||
322 | label_result: %d résultat |
|
324 | label_result: %d résultat | |
323 | label_result_plural: %d résultats |
|
325 | label_result_plural: %d résultats | |
324 | label_all_words: Tous les mots |
|
326 | label_all_words: Tous les mots | |
|
327 | label_wiki: Wiki | |||
|
328 | label_page_index: Index | |||
|
329 | label_current_version: Version actuelle | |||
|
330 | label_preview: Prévisualisation | |||
325 |
|
331 | |||
326 | button_login: Connexion |
|
332 | button_login: Connexion | |
327 | button_submit: Soumettre |
|
333 | button_submit: Soumettre |
@@ -143,6 +143,7 field_auth_source: 認証モード | |||||
143 | field_hide_mail: Emailアドレスを隠す |
|
143 | field_hide_mail: Emailアドレスを隠す | |
144 | field_comment: コメント |
|
144 | field_comment: コメント | |
145 | field_url: URL |
|
145 | field_url: URL | |
|
146 | field_start_page: メインページ | |||
146 |
|
147 | |||
147 | setting_app_title: アプリケーションのタイトル |
|
148 | setting_app_title: アプリケーションのタイトル | |
148 | setting_app_subtitle: アプリケーションのサブタイトル |
|
149 | setting_app_subtitle: アプリケーションのサブタイトル | |
@@ -155,6 +156,7 setting_issues_export_limit: 出力する問題数の上限 | |||||
155 | setting_mail_from: Emission メールアドレス |
|
156 | setting_mail_from: Emission メールアドレス | |
156 | setting_host_name: ホスト名 |
|
157 | setting_host_name: ホスト名 | |
157 | setting_text_formatting: テキストの書式 |
|
158 | setting_text_formatting: テキストの書式 | |
|
159 | setting_wiki_compression: Wiki history compression | |||
158 |
|
160 | |||
159 | label_user: ユーザ |
|
161 | label_user: ユーザ | |
160 | label_user_plural: ユーザ |
|
162 | label_user_plural: ユーザ | |
@@ -323,6 +325,10 label_search: 検索 | |||||
323 | label_result: %d 件の結果 |
|
325 | label_result: %d 件の結果 | |
324 | label_result_plural: %d 件の結果 |
|
326 | label_result_plural: %d 件の結果 | |
325 | label_all_words: すべての単語 |
|
327 | label_all_words: すべての単語 | |
|
328 | label_wiki: Wiki | |||
|
329 | label_page_index: 索引 | |||
|
330 | label_current_version: 最近版 | |||
|
331 | label_preview: 下検分 | |||
326 |
|
332 | |||
327 | button_login: ログイン |
|
333 | button_login: ログイン | |
328 | button_submit: 変更 |
|
334 | button_submit: 変更 |
@@ -139,6 +139,8 vertical-align: middle; | |||||
139 | .icon-cancel { background-image: url(../images/cancel.png); } |
|
139 | .icon-cancel { background-image: url(../images/cancel.png); } | |
140 | .icon-pdf { background-image: url(../images/pdf.png); } |
|
140 | .icon-pdf { background-image: url(../images/pdf.png); } | |
141 | .icon-csv { background-image: url(../images/csv.png); } |
|
141 | .icon-csv { background-image: url(../images/csv.png); } | |
|
142 | .icon-html { background-image: url(../images/html.png); } | |||
|
143 | .icon-txt { background-image: url(../images/txt.png); } | |||
142 | .icon-file { background-image: url(../images/file.png); } |
|
144 | .icon-file { background-image: url(../images/file.png); } | |
143 | .icon-folder { background-image: url(../images/folder.png); } |
|
145 | .icon-folder { background-image: url(../images/folder.png); } | |
144 | .icon-package { background-image: url(../images/package.png); } |
|
146 | .icon-package { background-image: url(../images/package.png); } | |
@@ -150,6 +152,8 vertical-align: middle; | |||||
150 | .icon-logout { background-image: url(../images/logout.png); } |
|
152 | .icon-logout { background-image: url(../images/logout.png); } | |
151 | .icon-help { background-image: url(../images/help.png); } |
|
153 | .icon-help { background-image: url(../images/help.png); } | |
152 | .icon-attachment { background-image: url(../images/attachment.png); } |
|
154 | .icon-attachment { background-image: url(../images/attachment.png); } | |
|
155 | .icon-index { background-image: url(../images/index.png); } | |||
|
156 | .icon-history { background-image: url(../images/history.png); } | |||
153 |
|
157 | |||
154 | .icon22-projects { background-image: url(../images/22x22/projects.png); } |
|
158 | .icon22-projects { background-image: url(../images/22x22/projects.png); } | |
155 | .icon22-users { background-image: url(../images/22x22/users.png); } |
|
159 | .icon22-users { background-image: url(../images/22x22/users.png); } | |
@@ -181,7 +185,7 border-left: 1px dashed #c0c0c0; | |||||
181 |
|
185 | |||
182 | } |
|
186 | } | |
183 |
|
187 | |||
184 | #content h2{ |
|
188 | #content h2, #content div.wiki h1 { | |
185 | display:block; |
|
189 | display:block; | |
186 | margin:0 0 16px 0; |
|
190 | margin:0 0 16px 0; | |
187 | font-size:1.7em; |
|
191 | font-size:1.7em; | |
@@ -576,4 +580,23 to account for 3 pixel bug: http://www.positioniseverything.net/explorer/threepx | |||||
576 |
|
580 | |||
577 | * html .threepxfix{ |
|
581 | * html .threepxfix{ | |
578 | margin-left: 3px; |
|
582 | margin-left: 3px; | |
579 | } No newline at end of file |
|
583 | } | |
|
584 | ||||
|
585 | /***** Wiki sections ****/ | |||
|
586 | #content div.wiki { font-size: 110%} | |||
|
587 | ||||
|
588 | #content div.wiki h2, div.wiki h3 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; color:#606060; } | |||
|
589 | #content div.wiki h2 { font-size: 1.4em;} | |||
|
590 | #content div.wiki h3 { font-size: 1.2em;} | |||
|
591 | ||||
|
592 | div.wiki table { | |||
|
593 | border: 1px solid #505050; | |||
|
594 | border-collapse: collapse; | |||
|
595 | } | |||
|
596 | ||||
|
597 | div.wiki table, div.wiki td { | |||
|
598 | border: 1px solid #bbb; | |||
|
599 | padding: 4px; | |||
|
600 | } | |||
|
601 | ||||
|
602 | #preview .preview { background: #fafbfc url(../images/draft.png); } |
@@ -449,6 +449,15 permissions_005: | |||||
449 | mail_option: false |
|
449 | mail_option: false | |
450 | sort: 151 |
|
450 | sort: 151 | |
451 | is_public: false |
|
451 | is_public: false | |
|
452 | permissions_061: | |||
|
453 | action: search | |||
|
454 | id: 62 | |||
|
455 | description: label_search | |||
|
456 | controller: projects | |||
|
457 | mail_enabled: false | |||
|
458 | mail_option: false | |||
|
459 | sort: 130 | |||
|
460 | is_public: true | |||
452 | permissions_050: |
|
461 | permissions_050: | |
453 | action: history |
|
462 | action: history | |
454 | id: 50 |
|
463 | id: 50 |
@@ -1,379 +1,163 | |||||
1 | --- |
|
1 | --- | |
2 |
permissions_roles_0 |
|
2 | permissions_roles_054: | |
3 | role_id: 3 |
|
|||
4 | permission_id: 34 |
|
|||
5 | permissions_roles_047: |
|
|||
6 | role_id: 1 |
|
|||
7 | permission_id: 15 |
|
|||
8 | permissions_roles_102: |
|
|||
9 | role_id: 2 |
|
|||
10 | permission_id: 4 |
|
|||
11 | permissions_roles_019: |
|
|||
12 | role_id: 3 |
|
3 | role_id: 3 | |
13 |
permission_id: |
|
4 | permission_id: 44 | |
14 |
permissions_roles_04 |
|
5 | permissions_roles_043: | |
15 | role_id: 2 |
|
|||
16 | permission_id: 24 |
|
|||
17 | permissions_roles_103: |
|
|||
18 | role_id: 2 |
|
|||
19 | permission_id: 27 |
|
|||
20 | permissions_roles_076: |
|
|||
21 | role_id: 2 |
|
6 | role_id: 2 | |
22 |
permission_id: |
|
7 | permission_id: 25 | |
23 |
permissions_roles_0 |
|
8 | permissions_roles_032: | |
24 | role_id: 1 |
|
9 | role_id: 1 | |
25 |
permission_id: |
|
10 | permission_id: 42 | |
26 | permissions_roles_104: |
|
|||
27 | role_id: 2 |
|
|||
28 | permission_id: 36 |
|
|||
29 | permissions_roles_077: |
|
|||
30 | role_id: 2 |
|
|||
31 | permission_id: 7 |
|
|||
32 | permissions_roles_105: |
|
|||
33 | role_id: 2 |
|
|||
34 | permission_id: 32 |
|
|||
35 | permissions_roles_078: |
|
|||
36 | role_id: 3 |
|
|||
37 | permission_id: 38 |
|
|||
38 | permissions_roles_106: |
|
|||
39 | role_id: 2 |
|
|||
40 | permission_id: 14 |
|
|||
41 | permissions_roles_020: |
|
|||
42 | role_id: 2 |
|
|||
43 | permission_id: 9 |
|
|||
44 | permissions_roles_079: |
|
|||
45 | role_id: 2 |
|
|||
46 | permission_id: 18 |
|
|||
47 | permissions_roles_107: |
|
|||
48 | role_id: 3 |
|
|||
49 | permission_id: 40 |
|
|||
50 | permissions_roles_021: |
|
11 | permissions_roles_021: | |
51 | role_id: 1 |
|
12 | role_id: 1 | |
52 |
permission_id: |
|
13 | permission_id: 22 | |
53 |
permissions_roles_10 |
|
14 | permissions_roles_010: | |
54 | role_id: 1 |
|
15 | role_id: 1 | |
55 | permission_id: 29 |
|
|||
56 | permissions_roles_050: |
|
|||
57 | role_id: 2 |
|
|||
58 | permission_id: 29 |
|
|||
59 | permissions_roles_022: |
|
|||
60 | role_id: 3 |
|
|||
61 | permission_id: 4 |
|
16 | permission_id: 4 | |
62 |
permissions_roles_ |
|
17 | permissions_roles_044: | |
63 | role_id: 3 |
|
18 | role_id: 3 | |
64 | permission_id: 22 |
|
19 | permission_id: 22 | |
65 |
permissions_roles_0 |
|
20 | permissions_roles_033: | |
66 | role_id: 3 |
|
|||
67 | permission_id: 37 |
|
|||
68 | permissions_roles_023: |
|
|||
69 | role_id: 1 |
|
|||
70 | permission_id: 23 |
|
|||
71 | permissions_roles_052: |
|
|||
72 | role_id: 2 |
|
|||
73 | permission_id: 33 |
|
|||
74 | permissions_roles_024: |
|
|||
75 | role_id: 1 |
|
|||
76 | permission_id: 1 |
|
|||
77 | permissions_roles_080: |
|
|||
78 | role_id: 2 |
|
|||
79 | permission_id: 13 |
|
|||
80 | permissions_roles_053: |
|
|||
81 | role_id: 2 |
|
|||
82 | permission_id: 1 |
|
|||
83 | permissions_roles_025: |
|
|||
84 | role_id: 2 |
|
|||
85 | permission_id: 10 |
|
|||
86 | permissions_roles_081: |
|
|||
87 | role_id: 3 |
|
|||
88 | permission_id: 20 |
|
|||
89 | permissions_roles_054: |
|
|||
90 | role_id: 2 |
|
21 | role_id: 2 | |
91 |
permission_id: |
|
22 | permission_id: 22 | |
92 |
permissions_roles_02 |
|
23 | permissions_roles_022: | |
93 | role_id: 1 |
|
|||
94 | permission_id: 36 |
|
|||
95 | permissions_roles_082: |
|
|||
96 | role_id: 1 |
|
|||
97 | permission_id: 39 |
|
|||
98 | permissions_roles_110: |
|
|||
99 | role_id: 3 |
|
|||
100 | permission_id: 6 |
|
|||
101 | permissions_roles_027: |
|
|||
102 | role_id: 3 |
|
|||
103 | permission_id: 31 |
|
|||
104 | permissions_roles_083: |
|
|||
105 | role_id: 1 |
|
|||
106 | permission_id: 33 |
|
|||
107 | permissions_roles_055: |
|
|||
108 | role_id: 1 |
|
24 | role_id: 1 | |
109 | permission_id: 38 |
|
25 | permission_id: 38 | |
110 |
permissions_roles_ |
|
26 | permissions_roles_011: | |
111 | role_id: 3 |
|
|||
112 | permission_id: 1 |
|
|||
113 | permissions_roles_028: |
|
|||
114 | role_id: 1 |
|
|||
115 | permission_id: 24 |
|
|||
116 | permissions_roles_084: |
|
|||
117 | role_id: 3 |
|
|||
118 | permission_id: 16 |
|
|||
119 | permissions_roles_056: |
|
|||
120 | role_id: 2 |
|
|||
121 | permission_id: 5 |
|
|||
122 | permissions_roles_029: |
|
|||
123 | role_id: 1 |
|
|||
124 | permission_id: 9 |
|
|||
125 | permissions_roles_085: |
|
|||
126 | role_id: 3 |
|
|||
127 | permission_id: 27 |
|
|||
128 | permissions_roles_057: |
|
|||
129 | role_id: 1 |
|
|||
130 | permission_id: 16 |
|
|||
131 | permissions_roles_112: |
|
|||
132 | role_id: 1 |
|
27 | role_id: 1 | |
133 | permission_id: 20 |
|
28 | permission_id: 20 | |
134 |
permissions_roles_0 |
|
29 | permissions_roles_045: | |
135 | role_id: 3 |
|
|||
136 | permission_id: 12 |
|
|||
137 | permissions_roles_058: |
|
|||
138 | role_id: 1 |
|
|||
139 | permission_id: 26 |
|
|||
140 | permissions_roles_113: |
|
|||
141 | role_id: 2 |
|
|||
142 | permission_id: 37 |
|
|||
143 | permissions_roles_087: |
|
|||
144 | role_id: 1 |
|
30 | role_id: 1 | |
145 |
permission_id: |
|
31 | permission_id: 12 | |
146 |
permissions_roles_0 |
|
32 | permissions_roles_034: | |
147 | role_id: 3 |
|
|||
148 | permission_id: 18 |
|
|||
149 | permissions_roles_114: |
|
|||
150 | role_id: 2 |
|
33 | role_id: 2 | |
151 |
permission_id: |
|
34 | permission_id: 44 | |
152 |
permissions_roles_ |
|
35 | permissions_roles_023: | |
153 | role_id: 2 |
|
36 | role_id: 2 | |
154 | permission_id: 15 |
|
37 | permission_id: 15 | |
155 |
permissions_roles_0 |
|
38 | permissions_roles_012: | |
156 |
role_id: |
|
39 | role_id: 1 | |
157 | permission_id: 3 |
|
40 | permission_id: 36 | |
158 | permissions_roles_001: |
|
41 | permissions_roles_001: | |
159 | role_id: 2 |
|
|||
160 | permission_id: 21 |
|
|||
161 | permissions_roles_116: |
|
|||
162 | role_id: 3 |
|
|||
163 | permission_id: 23 |
|
|||
164 | permissions_roles_030: |
|
|||
165 | role_id: 1 |
|
42 | role_id: 1 | |
166 |
permission_id: |
|
43 | permission_id: 14 | |
167 |
permissions_roles_0 |
|
44 | permissions_roles_046: | |
168 | role_id: 1 |
|
45 | role_id: 1 | |
169 | permission_id: 28 |
|
|||
170 | permissions_roles_002: |
|
|||
171 | role_id: 3 |
|
|||
172 | permission_id: 29 |
|
46 | permission_id: 29 | |
173 |
permissions_roles_ |
|
47 | permissions_roles_035: | |
174 | role_id: 3 |
|
|||
175 | permission_id: 28 |
|
|||
176 | permissions_roles_031: |
|
|||
177 | role_id: 2 |
|
|||
178 | permission_id: 38 |
|
|||
179 | permissions_roles_003: |
|
|||
180 | role_id: 3 |
|
|||
181 | permission_id: 41 |
|
|||
182 | permissions_roles_118: |
|
|||
183 | role_id: 1 |
|
48 | role_id: 1 | |
184 |
permission_id: |
|
49 | permission_id: 10 | |
185 |
permissions_roles_0 |
|
50 | permissions_roles_024: | |
186 | role_id: 3 |
|
|||
187 | permission_id: 9 |
|
|||
188 | permissions_roles_004: |
|
|||
189 | role_id: 2 |
|
51 | role_id: 2 | |
190 |
permission_id: |
|
52 | permission_id: 42 | |
191 |
permissions_roles_0 |
|
53 | permissions_roles_013: | |
192 | role_id: 2 |
|
54 | role_id: 2 | |
193 |
permission_id: |
|
55 | permission_id: 13 | |
194 |
permissions_roles_ |
|
56 | permissions_roles_002: | |
195 | role_id: 1 |
|
57 | role_id: 1 | |
196 |
permission_id: |
|
58 | permission_id: 34 | |
197 |
permissions_roles_0 |
|
59 | permissions_roles_047: | |
198 | role_id: 2 |
|
|||
199 | permission_id: 28 |
|
|||
200 | permissions_roles_005: |
|
|||
201 | role_id: 3 |
|
|||
202 | permission_id: 3 |
|
|||
203 | permissions_roles_061: |
|
|||
204 | role_id: 2 |
|
|||
205 | permission_id: 40 |
|
|||
206 | permissions_roles_006: |
|
|||
207 | role_id: 3 |
|
|||
208 | permission_id: 14 |
|
|||
209 | permissions_roles_090: |
|
|||
210 | role_id: 2 |
|
60 | role_id: 2 | |
211 |
permission_id: |
|
61 | permission_id: 4 | |
212 |
permissions_roles_06 |
|
62 | permissions_roles_036: | |
213 | role_id: 1 |
|
63 | role_id: 1 | |
214 |
permission_id: |
|
64 | permission_id: 25 | |
215 |
permissions_roles_0 |
|
65 | permissions_roles_025: | |
216 | role_id: 2 |
|
|||
217 | permission_id: 11 |
|
|||
218 | permissions_roles_007: |
|
|||
219 | role_id: 1 |
|
66 | role_id: 1 | |
220 |
permission_id: |
|
67 | permission_id: 8 | |
221 |
permissions_roles_0 |
|
68 | permissions_roles_014: | |
222 | role_id: 3 |
|
|||
223 | permission_id: 35 |
|
|||
224 | permissions_roles_063: |
|
|||
225 | role_id: 2 |
|
|||
226 | permission_id: 30 |
|
|||
227 | permissions_roles_035: |
|
|||
228 | role_id: 2 |
|
|||
229 | permission_id: 23 |
|
|||
230 | permissions_roles_008: |
|
|||
231 | role_id: 2 |
|
69 | role_id: 2 | |
232 |
permission_id: |
|
70 | permission_id: 38 | |
233 |
permissions_roles_0 |
|
71 | permissions_roles_003: | |
234 | role_id: 2 |
|
72 | role_id: 2 | |
235 |
permission_id: |
|
73 | permission_id: 11 | |
236 |
permissions_roles_0 |
|
74 | permissions_roles_048: | |
237 | role_id: 3 |
|
|||
238 | permission_id: 33 |
|
|||
239 | permissions_roles_036: |
|
|||
240 | role_id: 3 |
|
|||
241 | permission_id: 5 |
|
|||
242 | permissions_roles_120: |
|
|||
243 | role_id: 3 |
|
|||
244 | permission_id: 13 |
|
|||
245 | permissions_roles_009: |
|
|||
246 | role_id: 1 |
|
|||
247 | permission_id: 12 |
|
|||
248 | permissions_roles_093: |
|
|||
249 | role_id: 2 |
|
75 | role_id: 2 | |
250 |
permission_id: 4 |
|
76 | permission_id: 34 | |
251 | permissions_roles_065: |
|
|||
252 | role_id: 3 |
|
|||
253 | permission_id: 26 |
|
|||
254 | permissions_roles_037: |
|
77 | permissions_roles_037: | |
255 | role_id: 1 |
|
78 | role_id: 1 | |
256 |
permission_id: 4 |
|
79 | permission_id: 43 | |
257 |
permissions_roles_ |
|
80 | permissions_roles_026: | |
258 | role_id: 3 |
|
|||
259 | permission_id: 2 |
|
|||
260 | permissions_roles_094: |
|
|||
261 | role_id: 3 |
|
|||
262 | permission_id: 39 |
|
|||
263 | permissions_roles_066: |
|
|||
264 | role_id: 2 |
|
|||
265 | permission_id: 6 |
|
|||
266 | permissions_roles_038: |
|
|||
267 | role_id: 1 |
|
81 | role_id: 1 | |
268 |
permission_id: 2 |
|
82 | permission_id: 23 | |
269 |
permissions_roles_ |
|
83 | permissions_roles_015: | |
270 | role_id: 1 |
|
84 | role_id: 1 | |
271 |
permission_id: |
|
85 | permission_id: 5 | |
272 |
permissions_roles_0 |
|
86 | permissions_roles_004: | |
273 | role_id: 2 |
|
87 | role_id: 2 | |
274 | permission_id: 19 |
|
|||
275 | permissions_roles_067: |
|
|||
276 | role_id: 1 |
|
|||
277 | permission_id: 17 |
|
|||
278 | permissions_roles_039: |
|
|||
279 | role_id: 3 |
|
|||
280 | permission_id: 36 |
|
88 | permission_id: 36 | |
281 |
permissions_roles_ |
|
89 | permissions_roles_049: | |
282 | role_id: 3 |
|
90 | role_id: 3 | |
283 | permission_id: 24 |
|
91 | permission_id: 24 | |
284 |
permissions_roles_0 |
|
92 | permissions_roles_038: | |
285 |
role_id: |
|
93 | role_id: 2 | |
286 |
permission_id: |
|
94 | permission_id: 24 | |
287 |
permissions_roles_0 |
|
95 | permissions_roles_027: | |
288 | role_id: 1 |
|
|||
289 | permission_id: 32 |
|
|||
290 | permissions_roles_124: |
|
|||
291 | role_id: 1 |
|
96 | role_id: 1 | |
292 |
permission_id: |
|
97 | permission_id: 41 | |
293 |
permissions_roles_01 |
|
98 | permissions_roles_016: | |
294 | role_id: 1 |
|
99 | role_id: 1 | |
295 |
permission_id: |
|
100 | permission_id: 21 | |
296 |
permissions_roles_0 |
|
101 | permissions_roles_005: | |
297 | role_id: 3 |
|
|||
298 | permission_id: 19 |
|
|||
299 | permissions_roles_097: |
|
|||
300 | role_id: 2 |
|
|||
301 | permission_id: 35 |
|
|||
302 | permissions_roles_125: |
|
|||
303 | role_id: 2 |
|
|||
304 | permission_id: 16 |
|
|||
305 | permissions_roles_011: |
|
|||
306 | role_id: 3 |
|
|||
307 | permission_id: 42 |
|
|||
308 | permissions_roles_098: |
|
|||
309 | role_id: 1 |
|
102 | role_id: 1 | |
310 |
permission_id: |
|
103 | permission_id: 53 | |
311 |
permissions_roles_ |
|
104 | permissions_roles_050: | |
312 | role_id: 3 |
|
|||
313 | permission_id: 7 |
|
|||
314 | permissions_roles_012: |
|
|||
315 | role_id: 3 |
|
|||
316 | permission_id: 8 |
|
|||
317 | permissions_roles_040: |
|
|||
318 | role_id: 1 |
|
105 | role_id: 1 | |
319 |
permission_id: |
|
106 | permission_id: 13 | |
320 |
permissions_roles_0 |
|
107 | permissions_roles_039: | |
321 | role_id: 3 |
|
108 | role_id: 3 | |
322 |
permission_id: |
|
109 | permission_id: 20 | |
323 |
permissions_roles_0 |
|
110 | permissions_roles_028: | |
324 | role_id: 2 |
|
111 | role_id: 2 | |
325 |
permission_id: |
|
112 | permission_id: 20 | |
326 |
permissions_roles_01 |
|
113 | permissions_roles_017: | |
327 | role_id: 1 |
|
|||
328 | permission_id: 40 |
|
|||
329 | permissions_roles_070: |
|
|||
330 | role_id: 3 |
|
|||
331 | permission_id: 11 |
|
|||
332 | permissions_roles_042: |
|
|||
333 | role_id: 1 |
|
114 | role_id: 1 | |
334 | permission_id: 37 |
|
115 | permission_id: 37 | |
335 |
permissions_roles_0 |
|
116 | permissions_roles_006: | |
336 | role_id: 1 |
|
|||
337 | permission_id: 22 |
|
|||
338 | permissions_roles_071: |
|
|||
339 | role_id: 1 |
|
117 | role_id: 1 | |
340 |
permission_id: |
|
118 | permission_id: 15 | |
341 |
permissions_roles_0 |
|
119 | permissions_roles_051: | |
342 | role_id: 3 |
|
|||
343 | permission_id: 32 |
|
|||
344 | permissions_roles_015: |
|
|||
345 | role_id: 2 |
|
|||
346 | permission_id: 22 |
|
|||
347 | permissions_roles_072: |
|
|||
348 | role_id: 1 |
|
120 | role_id: 1 | |
349 |
permission_id: |
|
121 | permission_id: 30 | |
350 |
permissions_roles_04 |
|
122 | permissions_roles_040: | |
351 | role_id: 1 |
|
123 | role_id: 1 | |
|
124 | permission_id: 11 | |||
|
125 | permissions_roles_029: | |||
|
126 | role_id: 2 | |||
|
127 | permission_id: 43 | |||
|
128 | permissions_roles_018: | |||
|
129 | role_id: 2 | |||
352 | permission_id: 14 |
|
130 | permission_id: 14 | |
353 |
permissions_roles_0 |
|
131 | permissions_roles_007: | |
354 |
role_id: |
|
132 | role_id: 1 | |
355 |
permission_id: |
|
133 | permission_id: 35 | |
356 |
permissions_roles_0 |
|
134 | permissions_roles_052: | |
357 | role_id: 2 |
|
135 | role_id: 2 | |
358 | permission_id: 34 |
|
|||
359 | permissions_roles_045: |
|
|||
360 | role_id: 3 |
|
|||
361 | permission_id: 10 |
|
136 | permission_id: 10 | |
362 |
permissions_roles_ |
|
137 | permissions_roles_041: | |
363 | role_id: 1 |
|
138 | role_id: 1 | |
364 |
permission_id: |
|
139 | permission_id: 28 | |
365 |
permissions_roles_0 |
|
140 | permissions_roles_030: | |
366 |
role_id: |
|
141 | role_id: 1 | |
367 |
permission_id: |
|
142 | permission_id: 9 | |
368 |
permissions_roles_0 |
|
143 | permissions_roles_019: | |
369 | role_id: 2 |
|
144 | role_id: 2 | |
370 |
permission_id: |
|
145 | permission_id: 41 | |
371 |
permissions_roles_0 |
|
146 | permissions_roles_008: | |
|
147 | role_id: 2 | |||
|
148 | permission_id: 12 | |||
|
149 | permissions_roles_053: | |||
|
150 | role_id: 2 | |||
|
151 | permission_id: 35 | |||
|
152 | permissions_roles_042: | |||
372 | role_id: 1 |
|
153 | role_id: 1 | |
373 |
permission_id: |
|
154 | permission_id: 44 | |
374 |
permissions_roles_ |
|
155 | permissions_roles_031: | |
375 | role_id: 3 |
|
|||
376 | permission_id: 21 |
|
|||
377 | permissions_roles_018: |
|
|||
378 | role_id: 1 |
|
156 | role_id: 1 | |
379 |
permission_id: 4 |
|
157 | permission_id: 24 | |
|
158 | permissions_roles_020: | |||
|
159 | role_id: 1 | |||
|
160 | permission_id: 7 | |||
|
161 | permissions_roles_009: | |||
|
162 | role_id: 2 | |||
|
163 | permission_id: 37 |
@@ -24,7 +24,7 class MailerTest < Test::Unit::TestCase | |||||
24 | def test_issue_add |
|
24 | def test_issue_add | |
25 | issue = Issue.find(1) |
|
25 | issue = Issue.find(1) | |
26 | GLoc.valid_languages.each do |lang| |
|
26 | GLoc.valid_languages.each do |lang| | |
27 | Setting.default_language = lang |
|
27 | Setting.default_language = lang.to_s | |
28 | assert Mailer.deliver_issue_add(issue) |
|
28 | assert Mailer.deliver_issue_add(issue) | |
29 | end |
|
29 | end | |
30 | end |
|
30 | end | |
@@ -32,7 +32,7 class MailerTest < Test::Unit::TestCase | |||||
32 | def test_issue_edit |
|
32 | def test_issue_edit | |
33 | journal = Journal.find(1) |
|
33 | journal = Journal.find(1) | |
34 | GLoc.valid_languages.each do |lang| |
|
34 | GLoc.valid_languages.each do |lang| | |
35 | Setting.default_language = lang |
|
35 | Setting.default_language = lang.to_s | |
36 | assert Mailer.deliver_issue_edit(journal) |
|
36 | assert Mailer.deliver_issue_edit(journal) | |
37 | end |
|
37 | end | |
38 | end |
|
38 | end | |
@@ -40,7 +40,7 class MailerTest < Test::Unit::TestCase | |||||
40 | def test_document_add |
|
40 | def test_document_add | |
41 | document = Document.find(1) |
|
41 | document = Document.find(1) | |
42 | GLoc.valid_languages.each do |lang| |
|
42 | GLoc.valid_languages.each do |lang| | |
43 | Setting.default_language = lang |
|
43 | Setting.default_language = lang.to_s | |
44 | assert Mailer.deliver_document_add(document) |
|
44 | assert Mailer.deliver_document_add(document) | |
45 | end |
|
45 | end | |
46 | end |
|
46 | end | |
@@ -48,7 +48,7 class MailerTest < Test::Unit::TestCase | |||||
48 | def test_lost_password |
|
48 | def test_lost_password | |
49 | token = Token.find(2) |
|
49 | token = Token.find(2) | |
50 | GLoc.valid_languages.each do |lang| |
|
50 | GLoc.valid_languages.each do |lang| | |
51 | token.user.update_attribute :language, lang |
|
51 | token.user.update_attribute :language, lang.to_s | |
52 | assert Mailer.deliver_lost_password(token) |
|
52 | assert Mailer.deliver_lost_password(token) | |
53 | end |
|
53 | end | |
54 | end |
|
54 | end | |
@@ -56,7 +56,7 class MailerTest < Test::Unit::TestCase | |||||
56 | def test_register |
|
56 | def test_register | |
57 | token = Token.find(1) |
|
57 | token = Token.find(1) | |
58 | GLoc.valid_languages.each do |lang| |
|
58 | GLoc.valid_languages.each do |lang| | |
59 | token.user.update_attribute :language, lang |
|
59 | token.user.update_attribute :language, lang.to_s | |
60 | assert Mailer.deliver_register(token) |
|
60 | assert Mailer.deliver_register(token) | |
61 | end |
|
61 | end | |
62 | end |
|
62 | end |
General Comments 0
You need to be logged in to leave comments.
Login now