backlog 0.28.0 → 0.29.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +19 -0
- data/README.txt +8 -2
- data/Rakefile +3 -2
- data/Screenshot.png +0 -0
- data/app/controllers/groups_controller.rb +1 -2
- data/app/controllers/work_accounts_controller.rb +5 -2
- data/app/controllers/works_controller.rb +2 -1
- data/app/models/works_report_filter.rb +22 -0
- data/app/views/search/results.rhtml +1 -1
- data/app/views/work_accounts/_title.rhtml +1 -0
- data/app/views/work_accounts/list.rhtml +6 -7
- data/app/views/works/list.rhtml +36 -7
- data/lang/en.yaml +1 -0
- data/lang/no.yaml +1 -0
- data/test/performance/common.rb +1 -1
- data/test/performance/server_test.rb +5 -5
- data/vendor/plugins/has_history/README +12 -0
- data/vendor/plugins/has_history/Rakefile +22 -0
- data/vendor/plugins/has_history/generators/history_migration/history_migration_generator.rb +30 -0
- data/vendor/plugins/has_history/generators/history_migration/templates/migration.rb +14 -0
- data/vendor/plugins/has_history/generators/history_model/history_model_generator.rb +39 -0
- data/vendor/plugins/has_history/generators/history_model/templates/fixtures.yml +17 -0
- data/vendor/plugins/has_history/generators/history_model/templates/functional_test.rb +129 -0
- data/vendor/plugins/has_history/generators/history_model/templates/migration.rb +14 -0
- data/vendor/plugins/has_history/generators/history_model/templates/model.rb +4 -0
- data/vendor/plugins/has_history/generators/history_model/templates/unit_test.rb +75 -0
- data/vendor/plugins/has_history/init.rb +5 -0
- data/vendor/plugins/has_history/install.rb +1 -0
- data/vendor/plugins/has_history/lib/has_history.rb +178 -0
- data/vendor/plugins/has_history/tasks/has_history_tasks.rake +4 -0
- data/vendor/plugins/has_history/test/has_history_test.rb +31 -0
- data/vendor/plugins/has_history/test/ignorant_master.rb +3 -0
- data/vendor/plugins/has_history/test/ignorant_master_history.rb +4 -0
- data/vendor/plugins/has_history/test/lone_master.rb +42 -0
- data/vendor/plugins/has_history/test/paired_master.rb +5 -0
- data/vendor/plugins/has_history/test/paired_master_history.rb +5 -0
- data/vendor/plugins/has_history/test/test_helper.rb +154 -0
- data/vendor/plugins/has_history/uninstall.rb +1 -0
- metadata +36 -12
data/History.txt
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
== 0.29.0 2008-04-07
|
2
|
+
|
3
|
+
=== Features
|
4
|
+
|
5
|
+
* Tasks are now automatically grabbed when they are created.
|
6
|
+
* Added 8 hours of work for holidays and sick days in the weekly work sheet.
|
7
|
+
* Removed gem dependency to rmagick for easier installation.
|
8
|
+
* Improved work record search screen.
|
9
|
+
|
10
|
+
=== Fixes
|
11
|
+
|
12
|
+
* Fixed bug where validation errors for new record in the Daily Work Sheet redirected to the
|
13
|
+
"New Work" view instead of displaying the Daily Work Sheet with appropriate error message.
|
14
|
+
* Fixed bug where updates for rows without start time failed.
|
15
|
+
* Now treat times with only whitespace as empty.
|
16
|
+
* Added a standard link text to hits for empty work records in the search results view.
|
17
|
+
* Select empty work account on new rows in the Daily Work Sheet instead of the first work account.
|
18
|
+
* Sort memebers in a group by first name then last name.
|
19
|
+
|
1
20
|
== 0.28.0 2008-04-01
|
2
21
|
|
3
22
|
=== Features
|
data/README.txt
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
<img align="right" src="Screenshot.png"/>
|
2
|
+
|
1
3
|
== Backlog
|
2
4
|
|
3
5
|
Welcome to Backlog!
|
@@ -35,11 +37,15 @@ If you have concrete needs, and are willing to beta test the integration, please
|
|
35
37
|
* Install ruby
|
36
38
|
* Install RubyGems
|
37
39
|
* Install PostgreSQL (or other database management system)
|
38
|
-
*
|
39
|
-
* run <tt>sudo gem install backlog -y</tt>
|
40
|
+
* run <tt>sudo gem install backlog</tt>
|
40
41
|
* run <tt>sudo backlog setup_unix</tt>
|
41
42
|
* run <tt>sudo backlog start</tt>
|
42
43
|
|
44
|
+
===== Charts
|
45
|
+
|
46
|
+
* Install ImageMagick
|
47
|
+
* run <tt>sudo gem install rmagick</tt>
|
48
|
+
|
43
49
|
===== Updates
|
44
50
|
|
45
51
|
* run <tt>sudo gem update -y</tt>
|
data/Rakefile
CHANGED
@@ -32,8 +32,9 @@ Hoe.new("backlog", APP::VERSION) do |p|
|
|
32
32
|
}
|
33
33
|
p.need_zip = true
|
34
34
|
p.url = 'http://rubyforge.org/projects/backlog/'
|
35
|
-
p.extra_deps = [['rails', '= 1.2.4'], ['gruff', '~> 0.2.9'], ['
|
36
|
-
|
35
|
+
p.extra_deps = [['rails', '= 1.2.4'], ['gruff', '~> 0.2.9'], ['postgres', '~> 0.7.9'],
|
36
|
+
['slave', '~> 1.2.1']]
|
37
|
+
# ['rmagick', '~> 1.15.12'],
|
37
38
|
p.rsync_args = "-acv --delete --exclude=wiki*"
|
38
39
|
end
|
39
40
|
|
data/Screenshot.png
ADDED
Binary file
|
@@ -30,8 +30,7 @@ class GroupsController < ApplicationController
|
|
30
30
|
|
31
31
|
def edit
|
32
32
|
@group = Group.find(params[:id])
|
33
|
-
@users = User.find(:all)
|
34
|
-
@members = @group.users.to_s
|
33
|
+
@users = User.find(:all, :order => 'first_name, last_name')
|
35
34
|
@groups = Group.find(:all, :order => 'name')
|
36
35
|
end
|
37
36
|
|
@@ -57,9 +57,12 @@ class WorkAccountsController < ApplicationController
|
|
57
57
|
|
58
58
|
def works
|
59
59
|
work_account = WorkAccount.find(params[:id])
|
60
|
-
@report_filter =
|
60
|
+
@report_filter = WorksReportFilter.new(params[:report_filter])
|
61
61
|
@report_filter.title = "#{l :hours} for #{work_account.name} #{@report_filter.start_on && @report_filter.start_on.strftime('%Y-%m-%d - ')}#{@report_filter.end_on && @report_filter.end_on.strftime('%Y-%m-%d')}"
|
62
|
-
@works = Work.paginate :conditions => ["completed_at BETWEEN ? AND ? AND work_account_id = ?", @report_filter.start_on, @report_filter.end_on + 1, work_account.id
|
62
|
+
@works = Work.paginate :conditions => ["completed_at BETWEEN ? AND ? AND work_account_id = ? #{@report_filter.invoice.nil? ? '' : 'AND invoice = ?'} #{@report_filter.user_id.nil? ? '' : 'AND user_id = ?'}", @report_filter.start_on, @report_filter.end_on + 1, work_account.id, @report_filter.invoice, @report_filter.user_id].compact,
|
63
|
+
:page => params[:page], :per_page => @report_filter.page_size,
|
64
|
+
:order => 'started_on, start_time, completed_at'
|
65
|
+
@users = User.find(:all)
|
63
66
|
if params[:export] == 'excel'
|
64
67
|
render :template => '/works/list_excel', :layout => false
|
65
68
|
else
|
@@ -57,11 +57,12 @@ class WorksController < ApplicationController
|
|
57
57
|
@work.calculate_hours! unless @work.hours && @work.hours.to_f > 0
|
58
58
|
@work.user_id = current_user.id
|
59
59
|
end
|
60
|
-
if @work &&
|
60
|
+
if @work && @work.save
|
61
61
|
flash[:notice] = 'Work was successfully created.'
|
62
62
|
@work.task.grab if @work.task
|
63
63
|
else
|
64
64
|
if params[:detour]
|
65
|
+
flash[:notice] = 'Error creating new work record.'
|
65
66
|
back
|
66
67
|
else
|
67
68
|
new
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class WorksReportFilter < ReportFilter
|
2
|
+
attr_reader :invoice
|
3
|
+
attr_reader :user_id
|
4
|
+
|
5
|
+
def initialize(attributes)
|
6
|
+
@invoice = nil
|
7
|
+
@user_id = nil
|
8
|
+
|
9
|
+
if attributes
|
10
|
+
attributes = attributes.clone
|
11
|
+
|
12
|
+
invoice_param = attributes.delete(:invoice)
|
13
|
+
@invoice = invoice_param == 'true' if invoice_param && invoice_param.size > 0
|
14
|
+
|
15
|
+
user_id_param = attributes.delete(:user_id)
|
16
|
+
@user_id = user_id_param.to_i if user_id_param && user_id_param.size > 0
|
17
|
+
|
18
|
+
end
|
19
|
+
super(attributes)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -17,7 +17,7 @@
|
|
17
17
|
</div>
|
18
18
|
<ul>
|
19
19
|
<% @work_accounts.each do |work_account| %>
|
20
|
-
<li><%=link_to
|
20
|
+
<li><%=link_to work_account.name, :controller => 'work_accounts', :action => :edit, :id => work_account%></li>
|
21
21
|
<% end %>
|
22
22
|
</ul>
|
23
23
|
</div>
|
@@ -1,4 +1,5 @@
|
|
1
1
|
<div class="btitle">
|
2
2
|
<%=image_detour_to('hammer.png', l(:work), :action => :works, :id => @work_account) %>
|
3
|
+
<%=image_detour_to('clipboard.png', l(:edit), :action => :edit, :id => @work_account) unless controller.action_name == 'edit' %>
|
3
4
|
<h4><%=@work_account.name %></h4>
|
4
5
|
</div>
|
@@ -8,17 +8,16 @@
|
|
8
8
|
|
9
9
|
<table>
|
10
10
|
<tr>
|
11
|
-
|
12
|
-
<th><%=
|
13
|
-
|
11
|
+
<th><%=l :name %></th>
|
12
|
+
<th><%=l :track_times %></th>
|
13
|
+
<th><%=l :invoice_code %></th>
|
14
14
|
</tr>
|
15
15
|
|
16
16
|
<% for work_account in @work_accounts %>
|
17
17
|
<tr>
|
18
|
-
|
19
|
-
<td><%=
|
20
|
-
|
21
|
-
<td><%= link_to 'Show', :action => 'show', :id => work_account %></td>
|
18
|
+
<td><%=link_to h(work_account.name), :action => 'show', :id => work_account %></td>
|
19
|
+
<td align="right"><%=work_account.track_times? %></td>
|
20
|
+
<td><%=h(work_account.invoice_code) %></td>
|
22
21
|
<td><%= link_to 'Edit', :action => 'edit', :id => work_account %></td>
|
23
22
|
<td><%= link_to 'Destroy', { :action => 'destroy', :id => work_account }, :confirm => 'Are you sure?', :method => :post %></td>
|
24
23
|
</tr>
|
data/app/views/works/list.rhtml
CHANGED
@@ -6,7 +6,12 @@
|
|
6
6
|
|
7
7
|
<% if @report_filter %>
|
8
8
|
<% form_for :report_filter, :html => {:method => :get} do |f| %>
|
9
|
-
|
9
|
+
<table>
|
10
|
+
<tr>
|
11
|
+
<td>
|
12
|
+
<label for="report_filter_start_on"><%=l :start_on%></label>
|
13
|
+
</td>
|
14
|
+
<td>
|
10
15
|
<%=f.text_field 'start_on', :size => 16, :value => @report_filter.start_on ? @report_filter.start_on.strftime('%Y-%m-%d') : nil %>
|
11
16
|
<button id="trigger1">...</button>
|
12
17
|
<script type="text/javascript">//<![CDATA[
|
@@ -23,8 +28,11 @@
|
|
23
28
|
timeInterval : 15
|
24
29
|
});
|
25
30
|
//]]></script>
|
26
|
-
|
31
|
+
</td>
|
32
|
+
<td>
|
27
33
|
<label for="report_filter_end_on"><%=l :completed_at%></label>
|
34
|
+
</td>
|
35
|
+
<td>
|
28
36
|
<%=f.text_field 'end_on', :size => 16, :value => (@report_filter.end_on && @report_filter.end_on.strftime('%Y-%m-%d')) %>
|
29
37
|
<button id="trigger2">...</button>
|
30
38
|
<script type="text/javascript">//<![CDATA[
|
@@ -41,13 +49,32 @@
|
|
41
49
|
timeInterval : 15
|
42
50
|
});
|
43
51
|
//]]></script>
|
52
|
+
</td>
|
53
|
+
</tr>
|
44
54
|
|
45
|
-
<
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
55
|
+
<tr>
|
56
|
+
<td>
|
57
|
+
<label for="report_filter_user_id"><%=l :user%></label>
|
58
|
+
</td>
|
59
|
+
<td>
|
60
|
+
<%=f.select :user_id, [[l(:all), nil]] + @users.map{|user|[user.name, user.id]}, {}, {:onchange => 'form.submit()'} %>
|
61
|
+
</td>
|
62
|
+
<td>
|
63
|
+
<label for="report_filter_invoice"><%=l :invoice%></label>
|
64
|
+
</td>
|
65
|
+
<td>
|
66
|
+
<%=f.select :invoice, [[l(:all), nil], [l(:yes), true], [l(:no), false]], {}, {:onchange => 'form.submit()'} %>
|
50
67
|
|
68
|
+
<label for="report_filter_page_size"><%=l :paging%></label>
|
69
|
+
<%=f.check_box :page_size, {:checked => @report_filter.page_size == 10, :onchange => 'form.submit()'}, 10, nil %>
|
70
|
+
</td>
|
71
|
+
</tr>
|
72
|
+
<tr>
|
73
|
+
<td colspan="4" align="right">
|
74
|
+
<%=submit_tag l(:search) %>
|
75
|
+
</td>
|
76
|
+
</tr>
|
77
|
+
</table>
|
51
78
|
<% end %>
|
52
79
|
<% end %>
|
53
80
|
|
@@ -67,6 +94,7 @@
|
|
67
94
|
<th><%=l :task %></th>
|
68
95
|
<th><%=l :user %></th>
|
69
96
|
<th><%=l :done %></th>
|
97
|
+
<th><%=l :invoice %></th>
|
70
98
|
<% if @period && @period.track_times? %>
|
71
99
|
<th><%=l :started_at %></th>
|
72
100
|
<% end %>
|
@@ -78,6 +106,7 @@
|
|
78
106
|
<td><%=work.task.description if work.task %></td>
|
79
107
|
<td><%=work.user && work.user.login %></td>
|
80
108
|
<td><%=work.hours %></td>
|
109
|
+
<td><%=work.invoice ? l(:yes) : '' %></td>
|
81
110
|
<% if @period && @period.track_times? %>
|
82
111
|
<td><%=work.started_at && work.started_at.strftime('%Y-%m-%d %H:%M:%S') %></td>
|
83
112
|
<% end %>
|
data/lang/en.yaml
CHANGED
data/lang/no.yaml
CHANGED
data/test/performance/common.rb
CHANGED
@@ -3,15 +3,15 @@ require File.dirname(__FILE__) + '/common'
|
|
3
3
|
|
4
4
|
class ServerTest < Test::Unit::TestCase
|
5
5
|
include Backlog::Performance::Tests
|
6
|
-
|
7
|
-
PORT = 4000
|
8
|
-
|
6
|
+
|
9
7
|
def setup
|
10
|
-
|
8
|
+
@port = 3000
|
9
|
+
@factor = 1.0
|
10
|
+
start_server
|
11
11
|
end
|
12
12
|
|
13
13
|
def teardown
|
14
14
|
stop_server
|
15
15
|
end
|
16
16
|
|
17
|
-
end
|
17
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
HasHistory
|
2
|
+
==========
|
3
|
+
|
4
|
+
This plugin adds recording of history for a given model. The design tries to follow the analysis given in
|
5
|
+
|
6
|
+
http://martinfowler.com/ap2/timeNarrative.html
|
7
|
+
|
8
|
+
The plugin provides a "has_history" class method to ActiveRecord::Base
|
9
|
+
|
10
|
+
See has_history.rb for usage.
|
11
|
+
|
12
|
+
The plugin also provides a history model generator that creates a model class with
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the has_history plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation for the has_history plugin.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'HasHistory'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class ActiveRecord::ConnectionAdapters::Column
|
2
|
+
def name=(name)
|
3
|
+
@name = name
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
class HistoryMigrationGenerator < Rails::Generator::NamedBase
|
8
|
+
def manifest
|
9
|
+
record do |m|
|
10
|
+
columns = class_name.constantize.columns
|
11
|
+
id_column = columns.find{|c| c.name == 'id'}
|
12
|
+
id_column.name = "#{table_name.singularize}_id"
|
13
|
+
|
14
|
+
columns.delete_if{|c| c.name == 'created_at'}
|
15
|
+
|
16
|
+
updated_at_column = columns.find{|c| c.name == 'updated_at'}
|
17
|
+
updated_at_column.name = 'valid_from' if updated_at_column
|
18
|
+
|
19
|
+
m.migration_template 'migration.rb', 'db/migrate', :assigns => {
|
20
|
+
:migration_name => "Create#{"#{class_name}History".pluralize.gsub(/::/, '')}", :table_name => "#{table_name.singularize}_histories", :columns => columns
|
21
|
+
}, :migration_file_name => "create_#{file_path.gsub(/\//, '_')}_#{'history'.pluralize}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
def banner
|
27
|
+
"Usage: ./script/generate history_migration MODELNAME"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class <%= migration_name %> < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :<%= table_name %>, :force => true do |t|
|
4
|
+
# TODO: Remove the columns you don't want recorded.
|
5
|
+
<% for column in columns -%>
|
6
|
+
t.column :<%=column.name%>, :<%=column.type%><%=", :limit => #{column.limit}" unless column.limit.nil? %><%=", :null => #{column.null}" unless column.null %><%=", :default => #{column.default}" unless column.default.nil? %>
|
7
|
+
<% end -%>
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.down
|
12
|
+
drop_table "<%= table_name %>"
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class HistoryModelGenerator < Rails::Generator::NamedBase
|
2
|
+
default_options :skip_migration => false
|
3
|
+
|
4
|
+
def manifest
|
5
|
+
record do |m|
|
6
|
+
# # Check for class naming collisions.
|
7
|
+
m.class_collisions class_path, "#{class_name}History"
|
8
|
+
# m.class_collisions class_path, "#{class_name}HistoryTest"
|
9
|
+
#
|
10
|
+
# # Model, test, and fixture directories.
|
11
|
+
m.directory File.join('app/models', class_path)
|
12
|
+
# m.directory File.join('test/unit', class_path)
|
13
|
+
# m.directory File.join('test/fixtures', class_path)
|
14
|
+
#
|
15
|
+
# # Model class, unit test, and fixtures.
|
16
|
+
m.template 'model.rb', File.join('app/models', class_path, "#{file_name}_history.rb")
|
17
|
+
# m.template 'unit_test.rb', File.join('test/unit', class_path, "#{file_name}_history_test.rb")
|
18
|
+
# m.template 'fixtures.yml', File.join('test/fixtures', class_path, "#{table_name}_history.yml")
|
19
|
+
|
20
|
+
m.dependency 'model', ["#{class_name}History"], :collision => :skip, :skip_migration => true
|
21
|
+
|
22
|
+
unless options[:skip_migration]
|
23
|
+
m.dependency 'history_migration', [class_name], :collision => :skip, :skip_migration => true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
def banner
|
30
|
+
"Usage: #{$0} generate ModelName"
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_options!(opt)
|
34
|
+
opt.separator ''
|
35
|
+
opt.separator 'Options:'
|
36
|
+
opt.on("--skip-migration",
|
37
|
+
"Don't generate a migration file for this history model") { |v| options[:skip_migration] = v }
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
quentin:
|
2
|
+
id: 1
|
3
|
+
login: quentin
|
4
|
+
email: quentin@example.com
|
5
|
+
salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
|
6
|
+
crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
|
7
|
+
#crypted_password: "ce2/iFrNtQ8=\n" # quentin, use only if you're using 2-way encryption
|
8
|
+
created_at: <%%= 5.days.ago.to_s :db %>
|
9
|
+
# activated_at: <%%= 5.days.ago.to_s :db %> # only if you're activating new signups
|
10
|
+
aaron:
|
11
|
+
id: 2
|
12
|
+
login: aaron
|
13
|
+
email: aaron@example.com
|
14
|
+
salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
|
15
|
+
crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
|
16
|
+
# activation_code: aaronscode # only if you're activating new signups
|
17
|
+
created_at: <%%= 1.days.ago.to_s :db %>
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
require '<%= controller_file_name %>_controller'
|
3
|
+
|
4
|
+
# Re-raise errors caught by the controller.
|
5
|
+
class <%= controller_class_name %>Controller; def rescue_action(e) raise e end; end
|
6
|
+
|
7
|
+
class <%= controller_class_name %>ControllerTest < Test::Unit::TestCase
|
8
|
+
# Be sure to include AuthenticatedTestHelper in test/test_helper.rb instead
|
9
|
+
# Then, you can remove it from this and the units test.
|
10
|
+
include AuthenticatedTestHelper
|
11
|
+
|
12
|
+
fixtures :<%= table_name %>
|
13
|
+
|
14
|
+
def setup
|
15
|
+
@controller = <%= controller_class_name %>Controller.new
|
16
|
+
@request = ActionController::TestRequest.new
|
17
|
+
@response = ActionController::TestResponse.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_should_login_and_redirect
|
21
|
+
post :login, :login => 'quentin', :password => 'test'
|
22
|
+
assert session[:<%= file_name %>]
|
23
|
+
assert_response :redirect
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_should_fail_login_and_not_redirect
|
27
|
+
post :login, :login => 'quentin', :password => 'bad password'
|
28
|
+
assert_nil session[:<%= file_name %>]
|
29
|
+
assert_response :success
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_should_allow_signup
|
33
|
+
assert_difference <%= class_name %>, :count do
|
34
|
+
create_<%= file_name %>
|
35
|
+
assert_response :redirect
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_should_require_login_on_signup
|
40
|
+
assert_no_difference <%= class_name %>, :count do
|
41
|
+
create_<%= file_name %>(:login => nil)
|
42
|
+
assert assigns(:<%= file_name %>).errors.on(:login)
|
43
|
+
assert_response :success
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_should_require_password_on_signup
|
48
|
+
assert_no_difference <%= class_name %>, :count do
|
49
|
+
create_<%= file_name %>(:password => nil)
|
50
|
+
assert assigns(:<%= file_name %>).errors.on(:password)
|
51
|
+
assert_response :success
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_should_require_password_confirmation_on_signup
|
56
|
+
assert_no_difference <%= class_name %>, :count do
|
57
|
+
create_<%= file_name %>(:password_confirmation => nil)
|
58
|
+
assert assigns(:<%= file_name %>).errors.on(:password_confirmation)
|
59
|
+
assert_response :success
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_should_require_email_on_signup
|
64
|
+
assert_no_difference <%= class_name %>, :count do
|
65
|
+
create_<%= file_name %>(:email => nil)
|
66
|
+
assert assigns(:<%= file_name %>).errors.on(:email)
|
67
|
+
assert_response :success
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_should_logout
|
72
|
+
login_as :quentin
|
73
|
+
get :logout
|
74
|
+
assert_nil session[:<%= file_name %>]
|
75
|
+
assert_response :redirect
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_should_remember_me
|
79
|
+
post :login, :login => 'quentin', :password => 'test', :remember_me => "1"
|
80
|
+
assert_not_nil @response.cookies["auth_token"]
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_should_not_remember_me
|
84
|
+
post :login, :login => 'quentin', :password => 'test', :remember_me => "0"
|
85
|
+
assert_nil @response.cookies["auth_token"]
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_should_delete_token_on_logout
|
89
|
+
login_as :quentin
|
90
|
+
get :logout
|
91
|
+
assert_equal @response.cookies["auth_token"], []
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_should_login_with_cookie
|
95
|
+
<%= table_name %>(:quentin).remember_me
|
96
|
+
@request.cookies["auth_token"] = cookie_for(:quentin)
|
97
|
+
get :index
|
98
|
+
assert @controller.send(:logged_in?)
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_should_fail_expired_cookie_login
|
102
|
+
<%= table_name %>(:quentin).remember_me
|
103
|
+
users(:quentin).update_attribute :remember_token_expires_at, 5.minutes.ago
|
104
|
+
@request.cookies["auth_token"] = cookie_for(:quentin)
|
105
|
+
get :index
|
106
|
+
assert !@controller.send(:logged_in?)
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_should_fail_cookie_login
|
110
|
+
<%= table_name %>(:quentin).remember_me
|
111
|
+
@request.cookies["auth_token"] = auth_token('invalid_auth_token')
|
112
|
+
get :index
|
113
|
+
assert !@controller.send(:logged_in?)
|
114
|
+
end
|
115
|
+
|
116
|
+
protected
|
117
|
+
def create_<%= file_name %>(options = {})
|
118
|
+
post :signup, :<%= file_name %> => { :login => 'quire', :email => 'quire@example.com',
|
119
|
+
:password => 'quire', :password_confirmation => 'quire' }.merge(options)
|
120
|
+
end
|
121
|
+
|
122
|
+
def auth_token(token)
|
123
|
+
CGI::Cookie.new('name' => 'auth_token', 'value' => token)
|
124
|
+
end
|
125
|
+
|
126
|
+
def cookie_for(<%= file_name %>)
|
127
|
+
auth_token <%= table_name %>(<%= file_name %>).remember_token
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class <%= migration_name %> < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :<%= table_name %>, :force => true do |t|
|
4
|
+
# Remove the columns you don't want recorded.
|
5
|
+
<% for column in columns -%>
|
6
|
+
t.column :<%=column.name%>, :<%=column.type%><%=", :limit => #{column.limit}" unless column.limit.nil? %><%=", :null => #{column.null}" unless column.null %><%=", :default => #{column.default}" unless column.default.nil? %>
|
7
|
+
<% end -%>
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.down
|
12
|
+
drop_table "<%= table_name %>"
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class <%= class_name %>Test < Test::Unit::TestCase
|
4
|
+
# Be sure to include AuthenticatedTestHelper in test/test_helper.rb instead.
|
5
|
+
# Then, you can remove it from this and the functional test.
|
6
|
+
include AuthenticatedTestHelper
|
7
|
+
fixtures :<%= table_name %>
|
8
|
+
|
9
|
+
def test_should_create_<%= file_name %>
|
10
|
+
assert_difference <%= class_name %>, :count do
|
11
|
+
<%= file_name %> = create_<%= file_name %>
|
12
|
+
assert !<%= file_name %>.new_record?, "#{<%= file_name %>.errors.full_messages.to_sentence}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_should_require_login
|
17
|
+
assert_no_difference <%= class_name %>, :count do
|
18
|
+
u = create_<%= file_name %>(:login => nil)
|
19
|
+
assert u.errors.on(:login)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_should_require_password
|
24
|
+
assert_no_difference <%= class_name %>, :count do
|
25
|
+
u = create_<%= file_name %>(:password => nil)
|
26
|
+
assert u.errors.on(:password)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_should_require_password_confirmation
|
31
|
+
assert_no_difference <%= class_name %>, :count do
|
32
|
+
u = create_<%= file_name %>(:password_confirmation => nil)
|
33
|
+
assert u.errors.on(:password_confirmation)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_should_require_email
|
38
|
+
assert_no_difference <%= class_name %>, :count do
|
39
|
+
u = create_<%= file_name %>(:email => nil)
|
40
|
+
assert u.errors.on(:email)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_should_reset_password
|
45
|
+
<%= table_name %>(:quentin).update_attributes(:password => 'new password', :password_confirmation => 'new password')
|
46
|
+
assert_equal <%= table_name %>(:quentin), <%= class_name %>.authenticate('quentin', 'new password')
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_should_not_rehash_password
|
50
|
+
<%= table_name %>(:quentin).update_attributes(:login => 'quentin2')
|
51
|
+
assert_equal <%= table_name %>(:quentin), <%= class_name %>.authenticate('quentin2', 'test')
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_should_authenticate_<%= file_name %>
|
55
|
+
assert_equal <%= table_name %>(:quentin), <%= class_name %>.authenticate('quentin', 'test')
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_should_set_remember_token
|
59
|
+
<%= table_name %>(:quentin).remember_me
|
60
|
+
assert_not_nil <%= table_name %>(:quentin).remember_token
|
61
|
+
assert_not_nil <%= table_name %>(:quentin).remember_token_expires_at
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_should_unset_remember_token
|
65
|
+
<%= table_name %>(:quentin).remember_me
|
66
|
+
assert_not_nil <%= table_name %>(:quentin).remember_token
|
67
|
+
<%= table_name %>(:quentin).forget_me
|
68
|
+
assert_nil <%= table_name %>(:quentin).remember_token
|
69
|
+
end
|
70
|
+
|
71
|
+
protected
|
72
|
+
def create_<%= file_name %>(options = {})
|
73
|
+
<%= class_name %>.create({ :login => 'quire', :email => 'quire@example.com', :password => 'quire', :password_confirmation => 'quire' }.merge(options))
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
@@ -0,0 +1,178 @@
|
|
1
|
+
module HasHistory
|
2
|
+
# TODO (uwe)(2007-10-31): Allow different strategies: timestamped_master, separate_history_table, separate_history_table_with_last
|
3
|
+
|
4
|
+
# Adds a trigger to the model that saves a copy of the model in a history table.
|
5
|
+
#
|
6
|
+
# The default history model is called <ModelName>History, but can be overridden by the "class_name" option.
|
7
|
+
#
|
8
|
+
# All columns are copied unless excluded by the :except or :only options.
|
9
|
+
#
|
10
|
+
# The created_at field is never changed in the original model, and is therefore omitted in the history model.
|
11
|
+
#
|
12
|
+
# The updated_at column should preserve the original timestamps but changes meaning in the history table. It therefore is renamed to "valid_from".
|
13
|
+
#
|
14
|
+
# The original row is references by a foreign key column with default name "<lower case model name>_id".
|
15
|
+
# This can be overridden by the :foreign_key option.
|
16
|
+
#
|
17
|
+
# You can automagically generate the history model class at runtime by adding the :generate_model => true option.
|
18
|
+
# In that case, the following option is also available:
|
19
|
+
#
|
20
|
+
# * :allow_updates
|
21
|
+
#
|
22
|
+
# See HasHistory#acts_as_history for a description of this option.
|
23
|
+
#
|
24
|
+
# examples:
|
25
|
+
#
|
26
|
+
# class Person < ActiveRecord::Base
|
27
|
+
# has_history
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# class Person < ActiveRecord::Base
|
31
|
+
# has_history :class_name => 'PersonHistory'
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# class Person < ActiveRecord::Base
|
35
|
+
# has_history :except => :birthdate
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# class Person < ActiveRecord::Base
|
39
|
+
# has_history :except => [:birthdate, :social_security_no]
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# class Person < ActiveRecord::Base
|
43
|
+
# has_history :only => :weight
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# class Person < ActiveRecord::Base
|
47
|
+
# has_history :only => [:height, :weight]
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# class Person < ActiveRecord::Base
|
51
|
+
# has_history :foreign_key => :person_id
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# class Person < ActiveRecord::Base
|
55
|
+
# has_history :generate_model => true
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# class Person < ActiveRecord::Base
|
59
|
+
# has_history :generate_model => true, :allow_updates => true
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# class Person < ActiveRecord::Base
|
63
|
+
# has_history :generate_model => true, :allow_updates => true
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
def has_history options = {}
|
67
|
+
options[:only] = [options[:only]] if options[:only].is_a? Symbol
|
68
|
+
options[:except] = [options[:except]] if options[:except].is_a? Symbol
|
69
|
+
history_model_class_name = (options[:class_name] && options[:class_name].to_s) || "#{name}History"
|
70
|
+
|
71
|
+
if options[:generate_model]
|
72
|
+
Object::class_eval <<-EOF
|
73
|
+
class #{history_model_class_name} < ActiveRecord::Base
|
74
|
+
acts_as_history :class_name => :#{table_name.singularize}, :allow_updates => #{options[:allow_updates].inspect}
|
75
|
+
end
|
76
|
+
EOF
|
77
|
+
end
|
78
|
+
|
79
|
+
history_model = history_model_class_name.constantize
|
80
|
+
foreign_key = options[:foreign_key] || "#{table_name.singularize}_id".to_sym
|
81
|
+
|
82
|
+
before_update do |record|
|
83
|
+
old_values = find(record.id).attributes.symbolize_keys
|
84
|
+
new_values = record.attributes.symbolize_keys
|
85
|
+
values = {foreign_key => record.id}.update(old_values)
|
86
|
+
old_values.delete(:updated_at)
|
87
|
+
new_values.delete(:updated_at)
|
88
|
+
unless new_values == old_values
|
89
|
+
values[:valid_from] = values.delete(:updated_at) if values[:updated_at]
|
90
|
+
values.delete(:id)
|
91
|
+
values.delete_if {|k,v| not history_model.column_names.include? k.to_s}
|
92
|
+
values.delete_if {|k,v| not options[:only].include? k} if options[:only]
|
93
|
+
values.delete_if {|k,v| options[:except].include? k} if options[:except]
|
94
|
+
history_model.create!(values)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
if options[:order]
|
98
|
+
order = options[:order]
|
99
|
+
elsif history_model.columns.find {|c| c.name == 'valid_from'}
|
100
|
+
order = 'valid_from DESC'
|
101
|
+
else
|
102
|
+
order = 'id'
|
103
|
+
end
|
104
|
+
has_many history_model.table_name.pluralize.to_sym, :dependent => :destroy, :foreign_key => foreign_key, :order => order
|
105
|
+
end
|
106
|
+
|
107
|
+
# Adds a belongs_to relationship to a master model and blocks updates and destroy actions on this class.
|
108
|
+
#
|
109
|
+
# If the class name of the history model ends with "History", the default master model class name is automatically calculated (ie. PersonHistory => Person).
|
110
|
+
# If the history class name does not end in "History", or you want to use another class name, you must override it with the :class_name option.
|
111
|
+
#
|
112
|
+
# Normally the history model is blocked for updates, but updates can be enabled by the :allow_updates option.
|
113
|
+
#
|
114
|
+
# The original row is references by a foreign key column with default name "<lower case model name>_id".
|
115
|
+
# This can be overridden by the "foreign_key" option.
|
116
|
+
#
|
117
|
+
# If you would like to inject the update trigger in the master model from the history model, use the :inject_trigger => true option.
|
118
|
+
# If you do this, these additional options are available:
|
119
|
+
#
|
120
|
+
# * :only
|
121
|
+
# * :except
|
122
|
+
#
|
123
|
+
# See HasHistory#has_history for a description of these options.
|
124
|
+
#
|
125
|
+
# examples:
|
126
|
+
#
|
127
|
+
# class PersonHistory < ActiveRecord::Base
|
128
|
+
# acts_as_history
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# class PersonRecords < ActiveRecord::Base
|
132
|
+
# acts_as_history :class_name => 'Person'
|
133
|
+
# end
|
134
|
+
#
|
135
|
+
# class Person < ActiveRecord::Base
|
136
|
+
# acts_as_history :allow_updates => true
|
137
|
+
# end
|
138
|
+
#
|
139
|
+
# class Person < ActiveRecord::Base
|
140
|
+
# acts_as_history :inject_trigger => true, :except => :birthdate
|
141
|
+
# end
|
142
|
+
#
|
143
|
+
# class Person < ActiveRecord::Base
|
144
|
+
# acts_as_history :inject_trigger => true, :except => [:birthdate, :social_security_no]
|
145
|
+
# end
|
146
|
+
#
|
147
|
+
# class Person < ActiveRecord::Base
|
148
|
+
# acts_as_history :inject_trigger => true, :only => :weight
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# class Person < ActiveRecord::Base
|
152
|
+
# acts_as_history :inject_trigger => true, :only => [:height, :weight]
|
153
|
+
# end
|
154
|
+
#
|
155
|
+
# class Person < ActiveRecord::Base
|
156
|
+
# has_history :foreign_key => :person_id
|
157
|
+
# end
|
158
|
+
#
|
159
|
+
# class Person < ActiveRecord::Base
|
160
|
+
# has_history :generate_model => true
|
161
|
+
# end
|
162
|
+
#
|
163
|
+
def acts_as_history options = {}
|
164
|
+
model_name = options[:class_name] || (name =~ /(.*)History/ && $1.constantize.table_name.singularize.to_sym)
|
165
|
+
raise "Master model class name '#{$1}' is missing and cannot be deduced." if model_name.nil?
|
166
|
+
belongs_to model_name
|
167
|
+
unless options[:allow_updates]
|
168
|
+
validate_on_update do |record|
|
169
|
+
record.errors.add "History:", "You may not change history!"
|
170
|
+
end
|
171
|
+
before_destroy do |record|
|
172
|
+
record.errors.add :id, "You may not change history!"
|
173
|
+
false
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper.rb')
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
class HasHistoryTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@lone_master = LoneMaster.new
|
7
|
+
reset_connection
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_create
|
11
|
+
master = PairedMaster.create!
|
12
|
+
assert_equal 1, inserts.size
|
13
|
+
assert_equal 0, updates.size
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_update
|
17
|
+
master = PairedMaster.create!
|
18
|
+
master.a = 'b'
|
19
|
+
master.save!
|
20
|
+
assert_equal 2, inserts.size, "\n" + inserts.join("\n")
|
21
|
+
assert_equal 1, updates.size, updates.inspect
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_no_record_on_no_change_update
|
25
|
+
master = PairedMaster.create!
|
26
|
+
master.save!
|
27
|
+
assert_equal 1, inserts.size, "\n" + inserts.join("\n")
|
28
|
+
assert_equal 1, updates.size, updates.inspect
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class LoneMaster < ActiveRecord::Base
|
2
|
+
abstract_class = true
|
3
|
+
has_history :generate_model => true
|
4
|
+
has_one :other_model, :class_name => 'ModelStub'
|
5
|
+
has_many :other_models, :class_name => 'ModelStub'
|
6
|
+
attr_accessor :a, :b, :c, :d
|
7
|
+
|
8
|
+
def other_model=(val)
|
9
|
+
@other_model = val
|
10
|
+
end
|
11
|
+
def other_model
|
12
|
+
@other_model || nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def other_models=(val)
|
16
|
+
@other_models = val
|
17
|
+
end
|
18
|
+
def other_models
|
19
|
+
@other_models || []
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.columns
|
23
|
+
@columns ||= [
|
24
|
+
ActiveRecord::ConnectionAdapters::Column.new(:a, ''),
|
25
|
+
ActiveRecord::ConnectionAdapters::Column.new(:b, ''),
|
26
|
+
ActiveRecord::ConnectionAdapters::Column.new(:c, ''),
|
27
|
+
ActiveRecord::ConnectionAdapters::Column.new(:d, '')
|
28
|
+
]
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.columns_hash
|
32
|
+
@columns_hash ||= columns.inject({}) { |hash, column| hash[column.name.to_s] = column; hash }
|
33
|
+
end
|
34
|
+
|
35
|
+
# column-level security methods, used for testing
|
36
|
+
def self.a_authorized_for_bar?(user)
|
37
|
+
true
|
38
|
+
end
|
39
|
+
def self.b_authorized?(user)
|
40
|
+
false
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb'))
|
4
|
+
|
5
|
+
require File.dirname(__FILE__) + '/lone_master'
|
6
|
+
require File.dirname(__FILE__) + '/paired_master'
|
7
|
+
require File.dirname(__FILE__) + '/paired_master_history'
|
8
|
+
require File.dirname(__FILE__) + '/ignorant_master'
|
9
|
+
require File.dirname(__FILE__) + '/ignorant_master_history'
|
10
|
+
|
11
|
+
LoneMaster.connection.instance_eval do
|
12
|
+
SEQUENCE_COLUMNS = [
|
13
|
+
ActiveRecord::ConnectionAdapters::Column.new('value', nil, translate_field_type('INTEGER'), false)
|
14
|
+
]
|
15
|
+
MASTER_COLUMNS = [
|
16
|
+
ActiveRecord::ConnectionAdapters::Column.new('id', nil, translate_field_type('INTEGER'), false),
|
17
|
+
ActiveRecord::ConnectionAdapters::Column.new('created_at', nil, translate_field_type('TIMESTAMP'), false),
|
18
|
+
ActiveRecord::ConnectionAdapters::Column.new('updated_at', nil, translate_field_type('TIMESTAMP'), false),
|
19
|
+
ActiveRecord::ConnectionAdapters::Column.new('a', nil, translate_field_type('VARCHAR(1)'), false),
|
20
|
+
ActiveRecord::ConnectionAdapters::Column.new('b', nil, translate_field_type('BOOLEAN'), false),
|
21
|
+
ActiveRecord::ConnectionAdapters::Column.new('c', nil, translate_field_type('INTEGER'), false),
|
22
|
+
ActiveRecord::ConnectionAdapters::Column.new('d', nil, translate_field_type('FLOAT'), false),
|
23
|
+
]
|
24
|
+
HISTORY_COLUMNS = [
|
25
|
+
ActiveRecord::ConnectionAdapters::Column.new('id', nil, translate_field_type('INTEGER'), false),
|
26
|
+
ActiveRecord::ConnectionAdapters::Column.new('paired_master_id', nil, translate_field_type('INTEGER'), false),
|
27
|
+
ActiveRecord::ConnectionAdapters::Column.new('created_at', nil, translate_field_type('TIMESTAMP'), false),
|
28
|
+
ActiveRecord::ConnectionAdapters::Column.new('valid_from', nil, translate_field_type('TIMESTAMP'), false),
|
29
|
+
ActiveRecord::ConnectionAdapters::Column.new('a', nil, translate_field_type('VARCHAR(1)'), false),
|
30
|
+
ActiveRecord::ConnectionAdapters::Column.new('b', nil, translate_field_type('BOOLEAN'), false),
|
31
|
+
ActiveRecord::ConnectionAdapters::Column.new('c', nil, translate_field_type('INTEGER'), false),
|
32
|
+
ActiveRecord::ConnectionAdapters::Column.new('d', nil, translate_field_type('FLOAT'), false),
|
33
|
+
]
|
34
|
+
TABLES = {
|
35
|
+
'lone_masters' => {:columns => MASTER_COLUMNS},
|
36
|
+
'paired_masters' => {:columns => MASTER_COLUMNS},
|
37
|
+
'paired_master_histories' => {:columns => HISTORY_COLUMNS},
|
38
|
+
}
|
39
|
+
|
40
|
+
def statements
|
41
|
+
@statements
|
42
|
+
end
|
43
|
+
|
44
|
+
def inserts
|
45
|
+
@statements.select {|s| s =~ /^INSERT INTO /}
|
46
|
+
end
|
47
|
+
|
48
|
+
def updates
|
49
|
+
@statements.select {|s| s =~ /^UPDATE /}
|
50
|
+
end
|
51
|
+
|
52
|
+
def reset
|
53
|
+
@statements = []
|
54
|
+
@stored_rows = {}
|
55
|
+
end
|
56
|
+
|
57
|
+
def execute(sql, name = nil)
|
58
|
+
@statements << sql
|
59
|
+
case sql
|
60
|
+
when /BEGIN|COMMIT|ROLLBACK/
|
61
|
+
return
|
62
|
+
when /^SELECT currval/
|
63
|
+
return MockResult.new(SEQUENCE_COLUMNS, [['1']])
|
64
|
+
when /^INSERT INTO (.*) \((.*)\) VALUES\s*\((.*)\)$/
|
65
|
+
table_name, fields, values = $1, $2, $3
|
66
|
+
fields = fields.gsub('"', '').split(', ')
|
67
|
+
values = values.split(', ').map {|v| v == 'NULL' ? nil : v}
|
68
|
+
|
69
|
+
@stored_rows[table_name] ||= []
|
70
|
+
id = @stored_rows[table_name].size + 1
|
71
|
+
@stored_rows[table_name][id] = {}
|
72
|
+
fields.each_with_index do |field_name, i|
|
73
|
+
@stored_rows[table_name][id][fields[i]] = values[i]
|
74
|
+
end
|
75
|
+
return
|
76
|
+
when /^SELECT (.*) FROM (.*) WHERE \(.*\."id" = (\d*)\)/
|
77
|
+
fields, table_name, id = $1, $2, $3.to_i
|
78
|
+
raise "Oooops" unless fields == '*'
|
79
|
+
raise "Invalid ID" unless id >= 1
|
80
|
+
columns = (table_name =~ /_history/ ? HISTORY_COLUMNS : MASTER_COLUMNS)
|
81
|
+
row = [id] + columns[1..-1].map {|c| @stored_rows[table_name][id][c.name] }
|
82
|
+
return MockResult.new(columns, [row])
|
83
|
+
when /UPDATE (.*) SET (.*) WHERE "id" = (\d+)/
|
84
|
+
table_name, change_str, id = $1, $2, $3.to_i
|
85
|
+
raise "Invalid ID" unless id >= 1
|
86
|
+
changes_array = change_str.split(', ')
|
87
|
+
changes = {}
|
88
|
+
changes_array.each {|c| c =~ /"(.*)" = (.*)/ ; changes[$1] = $2 }
|
89
|
+
@stored_rows[table_name][id].update(changes)
|
90
|
+
return MockResult.new([], [1])
|
91
|
+
end
|
92
|
+
raise "Unknown SQL: '#{sql}'"
|
93
|
+
end
|
94
|
+
|
95
|
+
def columns(table_name, name = nil)
|
96
|
+
raise "Unknown table '#{table_name}'" unless TABLES[table_name]
|
97
|
+
TABLES[table_name][:columns]
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
class MockResult
|
103
|
+
attr_reader :fields
|
104
|
+
|
105
|
+
def initialize(columns, rows)
|
106
|
+
@fields = columns.map {|c| c.name}
|
107
|
+
@types = columns.map {|c| c.type}
|
108
|
+
@rows = rows
|
109
|
+
end
|
110
|
+
|
111
|
+
def type(i)
|
112
|
+
@types[i]
|
113
|
+
end
|
114
|
+
|
115
|
+
def result
|
116
|
+
@rows
|
117
|
+
end
|
118
|
+
|
119
|
+
def cmdtuples
|
120
|
+
@rows.size
|
121
|
+
end
|
122
|
+
|
123
|
+
def clear
|
124
|
+
@fields = nil
|
125
|
+
@types = nil
|
126
|
+
@rows = nil
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
class Test::Unit::TestCase
|
132
|
+
def statements
|
133
|
+
connection.statements
|
134
|
+
end
|
135
|
+
|
136
|
+
def inserts
|
137
|
+
connection.inserts
|
138
|
+
end
|
139
|
+
|
140
|
+
def updates
|
141
|
+
connection.updates
|
142
|
+
end
|
143
|
+
|
144
|
+
def reset_connection
|
145
|
+
connection.reset
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def connection
|
151
|
+
ActiveRecord::Base.connection
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
# Uninstall hook code here
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: backlog
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.29.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Uwe Kubosch
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-04-
|
12
|
+
date: 2008-04-07 00:00:00 +02:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -30,15 +30,6 @@ dependencies:
|
|
30
30
|
- !ruby/object:Gem::Version
|
31
31
|
version: 0.2.9
|
32
32
|
version:
|
33
|
-
- !ruby/object:Gem::Dependency
|
34
|
-
name: rmagick
|
35
|
-
version_requirement:
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ~>
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: 1.15.12
|
41
|
-
version:
|
42
33
|
- !ruby/object:Gem::Dependency
|
43
34
|
name: postgres
|
44
35
|
version_requirement:
|
@@ -66,7 +57,7 @@ dependencies:
|
|
66
57
|
- !ruby/object:Gem::Version
|
67
58
|
version: 1.5.1
|
68
59
|
version:
|
69
|
-
description: Backlog is a tool to help you collect and organize all your tasks, whether you are a single person or a small or large group.
|
60
|
+
description: Welcome to Backlog! Backlog is a tool to help you collect and organize all your tasks, whether you are a single person or a small or large group.
|
70
61
|
email: uwe@kubosch.no
|
71
62
|
executables:
|
72
63
|
- backlog
|
@@ -267,6 +258,7 @@ files:
|
|
267
258
|
- app/models/work_lock_notify.rb
|
268
259
|
- app/models/work.rb
|
269
260
|
- app/models/work_lock_nagger.rb
|
261
|
+
- app/models/works_report_filter.rb
|
270
262
|
- app/models/customer.rb
|
271
263
|
- app/models/period.rb
|
272
264
|
- app/models/task.rb
|
@@ -287,6 +279,7 @@ files:
|
|
287
279
|
- app/models/work_lock.rb
|
288
280
|
- app/models/work_account.rb
|
289
281
|
- no_test.rb~
|
282
|
+
- Screenshot.png
|
290
283
|
- lang
|
291
284
|
- lang/no.yaml
|
292
285
|
- lang/localizations.yaml
|
@@ -516,6 +509,37 @@ files:
|
|
516
509
|
- vendor/plugins/rails_time/lib
|
517
510
|
- vendor/plugins/rails_time/lib/time_of_day.rb
|
518
511
|
- vendor/plugins/rails_time/lib/activerecord_time_extension.rb
|
512
|
+
- vendor/plugins/has_history
|
513
|
+
- vendor/plugins/has_history/README
|
514
|
+
- vendor/plugins/has_history/init.rb
|
515
|
+
- vendor/plugins/has_history/generators
|
516
|
+
- vendor/plugins/has_history/generators/history_model
|
517
|
+
- vendor/plugins/has_history/generators/history_model/history_model_generator.rb
|
518
|
+
- vendor/plugins/has_history/generators/history_model/templates
|
519
|
+
- vendor/plugins/has_history/generators/history_model/templates/fixtures.yml
|
520
|
+
- vendor/plugins/has_history/generators/history_model/templates/unit_test.rb
|
521
|
+
- vendor/plugins/has_history/generators/history_model/templates/model.rb
|
522
|
+
- vendor/plugins/has_history/generators/history_model/templates/functional_test.rb
|
523
|
+
- vendor/plugins/has_history/generators/history_model/templates/migration.rb
|
524
|
+
- vendor/plugins/has_history/generators/history_migration
|
525
|
+
- vendor/plugins/has_history/generators/history_migration/history_migration_generator.rb
|
526
|
+
- vendor/plugins/has_history/generators/history_migration/templates
|
527
|
+
- vendor/plugins/has_history/generators/history_migration/templates/migration.rb
|
528
|
+
- vendor/plugins/has_history/tasks
|
529
|
+
- vendor/plugins/has_history/tasks/has_history_tasks.rake
|
530
|
+
- vendor/plugins/has_history/install.rb
|
531
|
+
- vendor/plugins/has_history/uninstall.rb
|
532
|
+
- vendor/plugins/has_history/test
|
533
|
+
- vendor/plugins/has_history/test/paired_master.rb
|
534
|
+
- vendor/plugins/has_history/test/ignorant_master_history.rb
|
535
|
+
- vendor/plugins/has_history/test/has_history_test.rb
|
536
|
+
- vendor/plugins/has_history/test/lone_master.rb
|
537
|
+
- vendor/plugins/has_history/test/ignorant_master.rb
|
538
|
+
- vendor/plugins/has_history/test/test_helper.rb
|
539
|
+
- vendor/plugins/has_history/test/paired_master_history.rb
|
540
|
+
- vendor/plugins/has_history/Rakefile
|
541
|
+
- vendor/plugins/has_history/lib
|
542
|
+
- vendor/plugins/has_history/lib/has_history.rb
|
519
543
|
- public
|
520
544
|
- "public/Frav\xC3\xA6rsskjema.xls"
|
521
545
|
- public/robots.txt
|