backlog 0.28.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. data/History.txt +19 -0
  2. data/README.txt +8 -2
  3. data/Rakefile +3 -2
  4. data/Screenshot.png +0 -0
  5. data/app/controllers/groups_controller.rb +1 -2
  6. data/app/controllers/work_accounts_controller.rb +5 -2
  7. data/app/controllers/works_controller.rb +2 -1
  8. data/app/models/works_report_filter.rb +22 -0
  9. data/app/views/search/results.rhtml +1 -1
  10. data/app/views/work_accounts/_title.rhtml +1 -0
  11. data/app/views/work_accounts/list.rhtml +6 -7
  12. data/app/views/works/list.rhtml +36 -7
  13. data/lang/en.yaml +1 -0
  14. data/lang/no.yaml +1 -0
  15. data/test/performance/common.rb +1 -1
  16. data/test/performance/server_test.rb +5 -5
  17. data/vendor/plugins/has_history/README +12 -0
  18. data/vendor/plugins/has_history/Rakefile +22 -0
  19. data/vendor/plugins/has_history/generators/history_migration/history_migration_generator.rb +30 -0
  20. data/vendor/plugins/has_history/generators/history_migration/templates/migration.rb +14 -0
  21. data/vendor/plugins/has_history/generators/history_model/history_model_generator.rb +39 -0
  22. data/vendor/plugins/has_history/generators/history_model/templates/fixtures.yml +17 -0
  23. data/vendor/plugins/has_history/generators/history_model/templates/functional_test.rb +129 -0
  24. data/vendor/plugins/has_history/generators/history_model/templates/migration.rb +14 -0
  25. data/vendor/plugins/has_history/generators/history_model/templates/model.rb +4 -0
  26. data/vendor/plugins/has_history/generators/history_model/templates/unit_test.rb +75 -0
  27. data/vendor/plugins/has_history/init.rb +5 -0
  28. data/vendor/plugins/has_history/install.rb +1 -0
  29. data/vendor/plugins/has_history/lib/has_history.rb +178 -0
  30. data/vendor/plugins/has_history/tasks/has_history_tasks.rake +4 -0
  31. data/vendor/plugins/has_history/test/has_history_test.rb +31 -0
  32. data/vendor/plugins/has_history/test/ignorant_master.rb +3 -0
  33. data/vendor/plugins/has_history/test/ignorant_master_history.rb +4 -0
  34. data/vendor/plugins/has_history/test/lone_master.rb +42 -0
  35. data/vendor/plugins/has_history/test/paired_master.rb +5 -0
  36. data/vendor/plugins/has_history/test/paired_master_history.rb +5 -0
  37. data/vendor/plugins/has_history/test/test_helper.rb +154 -0
  38. data/vendor/plugins/has_history/uninstall.rb +1 -0
  39. metadata +36 -12
@@ -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
- * Install ImageMagick (only required if you want graphs)
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'], ['rmagick', '~> 1.15.12'],
36
- ['postgres', '~> 0.7.9'], ['slave', '~> 1.2.1']]
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
 
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 = ReportFilter.new(params[: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], :page => params[:page], :per_page => @report_filter.page_size
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 && (@work.description || @work.notes || @work.start_time || @work.completed_at) && @work.save
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 task.description, :controller => 'work_accounts', :action => :edit, :id => work_account%></li>
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
- <% for column in WorkAccount.content_columns %>
12
- <th><%= column.human_name %></th>
13
- <% end %>
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
- <% for column in WorkAccount.content_columns %>
19
- <td><%=h work_account.send(column.name) %></td>
20
- <% end %>
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>
@@ -6,7 +6,12 @@
6
6
 
7
7
  <% if @report_filter %>
8
8
  <% form_for :report_filter, :html => {:method => :get} do |f| %>
9
- <p><label for="report_filter_start_on"><%=l :start_on%></label>
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
- <label for="report_filter_page_size"><%=l :paging%></label>
46
- <%=f.check_box :page_size, {:checked => @report_filter.page_size == 10, :onchange => 'form.submit()'}, 10, nil %>
47
-
48
- <%=submit_tag l(:search) %>
49
- </p>
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 %>
@@ -5,6 +5,7 @@ aborted: Aborted
5
5
  account: Account
6
6
  active: Active
7
7
  administration: Administration
8
+ all: All
8
9
  assigned_to: Assigned to
9
10
  back: Back
10
11
  backlog: Backlog
@@ -5,6 +5,7 @@ aborted: Avbrutt
5
5
  account: Konto
6
6
  active: Aktiv
7
7
  administration: Administrasjon
8
+ all: All
8
9
  assigned_to: Tilordnet
9
10
  back: Tilbake
10
11
  backlog: Oppgaveliste
@@ -47,7 +47,7 @@ module Backlog
47
47
  def test_lots_of_requests
48
48
  runs = 3
49
49
  no_of_threads = 2
50
- hits_per_thread = 5
50
+ hits_per_thread = 15
51
51
 
52
52
  duration = with_timing("First") {get_burn_down_chart}
53
53
  first_hit_limit = 12 * @factor
@@ -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
- start_server
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,4 @@
1
+ require 'digest/sha1'
2
+ class <%="#{class_name}History" %> < ActiveRecord::Base
3
+ acts_as_history
4
+ 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,5 @@
1
+ require 'has_history'
2
+
3
+ class ActiveRecord::Base
4
+ extend HasHistory
5
+ 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,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :has_history do
3
+ # # Task goes here
4
+ # 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,3 @@
1
+ class IgnorantMaster < ActiveRecord::Base
2
+ abstract_class = true
3
+ end
@@ -0,0 +1,4 @@
1
+ class IgnorantMasterHistory < ActiveRecord::Base
2
+ abstract_class = true
3
+ acts_as_history :inject_trigger => true
4
+ 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,5 @@
1
+ class PairedMaster < ActiveRecord::Base
2
+ abstract_class = true
3
+ require File.dirname(__FILE__) + '/paired_master_history'
4
+ has_history
5
+ end
@@ -0,0 +1,5 @@
1
+ class PairedMasterHistory < ActiveRecord::Base
2
+ abstract_class = true
3
+ require File.dirname(__FILE__) + '/paired_master'
4
+ acts_as_history
5
+ 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.28.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-01 00:00:00 +02:00
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. A time keeping module is also included to track time spent on the different tasks.
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