backlog 0.28.0 → 0.29.0

Sign up to get free protection for your applications and to get access to all the features.
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