backlog 0.36.2 → 0.37.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (250) hide show
  1. data/Gemfile +16 -4
  2. data/Gemfile.lock +130 -0
  3. data/History.txt +17 -0
  4. data/README.txt +0 -2
  5. data/Rakefile +17 -7
  6. data/app/controllers/absences_controller.rb +1 -2
  7. data/app/controllers/application_controller.rb +13 -14
  8. data/app/controllers/application_controller.rb.rails2 +186 -0
  9. data/app/controllers/estimates_controller.rb +1 -1
  10. data/app/controllers/groups_controller.rb +3 -1
  11. data/app/controllers/periods_controller.rb +61 -50
  12. data/app/controllers/{backlogs_controller.rb → projects_controller.rb} +35 -35
  13. data/app/controllers/search_controller.rb +2 -2
  14. data/app/controllers/tasks_controller.rb +11 -11
  15. data/app/controllers/user_controller.rb +6 -4
  16. data/app/controllers/welcome_controller.rb +4 -4
  17. data/app/controllers/work_locks_controller.rb +2 -2
  18. data/app/controllers/works_controller.rb +31 -31
  19. data/app/helpers/application_helper.rb +2 -2
  20. data/app/helpers/application_helper.rb.rails2 +118 -0
  21. data/app/helpers/periods_helper.rb +3 -3
  22. data/app/helpers/{backlogs_helper.rb → projects_helper.rb} +5 -5
  23. data/app/helpers/user_helper.rb +2 -2
  24. data/app/{models → mailers}/user_notify.rb +1 -0
  25. data/app/models/absence.rb +2 -2
  26. data/{lib → app/models}/clock.rb +0 -0
  27. data/app/models/estimate.rb +2 -2
  28. data/app/models/party.rb +1 -2
  29. data/app/models/period.rb +17 -18
  30. data/app/models/{backlog.rb → project.rb} +2 -2
  31. data/app/models/sidebar.rb +2 -2
  32. data/app/models/task.rb +28 -28
  33. data/app/models/user.rb +5 -4
  34. data/app/models/work.rb +29 -23
  35. data/app/models/work_lock_nagger.rb +1 -1
  36. data/app/models/works_report_filter.rb +4 -4
  37. data/app/views/customers/_name_list.rhtml +1 -1
  38. data/app/views/layouts/_headers.rhtml +2 -1
  39. data/app/views/layouts/_left_top.rhtml +32 -29
  40. data/app/views/layouts/_shortcuts.rhtml +15 -9
  41. data/app/views/layouts/_shortcuts_js.rhtml +4 -4
  42. data/app/views/layouts/mwrt002.html.erb +44 -0
  43. data/app/views/periods/_burn_down_chart.rhtml +1 -1
  44. data/app/views/periods/_show_active.rhtml +3 -3
  45. data/app/views/periods/_title.rhtml +1 -1
  46. data/app/views/{backlogs → projects}/_buttons.rhtml +4 -4
  47. data/app/views/projects/_form.rhtml +44 -0
  48. data/app/views/projects/_name_list.rhtml +5 -0
  49. data/app/views/projects/edit.rhtml +14 -0
  50. data/app/views/{backlogs → projects}/finish_task.rjs +0 -0
  51. data/app/views/projects/list.rhtml +16 -0
  52. data/app/views/{backlogs → projects}/move_task_to_period.rjs +0 -0
  53. data/app/views/{backlogs → projects}/new.rhtml +1 -1
  54. data/app/views/{backlogs → projects}/reopen_task.rjs +0 -0
  55. data/app/views/{backlogs → projects}/show.rhtml +6 -6
  56. data/app/views/search/results.rhtml +3 -3
  57. data/app/views/tasks/_backlog_header.rhtml +4 -4
  58. data/app/views/tasks/_completed.rhtml +2 -2
  59. data/app/views/tasks/_form.rhtml +13 -13
  60. data/app/views/tasks/_task.rhtml +10 -10
  61. data/app/views/tasks/edit.rhtml +1 -1
  62. data/app/views/tasks/list.rhtml +3 -3
  63. data/app/views/tasks/list_started.rhtml +4 -4
  64. data/app/views/tasks/start_work.rjs +1 -1
  65. data/app/views/user/login.rhtml +1 -1
  66. data/app/views/user/signup.rhtml +1 -1
  67. data/app/views/user/welcome.rhtml +1 -1
  68. data/app/views/works/_description_list.rhtml +1 -1
  69. data/app/views/works/_form.rhtml +5 -5
  70. data/app/views/works/_new_row.rhtml +8 -8
  71. data/app/views/works/_row.rhtml +1 -1
  72. data/app/views/works/_task_id_list.rhtml +1 -1
  73. data/app/views/works/daily_work_sheet.rhtml +1 -1
  74. data/app/views/works/list.rhtml +5 -5
  75. data/app/views/works/list_excel.rhtml +2 -2
  76. data/app/views/works/timeliste.rhtml +14 -14
  77. data/app/views/works/weekly_work_sheet.rhtml +5 -5
  78. data/app/views/works/weekly_work_sheet_details.rhtml +5 -5
  79. data/backlog.gemspec +44 -0
  80. data/bin/backlog +5 -1
  81. data/config.ru +4 -0
  82. data/config/application.rb +10 -0
  83. data/config/boot.rb +12 -116
  84. data/config/database.yml +3 -6
  85. data/config/database.yml~ +17 -0
  86. data/config/environment.rb +28 -49
  87. data/config/environments/development.rb +24 -20
  88. data/config/environments/development.rb.rails2 +26 -0
  89. data/config/environments/production.rb +26 -22
  90. data/config/environments/test.rb +20 -15
  91. data/config/environments/user_environment.rb +1 -1
  92. data/config/initializers/backtrace_silencers.rb +7 -0
  93. data/config/initializers/inflections.rb +10 -0
  94. data/config/initializers/jdbc.rb +1 -1
  95. data/config/initializers/mime_types.rb +5 -0
  96. data/config/initializers/secret_token.rb +7 -0
  97. data/config/initializers/session_store.rb +8 -0
  98. data/config/locales/en.yml +22 -11
  99. data/config/locales/no.yml +1 -0
  100. data/config/routes.rb +4 -5
  101. data/cruise_build.sh +6 -2
  102. data/db/migrate/004_add_period.rb +22 -22
  103. data/db/migrate/015_add_user_option.rb +5 -19
  104. data/db/migrate/017_increase_backlog_name_limit.rb +10 -0
  105. data/db/migrate/021_create_work_accounts.rb +0 -2
  106. data/db/migrate/20101006092700_rename_backlogs_to_projects.rb +22 -0
  107. data/db/migrate/20101006092700_rename_backlogs_to_projects.rb~ +22 -0
  108. data/db/schema.rb +27 -30
  109. data/db/seeds.rb +7 -0
  110. data/lib/array_helper.rb +0 -8
  111. data/lib/class_table_inheritance.rb +8 -7
  112. data/lib/tasks/backup.rake +3 -3
  113. data/lib/tasks/jdbc.rake +2 -2
  114. data/lib/version_from_history.rb +1 -1
  115. data/public/404.html +23 -7
  116. data/public/422.html +26 -0
  117. data/public/500.html +23 -6
  118. data/public/images/rails.png +0 -0
  119. data/public/javascripts/controls.js +5 -3
  120. data/public/javascripts/dragdrop.js +7 -6
  121. data/public/javascripts/effects.js +8 -13
  122. data/public/javascripts/prototype.js +3381 -1700
  123. data/public/javascripts/rails.js +175 -0
  124. data/public/robots.txt +5 -1
  125. data/script/rails +6 -0
  126. data/test/client/login.rb +0 -2
  127. data/test/client/login_test.rb +1 -1
  128. data/test/client/setup.rb +25 -24
  129. data/test/fixtures/{backlogs.yml → projects.yml} +2 -2
  130. data/test/fixtures/tasks.yml +9 -9
  131. data/test/fixtures/work_lock_subscriptions.yml +2 -2
  132. data/test/fixtures/works.yml +7 -7
  133. data/test/functional/periods_controller_test.rb +1 -1
  134. data/test/functional/{backlogs_controller_test.rb → projects_controller_test.rb} +22 -21
  135. data/test/functional/search_controller_test.rb +1 -1
  136. data/test/functional/tasks_controller_test.rb +17 -17
  137. data/test/functional/user_controller_test.rb +16 -21
  138. data/test/functional/welcome_controller_test.rb +4 -3
  139. data/test/functional/works_controller_test.rb +5 -5
  140. data/test/integration/user_system_test.rb +1 -1
  141. data/test/mocks/test/clock.rb +1 -1
  142. data/test/performance/browsing_test.rb +9 -0
  143. data/test/performance/common.rb +1 -1
  144. data/test/test_helper.rb +23 -6
  145. data/test/test_helper.rb~ +121 -0
  146. data/test/unit/user_test.rb +3 -3
  147. data/test/unit/work_test.rb +7 -7
  148. data/vendor/plugins/assert_cookie/lib/assert_cookie.rb +0 -2
  149. data/vendor/plugins/{foreign_key_migrations → dynamic_form}/MIT-LICENSE +1 -1
  150. data/vendor/plugins/dynamic_form/README +13 -0
  151. data/vendor/plugins/dynamic_form/Rakefile +10 -0
  152. data/vendor/plugins/dynamic_form/dynamic_form.gemspec +12 -0
  153. data/vendor/plugins/dynamic_form/init.rb +1 -0
  154. data/vendor/plugins/dynamic_form/lib/action_view/helpers/dynamic_form.rb +300 -0
  155. data/vendor/plugins/dynamic_form/lib/action_view/locale/en.yml +8 -0
  156. data/vendor/plugins/dynamic_form/lib/dynamic_form.rb +5 -0
  157. data/vendor/plugins/dynamic_form/test/dynamic_form_i18n_test.rb +42 -0
  158. data/vendor/plugins/dynamic_form/test/dynamic_form_test.rb +370 -0
  159. data/vendor/plugins/dynamic_form/test/test_helper.rb +9 -0
  160. data/vendor/plugins/prototype_legacy_helper/lib/prototype_legacy_helper.rb +432 -0
  161. data/vendor/plugins/prototype_legacy_helper/test/test_prototype_helper.rb +297 -0
  162. data/vendor/plugins/rails_time/test/debug.log +1 -0
  163. data/vendor/plugins/{redhillonrails_core → verification}/MIT-LICENSE +1 -1
  164. data/vendor/plugins/verification/README +34 -0
  165. data/vendor/plugins/verification/Rakefile +22 -0
  166. data/vendor/plugins/verification/init.rb +3 -0
  167. data/vendor/plugins/verification/lib/action_controller/verification.rb +132 -0
  168. data/vendor/plugins/verification/test/test_helper.rb +18 -0
  169. data/vendor/plugins/verification/test/verification_test.rb +270 -0
  170. data/vendor/plugins/will_paginate/lib/will_paginate/collection.rb +1 -1
  171. metadata +115 -134
  172. data/Gemfile~ +0 -4
  173. data/History.txt~ +0 -961
  174. data/LICENSE_LOCALIZATION +0 -20
  175. data/README_LOCALIZATION +0 -61
  176. data/README_RAILS +0 -180
  177. data/app/views/backlogs/_form.rhtml +0 -44
  178. data/app/views/backlogs/_name_list.rhtml +0 -5
  179. data/app/views/backlogs/edit.rhtml +0 -14
  180. data/app/views/backlogs/list.rhtml +0 -16
  181. data/app/views/layouts/mwrt002.rhtml +0 -43
  182. data/config/initializers/mongrel.rb +0 -83
  183. data/config/preinitializer.rb +0 -20
  184. data/config/warble.rb~ +0 -84
  185. data/db/migrate/017_insert_datek_projects.rb +0 -98
  186. data/lib/change_column_null_migration_fix.rb +0 -15
  187. data/no_test.rb~ +0 -6
  188. data/public/dispatch.cgi +0 -10
  189. data/public/dispatch.fcgi +0 -24
  190. data/public/dispatch.rb +0 -10
  191. data/script/about +0 -3
  192. data/script/breakpointer +0 -3
  193. data/script/console +0 -3
  194. data/script/dbconsole +0 -3
  195. data/script/destroy +0 -3
  196. data/script/generate +0 -3
  197. data/script/performance/benchmarker +0 -3
  198. data/script/performance/profiler +0 -3
  199. data/script/plugin +0 -3
  200. data/script/process/inspector +0 -3
  201. data/script/process/reaper +0 -3
  202. data/script/process/spawner +0 -3
  203. data/script/runner +0 -3
  204. data/script/server +0 -3
  205. data/test/client/login.rb~ +0 -33
  206. data/test/mocks/test/user_notify.rb +0 -16
  207. data/vendor/plugins/foreign_key_migrations/CHANGELOG +0 -103
  208. data/vendor/plugins/foreign_key_migrations/README +0 -87
  209. data/vendor/plugins/foreign_key_migrations/about.yml +0 -5
  210. data/vendor/plugins/foreign_key_migrations/init.rb +0 -1
  211. data/vendor/plugins/foreign_key_migrations/install.rb +0 -1
  212. data/vendor/plugins/foreign_key_migrations/lib/foreign_key_migrations.rb +0 -3
  213. data/vendor/plugins/foreign_key_migrations/lib/red_hill_consulting/foreign_key_migrations/active_record/base.rb +0 -22
  214. data/vendor/plugins/foreign_key_migrations/lib/red_hill_consulting/foreign_key_migrations/active_record/connection_adapters/abstract_adapter.rb +0 -22
  215. data/vendor/plugins/foreign_key_migrations/lib/red_hill_consulting/foreign_key_migrations/active_record/connection_adapters/table_definition.rb +0 -28
  216. data/vendor/plugins/lightwindow_helper/README +0 -33
  217. data/vendor/plugins/lightwindow_helper/assets/images/ajax-loading.gif +0 -0
  218. data/vendor/plugins/lightwindow_helper/assets/images/arrow-down.gif +0 -0
  219. data/vendor/plugins/lightwindow_helper/assets/images/arrow-up.gif +0 -0
  220. data/vendor/plugins/lightwindow_helper/assets/images/black-70.png +0 -0
  221. data/vendor/plugins/lightwindow_helper/assets/images/black.png +0 -0
  222. data/vendor/plugins/lightwindow_helper/assets/images/nextlabel.gif +0 -0
  223. data/vendor/plugins/lightwindow_helper/assets/images/prevlabel.gif +0 -0
  224. data/vendor/plugins/lightwindow_helper/assets/javascripts/lightwindow.js +0 -1921
  225. data/vendor/plugins/lightwindow_helper/assets/stylesheets/lightwindow.css +0 -376
  226. data/vendor/plugins/lightwindow_helper/init.rb +0 -1
  227. data/vendor/plugins/lightwindow_helper/install.rb +0 -7
  228. data/vendor/plugins/lightwindow_helper/lib/lightwindow_helper.rb +0 -31
  229. data/vendor/plugins/redhillonrails_core/CHANGELOG +0 -150
  230. data/vendor/plugins/redhillonrails_core/README +0 -124
  231. data/vendor/plugins/redhillonrails_core/init.rb +0 -19
  232. data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/base.rb +0 -54
  233. data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/abstract_adapter.rb +0 -31
  234. data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/column.rb +0 -21
  235. data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/foreign_key_definition.rb +0 -26
  236. data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/index_definition.rb +0 -11
  237. data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/mysql_adapter.rb +0 -74
  238. data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/mysql_column.rb +0 -8
  239. data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/postgresql_adapter.rb +0 -99
  240. data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/schema_statements.rb +0 -16
  241. data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/sqlite3_adapter.rb +0 -9
  242. data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/table_definition.rb +0 -27
  243. data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/schema.rb +0 -27
  244. data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/schema_dumper.rb +0 -47
  245. data/vendor/plugins/transactional_migrations/CHANGELOG +0 -9
  246. data/vendor/plugins/transactional_migrations/MIT-LICENSE +0 -20
  247. data/vendor/plugins/transactional_migrations/README +0 -15
  248. data/vendor/plugins/transactional_migrations/about.yml +0 -5
  249. data/vendor/plugins/transactional_migrations/init.rb +0 -1
  250. data/vendor/plugins/transactional_migrations/lib/red_hill_consulting/transactional_migrations/active_record/migration.rb +0 -19
@@ -73,7 +73,7 @@ class UserTest < ActiveSupport::TestCase
73
73
 
74
74
  u.change_password("hugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehuge")
75
75
  assert !u.save
76
- assert u.errors.invalid?('password')
76
+ assert u.errors['password'].any?
77
77
 
78
78
  # TODO (uwe): This is commented out since I allow empty passwords
79
79
  # u.change_password("")
@@ -96,11 +96,11 @@ class UserTest < ActiveSupport::TestCase
96
96
 
97
97
  u.login = "hugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahugeteslahug"
98
98
  assert !u.save
99
- assert u.errors.invalid?(:login)
99
+ assert u.errors[:login].any?
100
100
 
101
101
  u.login = ""
102
102
  assert !u.save
103
- assert u.errors.invalid?(:login)
103
+ assert u.errors[:login].any?
104
104
 
105
105
  u.login = "oktesla"
106
106
  assert u.save
@@ -50,20 +50,20 @@ class WorkTest < ActiveSupport::TestCase
50
50
  assert_equal 1, Work.find_work_for_day(Date.parse('2007-06-12')).size
51
51
  end
52
52
 
53
- def test_works_for_week_by_backlog
53
+ def test_works_for_week_by_project
54
54
  Thread.current[:user] = users(:tesla)
55
- work_totals = Work.works_for_week_by_backlog(2007, 24, users(:tesla))
56
- assert_equal({backlogs(:first) => [nil, BigDecimal('40'), BigDecimal('8'), nil, nil, nil, nil]}, work_totals)
55
+ work_totals = Work.works_for_week_by_project(2007, 24, users(:tesla))
56
+ assert_equal({projects(:first) => [nil, BigDecimal('40'), BigDecimal('8'), nil, nil, nil, nil]}, work_totals)
57
57
  end
58
58
 
59
- def test_works_for_week_by_backlog_anonymous
60
- work_totals = Work.works_for_week_by_backlog(2007, 24, nil)
61
- assert_equal({backlogs(:first) => [nil, BigDecimal('30'), nil, nil, nil, nil, nil]}, work_totals)
59
+ def test_works_for_week_by_project_anonymous
60
+ work_totals = Work.works_for_week_by_project(2007, 24, nil)
61
+ assert_equal({projects(:first) => [nil, BigDecimal('30'), nil, nil, nil, nil, nil]}, work_totals)
62
62
  end
63
63
 
64
64
  def test_legal_to_register_work_if_the_same_date_is_marked_as_absent
65
65
  Thread.current[:user] = users(:tesla)
66
- Work.create!({:started_on => '2007-06-10', :start_time => '17:59', :user_id => 1000001, :backlog => backlogs(:first)})
66
+ Work.create!({:started_on => '2007-06-10', :start_time => '17:59', :user_id => 1000001, :project => projects(:first)})
67
67
  end
68
68
 
69
69
  def test_calculate_hours
@@ -13,7 +13,6 @@ module Test
13
13
  # assert_cookie :secret, :path => lambda { |path| path =~ /secret/ },
14
14
  # :secure => true
15
15
  def assert_cookie(name, options={}, message="")
16
- clean_backtrace do
17
16
  cookie = cookies[name.to_s]
18
17
 
19
18
  msg = build_message(message, "expected cookie named <?> but it was not found.", name)
@@ -42,7 +41,6 @@ module Test
42
41
  assert_call_or_value :domain, options, cookie, message
43
42
  assert_call_or_value :expires, options, cookie, message
44
43
  assert_call_or_value :secure, options, cookie, message
45
- end
46
44
  end
47
45
 
48
46
  # Tests that a cookie named +name+ does not exist. This is useful
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006 RedHill Consulting, Pty. Ltd.
1
+ Copyright (c) 2010 David Heinemeier Hansson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -0,0 +1,13 @@
1
+ DynamicForm
2
+ ===========
3
+
4
+ DynamicForm holds a few helpers method to help you deal with your models, they are:
5
+
6
+ * input(record, method, options = {})
7
+ * form(record, options = {})
8
+ * error_message_on(object, method, options={})
9
+ * error_messages_for(record, options={})
10
+
11
+ It also adds f.error_messages and f.error_messages_on to your form builders.
12
+
13
+ Copyright (c) 2010 David Heinemeier Hansson, released under the MIT license
@@ -0,0 +1,10 @@
1
+ require 'rake/testtask'
2
+
3
+ desc 'Default: run unit tests.'
4
+ task :default => :test
5
+
6
+ desc 'Test the active_model_helper plugin.'
7
+ Rake::TestTask.new(:test) do |t|
8
+ t.libs << 'test'
9
+ t.pattern = 'test/**/*_test.rb'
10
+ end
@@ -0,0 +1,12 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'dynamic_form'
3
+ s.version = '1.0.0'
4
+ s.author = 'David Heinemeier Hansson'
5
+ s.email = 'david@loudthinking.com'
6
+ s.summary = 'Deprecated dynamic form helpers: input, form, error_messages_for, error_messages_on'
7
+
8
+ s.add_dependency('rails', '>= 3.0.0')
9
+
10
+ s.files = Dir['lib/**/*']
11
+ s.require_path = 'lib'
12
+ end
@@ -0,0 +1 @@
1
+ require 'dynamic_form'
@@ -0,0 +1,300 @@
1
+ require 'action_view/helpers'
2
+ require 'active_support/i18n'
3
+ require 'active_support/core_ext/enumerable'
4
+ require 'active_support/core_ext/object/blank'
5
+
6
+ module ActionView
7
+ module Helpers
8
+ # The Active Record Helper makes it easier to create forms for records kept in instance variables. The most far-reaching is the +form+
9
+ # method that creates a complete form for all the basic content types of the record (not associations or aggregations, though). This
10
+ # is a great way of making the record quickly available for editing, but likely to prove lackluster for a complicated real-world form.
11
+ # In that case, it's better to use the +input+ method and the specialized +form+ methods in link:classes/ActionView/Helpers/FormHelper.html
12
+ module DynamicForm
13
+ # Returns a default input tag for the type of object returned by the method. For example, if <tt>@post</tt>
14
+ # has an attribute +title+ mapped to a +VARCHAR+ column that holds "Hello World":
15
+ #
16
+ # input("post", "title")
17
+ # # => <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
18
+ def input(record_name, method, options = {})
19
+ InstanceTag.new(record_name, method, self).to_tag(options)
20
+ end
21
+
22
+ # Returns an entire form with all needed input tags for a specified Active Record object. For example, if <tt>@post</tt>
23
+ # has attributes named +title+ of type +VARCHAR+ and +body+ of type +TEXT+ then
24
+ #
25
+ # form("post")
26
+ #
27
+ # would yield a form like the following (modulus formatting):
28
+ #
29
+ # <form action='/posts/create' method='post'>
30
+ # <p>
31
+ # <label for="post_title">Title</label><br />
32
+ # <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
33
+ # </p>
34
+ # <p>
35
+ # <label for="post_body">Body</label><br />
36
+ # <textarea cols="40" id="post_body" name="post[body]" rows="20"></textarea>
37
+ # </p>
38
+ # <input name="commit" type="submit" value="Create" />
39
+ # </form>
40
+ #
41
+ # It's possible to specialize the form builder by using a different action name and by supplying another
42
+ # block renderer. For example, if <tt>@entry</tt> has an attribute +message+ of type +VARCHAR+ then
43
+ #
44
+ # form("entry",
45
+ # :action => "sign",
46
+ # :input_block => Proc.new { |record, column|
47
+ # "#{column.human_name}: #{input(record, column.name)}<br />"
48
+ # })
49
+ #
50
+ # would yield a form like the following (modulus formatting):
51
+ #
52
+ # <form action="/entries/sign" method="post">
53
+ # Message:
54
+ # <input id="entry_message" name="entry[message]" size="30" type="text" /><br />
55
+ # <input name="commit" type="submit" value="Sign" />
56
+ # </form>
57
+ #
58
+ # It's also possible to add additional content to the form by giving it a block, such as:
59
+ #
60
+ # form("entry", :action => "sign") do |form|
61
+ # form << content_tag("b", "Department")
62
+ # form << collection_select("department", "id", @departments, "id", "name")
63
+ # end
64
+ #
65
+ # The following options are available:
66
+ #
67
+ # * <tt>:action</tt> - The action used when submitting the form (default: +create+ if a new record, otherwise +update+).
68
+ # * <tt>:input_block</tt> - Specialize the output using a different block, see above.
69
+ # * <tt>:method</tt> - The method used when submitting the form (default: +post+).
70
+ # * <tt>:multipart</tt> - Whether to change the enctype of the form to "multipart/form-data", used when uploading a file (default: +false+).
71
+ # * <tt>:submit_value</tt> - The text of the submit button (default: "Create" if a new record, otherwise "Update").
72
+ def form(record_name, options = {})
73
+ record = instance_variable_get("@#{record_name}")
74
+ record = convert_to_model(record)
75
+
76
+ options = options.symbolize_keys
77
+ options[:action] ||= record.persisted? ? "update" : "create"
78
+ action = url_for(:action => options[:action], :id => record)
79
+
80
+ submit_value = options[:submit_value] || options[:action].gsub(/[^\w]/, '').capitalize
81
+
82
+ contents = form_tag({:action => action}, :method =>(options[:method] || 'post'), :enctype => options[:multipart] ? 'multipart/form-data': nil)
83
+ contents.safe_concat hidden_field(record_name, :id) if record.persisted?
84
+ contents.safe_concat all_input_tags(record, record_name, options)
85
+ yield contents if block_given?
86
+ contents.safe_concat submit_tag(submit_value)
87
+ contents.safe_concat('</form>')
88
+ end
89
+
90
+ # Returns a string containing the error message attached to the +method+ on the +object+ if one exists.
91
+ # This error message is wrapped in a <tt>DIV</tt> tag by default or with <tt>:html_tag</tt> if specified,
92
+ # which can be extended to include a <tt>:prepend_text</tt> and/or <tt>:append_text</tt> (to properly explain
93
+ # the error), and a <tt>:css_class</tt> to style it accordingly. +object+ should either be the name of an
94
+ # instance variable or the actual object. The method can be passed in either as a string or a symbol.
95
+ # As an example, let's say you have a model <tt>@post</tt> that has an error message on the +title+ attribute:
96
+ #
97
+ # <%= error_message_on "post", "title" %>
98
+ # # => <div class="formError">can't be empty</div>
99
+ #
100
+ # <%= error_message_on @post, :title %>
101
+ # # => <div class="formError">can't be empty</div>
102
+ #
103
+ # <%= error_message_on "post", "title",
104
+ # :prepend_text => "Title simply ",
105
+ # :append_text => " (or it won't work).",
106
+ # :html_tag => "span",
107
+ # :css_class => "inputError" %>
108
+ # # => <span class="inputError">Title simply can't be empty (or it won't work).</span>
109
+ def error_message_on(object, method, *args)
110
+ options = args.extract_options!
111
+ unless args.empty?
112
+ ActiveSupport::Deprecation.warn('error_message_on takes an option hash instead of separate ' +
113
+ 'prepend_text, append_text, html_tag, and css_class arguments', caller)
114
+
115
+ options[:prepend_text] = args[0] || ''
116
+ options[:append_text] = args[1] || ''
117
+ options[:html_tag] = args[2] || 'div'
118
+ options[:css_class] = args[3] || 'formError'
119
+ end
120
+ options.reverse_merge!(:prepend_text => '', :append_text => '', :html_tag => 'div', :css_class => 'formError')
121
+
122
+ object = convert_to_model(object)
123
+
124
+ if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) &&
125
+ (errors = obj.errors[method]).presence
126
+ content_tag(options[:html_tag],
127
+ (options[:prepend_text].html_safe << errors.first).safe_concat(options[:append_text]),
128
+ :class => options[:css_class]
129
+ )
130
+ else
131
+ ''
132
+ end
133
+ end
134
+
135
+ # Returns a string with a <tt>DIV</tt> containing all of the error messages for the objects located as instance variables by the names
136
+ # given. If more than one object is specified, the errors for the objects are displayed in the order that the object names are
137
+ # provided.
138
+ #
139
+ # This <tt>DIV</tt> can be tailored by the following options:
140
+ #
141
+ # * <tt>:header_tag</tt> - Used for the header of the error div (default: "h2").
142
+ # * <tt>:id</tt> - The id of the error div (default: "errorExplanation").
143
+ # * <tt>:class</tt> - The class of the error div (default: "errorExplanation").
144
+ # * <tt>:object</tt> - The object (or array of objects) for which to display errors,
145
+ # if you need to escape the instance variable convention.
146
+ # * <tt>:object_name</tt> - The object name to use in the header, or any text that you prefer.
147
+ # If <tt>:object_name</tt> is not set, the name of the first object will be used.
148
+ # * <tt>:header_message</tt> - The message in the header of the error div. Pass +nil+
149
+ # or an empty string to avoid the header message altogether. (Default: "X errors
150
+ # prohibited this object from being saved").
151
+ # * <tt>:message</tt> - The explanation message after the header message and before
152
+ # the error list. Pass +nil+ or an empty string to avoid the explanation message
153
+ # altogether. (Default: "There were problems with the following fields:").
154
+ #
155
+ # To specify the display for one object, you simply provide its name as a parameter.
156
+ # For example, for the <tt>@user</tt> model:
157
+ #
158
+ # error_messages_for 'user'
159
+ #
160
+ # You can also supply an object:
161
+ #
162
+ # error_messages_for @user
163
+ #
164
+ # This will use the last part of the model name in the presentation. For instance, if
165
+ # this is a MyKlass::User object, this will use "user" as the name in the String. This
166
+ # is taken from MyKlass::User.model_name.human, which can be overridden.
167
+ #
168
+ # To specify more than one object, you simply list them; optionally, you can add an extra <tt>:object_name</tt> parameter, which
169
+ # will be the name used in the header message:
170
+ #
171
+ # error_messages_for 'user_common', 'user', :object_name => 'user'
172
+ #
173
+ # You can also use a number of objects, which will have the same naming semantics
174
+ # as a single object.
175
+ #
176
+ # error_messages_for @user, @post
177
+ #
178
+ # If the objects cannot be located as instance variables, you can add an extra <tt>:object</tt> parameter which gives the actual
179
+ # object (or array of objects to use):
180
+ #
181
+ # error_messages_for 'user', :object => @question.user
182
+ #
183
+ # NOTE: This is a pre-packaged presentation of the errors with embedded strings and a certain HTML structure. If what
184
+ # you need is significantly different from the default presentation, it makes plenty of sense to access the <tt>object.errors</tt>
185
+ # instance yourself and set it up. View the source of this method to see how easy it is.
186
+ def error_messages_for(*params)
187
+ options = params.extract_options!.symbolize_keys
188
+
189
+ objects = Array.wrap(options.delete(:object) || params).map do |object|
190
+ object = instance_variable_get("@#{object}") unless object.respond_to?(:to_model)
191
+ object = convert_to_model(object)
192
+
193
+ if object.class.respond_to?(:model_name)
194
+ options[:object_name] ||= object.class.model_name.human.downcase
195
+ end
196
+
197
+ object
198
+ end
199
+
200
+ objects.compact!
201
+ count = objects.inject(0) {|sum, object| sum + object.errors.count }
202
+
203
+ unless count.zero?
204
+ html = {}
205
+ [:id, :class].each do |key|
206
+ if options.include?(key)
207
+ value = options[key]
208
+ html[key] = value unless value.blank?
209
+ else
210
+ html[key] = 'errorExplanation'
211
+ end
212
+ end
213
+ options[:object_name] ||= params.first
214
+
215
+ I18n.with_options :locale => options[:locale], :scope => [:errors, :template] do |locale|
216
+ header_message = if options.include?(:header_message)
217
+ options[:header_message]
218
+ else
219
+ locale.t :header, :count => count, :model => options[:object_name].to_s.gsub('_', ' ')
220
+ end
221
+
222
+ message = options.include?(:message) ? options[:message] : locale.t(:body)
223
+
224
+ error_messages = objects.sum do |object|
225
+ object.errors.full_messages.map do |msg|
226
+ content_tag(:li, msg)
227
+ end
228
+ end.join.html_safe
229
+
230
+ contents = ''
231
+ contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank?
232
+ contents << content_tag(:p, message) unless message.blank?
233
+ contents << content_tag(:ul, error_messages)
234
+
235
+ content_tag(:div, contents.html_safe, html)
236
+ end
237
+ else
238
+ ''
239
+ end
240
+ end
241
+
242
+ private
243
+
244
+ def all_input_tags(record, record_name, options)
245
+ input_block = options[:input_block] || default_input_block
246
+ record.class.content_columns.collect{ |column| input_block.call(record_name, column) }.join("\n")
247
+ end
248
+
249
+ def default_input_block
250
+ Proc.new { |record, column| %(<p><label for="#{record}_#{column.name}">#{column.human_name}</label><br />#{input(record, column.name)}</p>) }
251
+ end
252
+
253
+ module InstanceTagMethods
254
+ def to_tag(options = {})
255
+ case column_type
256
+ when :string
257
+ field_type = @method_name.include?("password") ? "password" : "text"
258
+ to_input_field_tag(field_type, options)
259
+ when :text
260
+ to_text_area_tag(options)
261
+ when :integer, :float, :decimal
262
+ to_input_field_tag("text", options)
263
+ when :date
264
+ to_date_select_tag(options)
265
+ when :datetime, :timestamp
266
+ to_datetime_select_tag(options)
267
+ when :time
268
+ to_time_select_tag(options)
269
+ when :boolean
270
+ to_boolean_select_tag(options)
271
+ end
272
+ end
273
+
274
+ def column_type
275
+ object.send(:column_for_attribute, @method_name).type
276
+ end
277
+ end
278
+
279
+ module FormBuilderMethods
280
+ def error_message_on(method, *args)
281
+ @template.error_message_on(@object || @object_name, method, *args)
282
+ end
283
+
284
+ def error_messages(options = {})
285
+ @template.error_messages_for(@object_name, objectify_options(options))
286
+ end
287
+ end
288
+ end
289
+
290
+ class InstanceTag
291
+ include DynamicForm::InstanceTagMethods
292
+ end
293
+
294
+ class FormBuilder
295
+ include DynamicForm::FormBuilderMethods
296
+ end
297
+ end
298
+ end
299
+
300
+ I18n.load_path << File.expand_path("../../locale/en.yml", __FILE__)
@@ -0,0 +1,8 @@
1
+ en:
2
+ errors:
3
+ template:
4
+ header:
5
+ one: "1 error prohibited this %{model} from being saved"
6
+ other: "%{count} errors prohibited this %{model} from being saved"
7
+ # The variable :count is also available
8
+ body: "There were problems with the following fields:"
@@ -0,0 +1,5 @@
1
+ require 'action_view/helpers/dynamic_form'
2
+
3
+ class ActionView::Base
4
+ include DynamicForm
5
+ end
@@ -0,0 +1,42 @@
1
+ require 'test_helper'
2
+
3
+ class DynamicFormI18nTest < Test::Unit::TestCase
4
+ include ActionView::Context
5
+ include ActionView::Helpers::DynamicForm
6
+
7
+ attr_reader :request
8
+
9
+ def setup
10
+ @object = stub :errors => stub(:count => 1, :full_messages => ['full_messages'])
11
+ @object.stubs :to_model => @object
12
+ @object.stubs :class => stub(:model_name => stub(:human => ""))
13
+
14
+ @object_name = 'book_seller'
15
+ @object_name_without_underscore = 'book seller'
16
+
17
+ stubs(:content_tag).returns 'content_tag'
18
+
19
+ I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved"
20
+ I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).returns 'There were problems with the following fields:'
21
+ end
22
+
23
+ def test_error_messages_for_given_a_header_option_it_does_not_translate_header_message
24
+ I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').never
25
+ error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en')
26
+ end
27
+
28
+ def test_error_messages_for_given_no_header_option_it_translates_header_message
29
+ I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').returns 'header message'
30
+ error_messages_for(:object => @object, :locale => 'en')
31
+ end
32
+
33
+ def test_error_messages_for_given_a_message_option_it_does_not_translate_message
34
+ I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).never
35
+ error_messages_for(:object => @object, :message => 'message', :locale => 'en')
36
+ end
37
+
38
+ def test_error_messages_for_given_no_message_option_it_translates_message
39
+ I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).returns 'There were problems with the following fields:'
40
+ error_messages_for(:object => @object, :locale => 'en')
41
+ end
42
+ end