fortitude 0.9.1-java → 0.9.2-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.travis.yml +28 -22
  4. data/CHANGES.md +50 -0
  5. data/CONTRIBUTORS.md +1 -0
  6. data/doc/.gitignore +18 -0
  7. data/doc/Gemfile +21 -0
  8. data/doc/config.rb +92 -0
  9. data/doc/source/images/background.png +0 -0
  10. data/doc/source/images/middleman.png +0 -0
  11. data/doc/source/images/why/icon_button.png +0 -0
  12. data/doc/source/images/why/icon_button@2x.png +0 -0
  13. data/doc/source/images/why/modal_dialog@2x.png +0 -0
  14. data/doc/source/index.html.pcss +96 -0
  15. data/doc/source/index.html.rb +66 -0
  16. data/doc/source/javascripts/all.js +1 -0
  17. data/doc/source/javascripts/highlight.pack.js +1 -0
  18. data/doc/source/layouts/layout.rb +55 -0
  19. data/doc/source/portable/fortitude-bootstrap.rb +53 -0
  20. data/doc/source/shared/base.pcss +62 -0
  21. data/doc/source/shared/base.rb +30 -0
  22. data/doc/source/shared/common.rb +55 -0
  23. data/doc/source/shared/standard_page.rb +40 -0
  24. data/doc/source/stylesheets/_shared_prefix.scss +25 -0
  25. data/doc/source/stylesheets/all.css.scss +7 -0
  26. data/doc/source/stylesheets/basics.css.scss +20 -0
  27. data/doc/source/stylesheets/bootstrap_importer.css.scss +1 -0
  28. data/doc/source/stylesheets/highlight/arta.css +140 -0
  29. data/doc/source/stylesheets/highlight/ascetic.css +52 -0
  30. data/doc/source/stylesheets/highlight/atelier-dune.dark.css +95 -0
  31. data/doc/source/stylesheets/highlight/atelier-dune.light.css +95 -0
  32. data/doc/source/stylesheets/highlight/atelier-forest.dark.css +95 -0
  33. data/doc/source/stylesheets/highlight/atelier-forest.light.css +95 -0
  34. data/doc/source/stylesheets/highlight/atelier-heath.dark.css +95 -0
  35. data/doc/source/stylesheets/highlight/atelier-heath.light.css +95 -0
  36. data/doc/source/stylesheets/highlight/atelier-lakeside.dark.css +95 -0
  37. data/doc/source/stylesheets/highlight/atelier-lakeside.light.css +95 -0
  38. data/doc/source/stylesheets/highlight/atelier-seaside.dark.css +95 -0
  39. data/doc/source/stylesheets/highlight/atelier-seaside.light.css +95 -0
  40. data/doc/source/stylesheets/highlight/brown_paper.css +104 -0
  41. data/doc/source/stylesheets/highlight/brown_papersq.png +0 -0
  42. data/doc/source/stylesheets/highlight/codepen-embed.css +108 -0
  43. data/doc/source/stylesheets/highlight/color-brewer.css +168 -0
  44. data/doc/source/stylesheets/highlight/dark.css +104 -0
  45. data/doc/source/stylesheets/highlight/default.css +152 -0
  46. data/doc/source/stylesheets/highlight/docco.css +135 -0
  47. data/doc/source/stylesheets/highlight/far.css +111 -0
  48. data/doc/source/stylesheets/highlight/foundation.css +136 -0
  49. data/doc/source/stylesheets/highlight/github.css +124 -0
  50. data/doc/source/stylesheets/highlight/googlecode.css +147 -0
  51. data/doc/source/stylesheets/highlight/hybrid.css +170 -0
  52. data/doc/source/stylesheets/highlight/idea.css +125 -0
  53. data/doc/source/stylesheets/highlight/ir_black.css +109 -0
  54. data/doc/source/stylesheets/highlight/kimbie.dark.css +96 -0
  55. data/doc/source/stylesheets/highlight/kimbie.light.css +96 -0
  56. data/doc/source/stylesheets/highlight/magula.css +121 -0
  57. data/doc/source/stylesheets/highlight/mono-blue.css +69 -0
  58. data/doc/source/stylesheets/highlight/monokai.css +127 -0
  59. data/doc/source/stylesheets/highlight/monokai_sublime.css +154 -0
  60. data/doc/source/stylesheets/highlight/obsidian.css +153 -0
  61. data/doc/source/stylesheets/highlight/paraiso.dark.css +95 -0
  62. data/doc/source/stylesheets/highlight/paraiso.light.css +95 -0
  63. data/doc/source/stylesheets/highlight/pojoaque.css +107 -0
  64. data/doc/source/stylesheets/highlight/pojoaque.jpg +0 -0
  65. data/doc/source/stylesheets/highlight/railscasts.css +187 -0
  66. data/doc/source/stylesheets/highlight/rainbow.css +108 -0
  67. data/doc/source/stylesheets/highlight/school_book.css +112 -0
  68. data/doc/source/stylesheets/highlight/school_book.png +0 -0
  69. data/doc/source/stylesheets/highlight/solarized_dark.css +108 -0
  70. data/doc/source/stylesheets/highlight/solarized_light.css +108 -0
  71. data/doc/source/stylesheets/highlight/sunburst.css +164 -0
  72. data/doc/source/stylesheets/highlight/tomorrow-night-blue.css +95 -0
  73. data/doc/source/stylesheets/highlight/tomorrow-night-bright.css +94 -0
  74. data/doc/source/stylesheets/highlight/tomorrow-night-eighties.css +94 -0
  75. data/doc/source/stylesheets/highlight/tomorrow-night.css +95 -0
  76. data/doc/source/stylesheets/highlight/tomorrow.css +92 -0
  77. data/doc/source/stylesheets/highlight/vs.css +93 -0
  78. data/doc/source/stylesheets/highlight/xcode.css +158 -0
  79. data/doc/source/stylesheets/highlight/zenburn.css +118 -0
  80. data/doc/source/why/a_larger_view.html.rb +774 -0
  81. data/doc/source/why/a_simple_helper.html.rb +332 -0
  82. data/doc/source/why/building_a_rich_modal_dialog.html.rb +156 -0
  83. data/doc/source/why/commonality_and_inheritance.html.rb +564 -0
  84. data/doc/source/why/example_list.rb +60 -0
  85. data/doc/source/why/example_page.rb +116 -0
  86. data/doc/source/why/index.html.rb +86 -0
  87. data/doc/source/why/other_benefits.html.rb +110 -0
  88. data/fortitude.gemspec +6 -1
  89. data/lib/fortitude/doctypes/html4_tags_strict.rb +1 -0
  90. data/lib/fortitude/doctypes/html5.rb +1 -0
  91. data/lib/fortitude/method_templates/tag_method_template.rb.smpl +27 -14
  92. data/lib/fortitude/rails/helpers.rb +2 -2
  93. data/lib/fortitude/rendering_context.rb +10 -1
  94. data/lib/fortitude/tags/tag.rb +3 -2
  95. data/lib/fortitude/tags/tag_support.rb +8 -3
  96. data/lib/fortitude/tilt/fortitude_template.rb +6 -2
  97. data/lib/fortitude/version.rb +1 -1
  98. data/lib/fortitude/widget.rb +2 -0
  99. data/lib/fortitude/widget/convenience.rb +30 -0
  100. data/lib/fortitude/widget/files.rb +22 -11
  101. data/lib/fortitude/widget/needs.rb +5 -3
  102. data/spec/helpers/system_helpers.rb +1 -0
  103. data/spec/rails/development_mode_system_spec.rb +0 -1
  104. data/spec/rails/rendering_system_spec.rb +20 -1
  105. data/spec/rails/templates/rendering_system_spec/app/controllers/rendering_system_spec_controller.rb +13 -0
  106. data/spec/rails/templates/rendering_system_spec/app/views/rendering_system_spec/render_hash_subclass.rb +18 -0
  107. data/spec/rails/templates/rendering_system_spec/lib/my_hash.rb +5 -0
  108. data/spec/system/convenience_methods_system_spec.rb +166 -0
  109. data/spec/system/formatting_system_spec.rb +25 -1
  110. data/spec/system/tag_rendering_system_spec.rb +73 -0
  111. data/spec/system/tilt_system_spec.rb +31 -0
  112. data/spec/system/widget_class_from_spec.rb +20 -4
  113. metadata +91 -4
@@ -0,0 +1,93 @@
1
+ /*
2
+
3
+ Visual Studio-like style based on original C# coloring by Jason Diamond <jason@diamond.name>
4
+
5
+ */
6
+ .hljs {
7
+ display: block;
8
+ overflow-x: auto;
9
+ padding: 0.5em;
10
+ background: white;
11
+ color: black;
12
+ -webkit-text-size-adjust: none;
13
+ }
14
+
15
+ .hljs-comment,
16
+ .hljs-annotation,
17
+ .diff .hljs-header,
18
+ .hljs-chunk,
19
+ .apache .hljs-cbracket {
20
+ color: #008000;
21
+ }
22
+
23
+ .hljs-keyword,
24
+ .hljs-id,
25
+ .hljs-built_in,.css
26
+ .smalltalk .hljs-class,
27
+ .hljs-winutils,
28
+ .bash .hljs-variable,
29
+ .tex .hljs-command,
30
+ .hljs-request,
31
+ .hljs-status,
32
+ .nginx .hljs-title,
33
+ .xml .hljs-tag,
34
+ .xml .hljs-tag .hljs-value {
35
+ color: #00f;
36
+ }
37
+
38
+ .hljs-string,
39
+ .hljs-title,
40
+ .hljs-parent,
41
+ .hljs-tag .hljs-value,
42
+ .hljs-rules .hljs-value,
43
+ .ruby .hljs-symbol,
44
+ .ruby .hljs-symbol .hljs-string,
45
+ .hljs-template_tag,
46
+ .django .hljs-variable,
47
+ .hljs-addition,
48
+ .hljs-flow,
49
+ .hljs-stream,
50
+ .apache .hljs-tag,
51
+ .hljs-date,
52
+ .tex .hljs-formula,
53
+ .coffeescript .hljs-attribute {
54
+ color: #a31515;
55
+ }
56
+
57
+ .ruby .hljs-string,
58
+ .hljs-decorator,
59
+ .hljs-filter .hljs-argument,
60
+ .hljs-localvars,
61
+ .hljs-array,
62
+ .hljs-attr_selector,
63
+ .hljs-pseudo,
64
+ .hljs-pi,
65
+ .hljs-doctype,
66
+ .hljs-deletion,
67
+ .hljs-envvar,
68
+ .hljs-shebang,
69
+ .hljs-preprocessor,
70
+ .hljs-pragma,
71
+ .userType,
72
+ .apache .hljs-sqbracket,
73
+ .nginx .hljs-built_in,
74
+ .tex .hljs-special,
75
+ .hljs-prompt {
76
+ color: #2b91af;
77
+ }
78
+
79
+ .hljs-phpdoc,
80
+ .hljs-dartdoc,
81
+ .hljs-javadoc,
82
+ .hljs-xmlDocTag {
83
+ color: #808080;
84
+ }
85
+
86
+ .hljs-type,
87
+ .hljs-typename { font-weight: bold; }
88
+
89
+ .vhdl .hljs-string { color: #666666; }
90
+ .vhdl .hljs-literal { color: #a31515; }
91
+ .vhdl .hljs-attribute { color: #00b0e8; }
92
+
93
+ .xml .hljs-attribute { color: #f00; }
@@ -0,0 +1,158 @@
1
+ /*
2
+
3
+ XCode style (c) Angel Garcia <angelgarcia.mail@gmail.com>
4
+
5
+ */
6
+
7
+ .hljs {
8
+ display: block;
9
+ overflow-x: auto;
10
+ padding: 0.5em;
11
+ background: #fff;
12
+ color: black;
13
+ -webkit-text-size-adjust: none;
14
+ }
15
+
16
+ .hljs-comment,
17
+ .hljs-javadoc {
18
+ color: #006a00;
19
+ }
20
+
21
+ .hljs-keyword,
22
+ .hljs-literal,
23
+ .nginx .hljs-title {
24
+ color: #aa0d91;
25
+ }
26
+ .method,
27
+ .hljs-list .hljs-title,
28
+ .hljs-tag .hljs-title,
29
+ .setting .hljs-value,
30
+ .hljs-winutils,
31
+ .tex .hljs-command,
32
+ .http .hljs-title,
33
+ .hljs-request,
34
+ .hljs-status {
35
+ color: #008;
36
+ }
37
+
38
+ .hljs-envvar,
39
+ .tex .hljs-special {
40
+ color: #660;
41
+ }
42
+
43
+ .hljs-string {
44
+ color: #c41a16;
45
+ }
46
+ .hljs-tag .hljs-value,
47
+ .hljs-cdata,
48
+ .hljs-filter .hljs-argument,
49
+ .hljs-attr_selector,
50
+ .apache .hljs-cbracket,
51
+ .hljs-date,
52
+ .hljs-regexp {
53
+ color: #080;
54
+ }
55
+
56
+ .hljs-sub .hljs-identifier,
57
+ .hljs-pi,
58
+ .hljs-tag,
59
+ .hljs-tag .hljs-keyword,
60
+ .hljs-decorator,
61
+ .ini .hljs-title,
62
+ .hljs-shebang,
63
+ .hljs-prompt,
64
+ .hljs-hexcolor,
65
+ .hljs-rules .hljs-value,
66
+ .hljs-symbol,
67
+ .hljs-symbol .hljs-string,
68
+ .hljs-number,
69
+ .css .hljs-function,
70
+ .hljs-function .hljs-title,
71
+ .coffeescript .hljs-attribute {
72
+ color: #1c00cf;
73
+ }
74
+
75
+ .hljs-class .hljs-title,
76
+ .smalltalk .hljs-class,
77
+ .hljs-javadoctag,
78
+ .hljs-yardoctag,
79
+ .hljs-phpdoc,
80
+ .hljs-dartdoc,
81
+ .hljs-type,
82
+ .hljs-typename,
83
+ .hljs-tag .hljs-attribute,
84
+ .hljs-doctype,
85
+ .hljs-class .hljs-id,
86
+ .hljs-built_in,
87
+ .setting,
88
+ .hljs-params,
89
+ .clojure .hljs-attribute {
90
+ color: #5c2699;
91
+ }
92
+
93
+ .hljs-variable {
94
+ color: #3f6e74;
95
+ }
96
+ .css .hljs-tag,
97
+ .hljs-rules .hljs-property,
98
+ .hljs-pseudo,
99
+ .hljs-subst {
100
+ color: #000;
101
+ }
102
+
103
+ .css .hljs-class,
104
+ .css .hljs-id {
105
+ color: #9b703f;
106
+ }
107
+
108
+ .hljs-value .hljs-important {
109
+ color: #ff7700;
110
+ font-weight: bold;
111
+ }
112
+
113
+ .hljs-rules .hljs-keyword {
114
+ color: #c5af75;
115
+ }
116
+
117
+ .hljs-annotation,
118
+ .apache .hljs-sqbracket,
119
+ .nginx .hljs-built_in {
120
+ color: #9b859d;
121
+ }
122
+
123
+ .hljs-preprocessor,
124
+ .hljs-preprocessor *,
125
+ .hljs-pragma {
126
+ color: #643820;
127
+ }
128
+
129
+ .tex .hljs-formula {
130
+ background-color: #eee;
131
+ font-style: italic;
132
+ }
133
+
134
+ .diff .hljs-header,
135
+ .hljs-chunk {
136
+ color: #808080;
137
+ font-weight: bold;
138
+ }
139
+
140
+ .diff .hljs-change {
141
+ background-color: #bccff9;
142
+ }
143
+
144
+ .hljs-addition {
145
+ background-color: #baeeba;
146
+ }
147
+
148
+ .hljs-deletion {
149
+ background-color: #ffc8bd;
150
+ }
151
+
152
+ .hljs-comment .hljs-yardoctag {
153
+ font-weight: bold;
154
+ }
155
+
156
+ .method .hljs-id {
157
+ color: #000;
158
+ }
@@ -0,0 +1,118 @@
1
+ /*
2
+
3
+ Zenburn style from voldmar.ru (c) Vladimir Epifanov <voldmar@voldmar.ru>
4
+ based on dark.css by Ivan Sagalaev
5
+
6
+ */
7
+
8
+ .hljs {
9
+ display: block;
10
+ overflow-x: auto;
11
+ padding: 0.5em;
12
+ background: #3f3f3f;
13
+ color: #dcdcdc;
14
+ -webkit-text-size-adjust: none;
15
+ }
16
+
17
+ .hljs-keyword,
18
+ .hljs-tag,
19
+ .css .hljs-class,
20
+ .css .hljs-id,
21
+ .lisp .hljs-title,
22
+ .nginx .hljs-title,
23
+ .hljs-request,
24
+ .hljs-status,
25
+ .clojure .hljs-attribute {
26
+ color: #e3ceab;
27
+ }
28
+
29
+ .django .hljs-template_tag,
30
+ .django .hljs-variable,
31
+ .django .hljs-filter .hljs-argument {
32
+ color: #dcdcdc;
33
+ }
34
+
35
+ .hljs-number,
36
+ .hljs-date {
37
+ color: #8cd0d3;
38
+ }
39
+
40
+ .dos .hljs-envvar,
41
+ .dos .hljs-stream,
42
+ .hljs-variable,
43
+ .apache .hljs-sqbracket {
44
+ color: #efdcbc;
45
+ }
46
+
47
+ .dos .hljs-flow,
48
+ .diff .hljs-change,
49
+ .python .exception,
50
+ .python .hljs-built_in,
51
+ .hljs-literal,
52
+ .tex .hljs-special {
53
+ color: #efefaf;
54
+ }
55
+
56
+ .diff .hljs-chunk,
57
+ .hljs-subst {
58
+ color: #8f8f8f;
59
+ }
60
+
61
+ .dos .hljs-keyword,
62
+ .hljs-decorator,
63
+ .hljs-title,
64
+ .hljs-type,
65
+ .diff .hljs-header,
66
+ .ruby .hljs-class .hljs-parent,
67
+ .apache .hljs-tag,
68
+ .nginx .hljs-built_in,
69
+ .tex .hljs-command,
70
+ .hljs-prompt {
71
+ color: #efef8f;
72
+ }
73
+
74
+ .dos .hljs-winutils,
75
+ .ruby .hljs-symbol,
76
+ .ruby .hljs-symbol .hljs-string,
77
+ .ruby .hljs-string {
78
+ color: #dca3a3;
79
+ }
80
+
81
+ .diff .hljs-deletion,
82
+ .hljs-string,
83
+ .hljs-tag .hljs-value,
84
+ .hljs-preprocessor,
85
+ .hljs-pragma,
86
+ .hljs-built_in,
87
+ .hljs-javadoc,
88
+ .smalltalk .hljs-class,
89
+ .smalltalk .hljs-localvars,
90
+ .smalltalk .hljs-array,
91
+ .css .hljs-rules .hljs-value,
92
+ .hljs-attr_selector,
93
+ .hljs-pseudo,
94
+ .apache .hljs-cbracket,
95
+ .tex .hljs-formula,
96
+ .coffeescript .hljs-attribute {
97
+ color: #cc9393;
98
+ }
99
+
100
+ .hljs-shebang,
101
+ .diff .hljs-addition,
102
+ .hljs-comment,
103
+ .hljs-annotation,
104
+ .hljs-pi,
105
+ .hljs-doctype {
106
+ color: #7f9f7f;
107
+ }
108
+
109
+ .coffeescript .javascript,
110
+ .javascript .xml,
111
+ .tex .hljs-formula,
112
+ .xml .javascript,
113
+ .xml .vbscript,
114
+ .xml .css,
115
+ .xml .hljs-cdata {
116
+ opacity: 0.5;
117
+ }
118
+
@@ -0,0 +1,774 @@
1
+ require 'source/why/example_page'
2
+
3
+ module Views
4
+ module Why
5
+ class ALargerView < Views::Why::ExamplePage
6
+ def example_intro
7
+ p {
8
+ text "Fortitude’s benefits become even more evident as your application scales. Next, we’ll take a look at "
9
+ text "a much larger view and the ways Fortitude can improve code at scale."
10
+ }
11
+ end
12
+
13
+ def example_description
14
+ p {
15
+ text "This view has been adapted from another real-world application. It’s a view from an administrative page "
16
+ text "that shows a list of blocked users, reported users, and "; em "reporting"; text " users ("
17
+ em "i.e."; text ", those who have reported other users)."
18
+ }
19
+
20
+ erb "/app/views/admin/users/user_reporting.html.erb", <<-EOS
21
+ <h1>Blocked users in the last <%= @time_span_days %> days:</h1>
22
+
23
+ <table class="blocked_users user_list">
24
+ <thead>
25
+ <tr>
26
+ <th>User ID</th>
27
+ <th>Username</th>
28
+ <th>Email</th>
29
+ <th>Last Login</th>
30
+ <th>Blocked Because</th>
31
+ <th>Blocked By</th>
32
+ </tr>
33
+ </thead>
34
+ <tbody>
35
+ <% @blocked_users.each do |blocked_user|
36
+ blocked_details = @blocked_user_reasons[blocked_user.id] %>
37
+ <tr>
38
+ <td><%= blocked_user.id %></td>
39
+ <td><%= blocked_user.username %></td>
40
+ <td><%= blocked_user.email || "<span class=\"no_email\">(none available)</span>".html_safe %></td>
41
+ <td><%= time_ago_in_words(blocked_user.last_login_at) %></td>
42
+ <td><%= blocked_details[:reason] %></td>
43
+ <td><%= blocked_details[:admin_user] %></td>
44
+ </tr>
45
+ </tbody>
46
+ </table>
47
+
48
+ <h1>Reported Users in the last <%= @time_span_days %> days:</h1>
49
+ <table class="reported_users user_list">
50
+ <thead>
51
+ <tr>
52
+ <th>User ID</th>
53
+ <th>Username</th>
54
+ <th>Email</th>
55
+ <th>Last Login</th>
56
+ <th>Reported For</th>
57
+ <th>Report Text</th>
58
+ <th>Reported By</th>
59
+ </tr>
60
+ </thead>
61
+ <tbody>
62
+ <% @reported_users.each do |reported_user|
63
+ report_details = @reported_user_reasons[reported_user.id] %>
64
+ <tr>
65
+ <td><%= reported_user.id %></td>
66
+ <td><%= reported_user.username %></td>
67
+ <td><%= reported_user.email || "<span class=\"no_email\">(none available)</span>".html_safe %></td>
68
+ <td><%= time_ago_in_words(reported_user.last_login_at) %></td>
69
+ <td><%= report_details[:abuse_flag] %></td>
70
+ <td><%= report_details[:abuse_text] %></td>
71
+ <td><%= report_details[:abuse_reporter] %></td>
72
+ </tr>
73
+ </tbody>
74
+ </table>
75
+
76
+ <h1>Most Reporting Users in the last <%= @time_span_days %> days:</h1>
77
+ <table class="reporting_users user_list">
78
+ <thead>
79
+ <tr>
80
+ <th>User ID</th>
81
+ <th>Username</th>
82
+ <th>Email</th>
83
+ <th>Last Login</th>
84
+ <th>Total Reported Users</th>
85
+ <th>Reported-For Breakdown</th>
86
+ </tr>
87
+ </thead>
88
+ <tbody>
89
+ <% @top_reporting_users.each do |reporting_user|
90
+ reporting_details = @top_reporting_user_details[reporting_user.id] %>
91
+ <tr>
92
+ <td><%= reporting_user.id %></td>
93
+ <td><%= reporting_user.username %></td>
94
+ <td><%= reporting_user.email || "<span class=\"no_email\">(none available)</span>".html_safe %></td>
95
+ <td><%= time_ago_in_words(reporting_user.last_login_at) %></td>
96
+ <td><%= reporting_details[:flags].uniq { |f| f.reported_user_id }.length %></td>
97
+ <td>
98
+ <% grouped_by_flag = reporting_details[:flags].group_by { |f| f.flag_type }
99
+ grouped_by_flag.each do |flag_type, flags| %>
100
+ Flag <strong><%= flag_type %></strong>: <%= flags.length %> total flag(s)<br>
101
+ <% end %>
102
+ </td>
103
+ </tr>
104
+ </tbody>
105
+ </table>
106
+ EOS
107
+
108
+ p {
109
+ text "Obviously, the most notable thing about this view is that it’s "; em "big"; text ". That’s OK — "
110
+ text "there’s no inherent reason that long views are bad. The tables in this view aren’t used anywhere "
111
+ text "else, so there’s no need to share the code with anything else."
112
+ }
113
+
114
+ p {
115
+ text "However, the length of this view makes it pretty hard to read; if you open it on anything but a huge "
116
+ text "monitor, it’s not even obvious that there "; em "is"; text " a list of reporting users, for example. "
117
+ }
118
+
119
+ p {
120
+ text "Further, there’s a lot of duplication among these three tables, which would be nice to clean up, too."
121
+ }
122
+ end
123
+
124
+ def using_standard_engines
125
+ p {
126
+ text "Using standard templating engines, our options in this case are pretty limited."
127
+ }
128
+
129
+ h5 "Splitting Apart the View"
130
+
131
+ p {
132
+ text "One thing we could do would be to break out the three tables into their own partials. This does work, "
133
+ text "and looks pretty concise:"
134
+ }
135
+
136
+ erb "/app/views/admin/users/user_reporting.html.erb", <<-EOS
137
+ <%= render :partial => 'blocked_users' %>
138
+ <%= render :partial => 'reported_users' %>
139
+ <%= render :partial => 'reporting_users' %>
140
+ EOS
141
+
142
+ p {
143
+ text "However, we’ve now created a subtle but insidious problem: these three new partials now require quite "
144
+ text "a few variables to be set in order to render them — specifically, "; code "@time_span_days"; text ", "
145
+ code "@blocked_users"; text ", and "; code "@blocked_user_reasons"; text " for one of them, "
146
+ code "@time_span_days"; text ", "; code "@reported_users"; text ", and "; code "@reported_user_reasons"
147
+ text " for the next, and so on. This is fine, except that "; em "nowhere in the code can you tell this"
148
+ text "— if you look at the original view, it now doesn’t mention those whatsoever, and you can only tell "
149
+ text "that the partials require those variables by carefully scouring their text to see what they use."
150
+ }
151
+
152
+ p {
153
+ text "Even worse, "; em "nowhere in the originating view are any of these variables mentioned"; text "! "
154
+ text "If you were to look at the view, it’s literally impossible to tell which variables the controller "
155
+ text "needs to set in order to make it work. You need to go look at the partials it renders. Now, imagine "
156
+ text "we perform some further refactorings — you might need to look through several layers of partials, "
157
+ text "carefully writing down which "; code "@"; text " variables are used and deduplicating them, just to "
158
+ text "wrap your head around the data being communicated from the controller to the view."
159
+ }
160
+
161
+ p {
162
+ text "Although many views are written this way, this is not any better in view code than it is in any other "
163
+ text "code. We’re passing data using what are effectively implicit global variables, and that’s seriously "
164
+ text "detrimental to maintainability and readability in any application."
165
+ }
166
+
167
+ p {
168
+ text "Further: if we do this, we haven’t done anything to factor out the commonality of these tables…and, "
169
+ text "because they’re now in three separate files, the likelihood that they’ll diverge in a bad way goes up. "
170
+ text "(For example, it’d be much easier for a developer to add a new common column to one of them, while "
171
+ text "forgetting to add it to the others — and now we have not just duplication, but duplication "; em "and"
172
+ text " inconsistency, which is pretty close to the canonical definition of poorly-factored code.)"
173
+ }
174
+
175
+ h5 "Unifying the Tables"
176
+
177
+ p {
178
+ text "Trying to unify the code for the three tables is a lot trickier. While the tables actually have a "
179
+ text "great deal of commonality, "
180
+ text "they also have varying numbers of columns, and that makes it considerably harder. We have a few "
181
+ text "choices here:"
182
+ }
183
+
184
+ ol {
185
+ li {
186
+ text "We could create one partial for the entire table, and then pass in two arrays: one of the extra "
187
+ text "column names, and the second an array of arrays of HTML fragments, one outer array for each row, "
188
+ text "one element in the inner array for each column. Trying to even write that calling code seems like "
189
+ text "an exercise in sheer mess, and prospects for maintainability are slim at best."
190
+ }
191
+ li {
192
+ text "We could create a partial for the table header, and one for the body, and pass in the above data, "
193
+ text "structured just as mentioned above; this cleans it up a little more, but doesn’t really make any "
194
+ text "real fundamental difference, since the big mess will be in the caller, not the partials."
195
+ }
196
+ li {
197
+ text "We could create a partial for the table header, one for the first chunk of the table body, "
198
+ text "and then one that renders a single row of the body, "
199
+ text "passing in HTML for the cells, and loop over the body partial."
200
+ }
201
+ }
202
+
203
+ p {
204
+ text "This last option certainly seems like the best. Let’s see what it looks like in practice. First, the "
205
+ text "header partial:"
206
+ }
207
+
208
+ erb "/app/views/admin/users/_user_reporting_table_header.html.erb", <<-EOS
209
+ <h1><%= title %></h1>
210
+
211
+ <table class="<%= table_classes %> user_list">
212
+ <thead>
213
+ <tr>
214
+ <th>User ID</th>
215
+ <th>Username</th>
216
+ <th>Email</th>
217
+ <th>Last Login</th>
218
+ <% table_header_columns.each do |table_header_column| %>
219
+ <th><%= table_header_column %></th>
220
+ <% end %>
221
+ </tr>
222
+ </thead>
223
+ <tbody>
224
+ EOS
225
+
226
+ p "And the body-row partial:"
227
+
228
+ erb "/app/views/admin/users/_user_reporting_table_row_begin.html.erb", <<-EOS
229
+ <tr>
230
+ <td><%= user.id %></td>
231
+ <td><%= user.username %></td>
232
+ <td><%= user.email || "<span class=\"no_email\">(none available)</span>".html_safe %></td>
233
+ <td><%= time_ago_in_words(user.last_login_at) %></td>
234
+ <%= extra_columns %>
235
+ </tr>
236
+ EOS
237
+
238
+ p "Finally, here’s the caller:"
239
+
240
+ erb "/app/views/admin/users/user_reporting.html.erb", <<-EOS
241
+ <%= render :partial => 'user_reporting_table_header', :locals => {
242
+ :title => "Blocked users in the last \#{time_span_days} days:",
243
+ :table_classes => "blocked_users",
244
+ :table_header_columns => [ "Blocked Because", "Blocked By" ]
245
+ } %>
246
+ <tbody>
247
+ <% @blocked_users.each do |blocked_user|
248
+ blocked_details = @blocked_user_reasons[blocked_user.id] %>
249
+ <%= render :partial => 'user_reporting_table_row_begin', :locals => {
250
+ :user => blocked_user, :extra_columns => "<td>\#{h(blocked_details[:reason])}</td><td>\#{h(blocked_details[:admin_user])}</td>"
251
+ } %>
252
+ <% end %>
253
+ </tbody>
254
+ </table>
255
+
256
+ <%= render :partial => 'user_reporting_table_header', :locals => {
257
+ :title => "Reported users in the last \#{time_span_days} days:",
258
+ :table_classes => "reported_users",
259
+ :table_header_columns => [ "Reported For", "Report Text", "Reported By" ]
260
+ } %>
261
+ <tbody>
262
+ <% @reported_users.each do |reported_user|
263
+ report_details = @reported_user_reasons[reported_user.id] %>
264
+ <%= render :partial => 'user_reporting_table_row_begin', :locals => {
265
+ :user => reported_user, :extra_columns =>
266
+ "<td>\#{h(report_details[:abuse_flag])}</td><td>\#{h(report_details[:abuse_text])}</td><td>\#{h(report_details[:abuse_reporter])}</td>"
267
+ } %>
268
+ <% end %>
269
+ </tbody>
270
+ </table>
271
+
272
+ <%= render :partial => 'user_reporting_table_header', :locals => {
273
+ :title => "Most Reporting Users in the last \#{time_span_days} days:",
274
+ :table_classes => "reporting_users",
275
+ :table_header_columns => [ "Total Reported Users", "Reported-For Breakdown" ]
276
+ } %>
277
+ <tbody>
278
+ <% @top_reporting_users.each do |reporting_user|
279
+ reporting_details = @top_reporting_user_details[reporting_user.id]
280
+ total_flags = reporting_details[:flags].uniq { |f| f.reported_user_id }.length
281
+
282
+ grouped_by_data = grouped_by_flag.map do |flag_type, flags|
283
+ capture do %>
284
+ Flag <strong><%= flag_type %></strong>: <%= flags.length %> total flag(s)<br>
285
+ <% end
286
+ end.join("\n") %>
287
+
288
+ <%= render :partial => 'user_reporting_table_row_begin', :locals => {
289
+ :user => reporting_user, :extra_columns =>
290
+ "<td>\#{h(total_flags)}</td><td>\#{grouped_by_data}</td>"
291
+ } %>
292
+ <% end %>
293
+ </tbody>
294
+ </table>
295
+ EOS
296
+ end
297
+
298
+ def standard_engine_issues
299
+ p {
300
+ text "Ugh. We’ve managed to unify the tables, and yet only at significant expense. Let’s try to break down "
301
+ text "the issues we see here:"
302
+ }
303
+
304
+ ul {
305
+ li {
306
+ strong "Caller Mess"; text ": By far the most apparent issue here: "
307
+ em "now our caller is almost inscrutable"; text ". It doesn’t even look at first glance like it has "
308
+ text "tables in it, the actual HTML involved is strewn in little tiny chunks that are difficult to find "
309
+ text "(and often is now embedded as Ruby strings, not ERb HTML), we’ve had to resort to "; code "capture"
310
+ text " to pass HTML in, and so on. Generally speaking, it sure isn’t very readable."
311
+ }
312
+ li {
313
+ strong { em "Dangerous (XSS Potential)" }; text ": Once again, we’re back in the land of having to be "
314
+ text "very, very careful about HTML escaping. We have to call "; code "#h"; text " manually around the "
315
+ text "user data we put into the "; code "extra_columns"; text " partial parameter. And if we forget, "
316
+ text "even once? We now have an XSS vulnerability."
317
+ }
318
+ li {
319
+ strong { text "Falling back to "; code "#capture" }; text ": For our last table, building the string "
320
+ text "we need to pass in as "; code "extra_columns"; text " requires enough logic that we either would have "
321
+ text "to use Ruby string concatenation multiple times, or, better, use "; code "#capture"
322
+ text " like we do here. While there’s nothing fundamentally wrong with "; code "#capture"
323
+ text ", the way it suddenly builds HTML out-of-order almost always makes code harder to read and harder "
324
+ text "to maintain and debug."
325
+ }
326
+ li {
327
+ strong "File Clutter"; text ": Our single view is now represented using three files, all of which properly "
328
+ text "live in the "; code "app/views/admin/users"; text " directory. Initially, this isn’t that big a deal; "
329
+ text "however, in a large application, this really adds up. Add seven more views like this, and now you have "
330
+ text "24 separate view files or partials in that directory, with no obvious way of knowing which partials "
331
+ text "belong to which views (or even knowing for sure which partials might not even be used any more). "
332
+ text "In other words, the fact that you have to create a new file just to be able to reuse some code, "
333
+ text "even within a single view, is a heavyweight restriction that leads to more files than you otherwise "
334
+ text "might want."
335
+ }
336
+ li {
337
+ strong "Indentation (of Source)"; text ": This is one of those issues that can seem minor, but really adds up over "
338
+ text "the course of a larger application. How do we indent lines in the above example? It’s not entirely "
339
+ text "clear, and there really isn’t a great solution. Basically, the natural indentation of the HTML itself "
340
+ text "is fighting with the indentation of our interlaced Ruby code. There are many different ways to handle "
341
+ text "this, but none of them are great, and none of them are accepted as “the right way”; every developer "
342
+ text "or team will have its own preferences, and your editor probably won’t be of much help."
343
+ }
344
+ li {
345
+ p {
346
+ strong "Indentation (of Output)"; text ": Related to this, the HTML output we get from this is now really, "
347
+ text "really poorly indented. For example, if the last table has one row, it is now going to look like this "
348
+ text "(with some sections elided for clarity):"
349
+ }
350
+
351
+ html_source <<-EOS
352
+ <html>
353
+ <head>
354
+ ...
355
+ </head>
356
+ <body>
357
+ ...
358
+ <h1>Most Reporting Users in the last 30 days:</h1>
359
+
360
+ <table class="reporting_users user_list">
361
+ <thead>
362
+ <tr>
363
+ <th>User ID</th>
364
+ <th>Username</th>
365
+ <th>Email</th>
366
+ <th>Last Login</th>
367
+ <th>Total Reported Users</th>
368
+ <th>Reported-For Breakdown</th>
369
+ </tr>
370
+ </thead>
371
+ <tbody>
372
+ <tr>
373
+ <td>12345</td>
374
+ <td>jane_doe_1</td>
375
+ <td>jdoe@example.com</td>
376
+ <td>about 2 hours ago</td>
377
+ <td>17</td><td>Flag <strong>Spam</strong>: 27 total flag(s)<br>
378
+ Flag <strong>Personal Attack</strong>: 17 total flag(s)<br>
379
+ Flag <strong>Off-Topic</strong>: 9 total flag(s)<br>
380
+ </td>
381
+ </tr>
382
+ </tbody>
383
+ </table>
384
+ ...
385
+ </body>
386
+ </html>
387
+ EOS
388
+ p "Is this the end of the world? Of course not."
389
+ p "Does it make our HTML considerably harder to read? Of course. "
390
+ p "Should we have to put up with this as just a fact of life in 2015? Absolutely not."
391
+ }
392
+ }
393
+ end
394
+
395
+ def using_fortitude
396
+ h5 "Before Refactoring, a Translation"
397
+
398
+ p {
399
+ text "Fortitude can help improve this situation "; em "a lot"; text ". However, before we refactor this, let’s "
400
+ text "take a look at what a simple Fortitude translation of the original, un-refactored view looks like in "
401
+ text "Fortitude:"
402
+ }
403
+
404
+ fortitude '/app/views/admin/users/user_reporting.html.rb', <<-EOS
405
+ class Views::Admin::Users::UserReporting < Views::Base
406
+ needs :time_span_days
407
+ needs :blocked_users, :blocked_user_reasons
408
+ needs :reported_users, :reported_user_reasons
409
+ needs :top_reporting_users, :top_reporting_user_details
410
+
411
+ def content
412
+ h1 "Blocked users in the last \#{time_span_days} days: "
413
+
414
+ table(:class => "blocked_users user_list") {
415
+ thead {
416
+ tr {
417
+ th "User ID"
418
+ th "Username"
419
+ th "Email"
420
+ th "Last Login"
421
+ th "Blocked Because"
422
+ th "Blocked By"
423
+ }
424
+ }
425
+
426
+ tbody {
427
+ blocked_users.each do |blocked_user|
428
+ blocked_details = blocked_user_reasons[blocked_user.id]
429
+ tr {
430
+ td blocked_user.id
431
+ td blocked_user.username
432
+ td {
433
+ if blocked_user.email
434
+ text blocked_user.email
435
+ else
436
+ span "(none available)", :class => :no_email
437
+ end
438
+ }
439
+ td time_ago_in_words(blocked_user.last_login_at)
440
+ td blocked_details[:reason]
441
+ td blocked_details[:admin_user]
442
+ }
443
+ end
444
+ }
445
+ }
446
+
447
+ h1 "Reported Users in the last \#{time_span_days} days:"
448
+
449
+ table(:class => "reported_users user_list") {
450
+ thead {
451
+ tr {
452
+ th "User ID"
453
+ th "Username"
454
+ th "Email"
455
+ th "Last Login"
456
+ th "Reported For"
457
+ th "Report Text"
458
+ th "Reported By"
459
+ }
460
+ }
461
+
462
+ tbody {
463
+ reported_users.each do |reported_user|
464
+ report_details = reported_user_reasons[reported_user.id]
465
+ tr {
466
+ td reported_user.id
467
+ td reported_user.username
468
+ td {
469
+ if reported_user.email
470
+ text reported_user.email
471
+ else
472
+ span "(none available)", :class => :no_email
473
+ end
474
+ }
475
+ td time_ago_in_words(reported_user.last_login_at)
476
+ td report_details[:abuse_flag]
477
+ td report_details[:abuse_text]
478
+ td report_details[:abuse_reporter]
479
+ }
480
+ end
481
+ }
482
+ }
483
+
484
+ h1 "Most Reporting Users in the last \#{time_span_days} days:"
485
+
486
+ table(:class => "reporting_users user_list") {
487
+ thead {
488
+ tr {
489
+ th "User ID"
490
+ th "Username"
491
+ th "Email"
492
+ th "Last Login"
493
+ th "Total Reported Users"
494
+ th "Reported-For Breakdown"
495
+ }
496
+ }
497
+
498
+ tbody {
499
+ top_reporting_users.each do |reporting_user|
500
+ reporting_details = top_reporting_user_details[reporting_user.id]
501
+ tr {
502
+ td reporting_user.id
503
+ td reporting_user.username
504
+ td {
505
+ if reporting_user.email
506
+ text reporting_user.email
507
+ else
508
+ span "(none available)", :class => :no_email
509
+ end
510
+ }
511
+ td time_ago_in_words(reporting_user.last_login_at)
512
+ td(reporting_details[:flags].uniq { |f| f.reported_user_id }.length)
513
+ td {
514
+ grouped_by_flag = reporting_details[:flags].group_by { |f| f.flag_type }
515
+ grouped_by_flag.each do |flag_type, flags|
516
+ text "Flag"; strong flag_type; text ": \#{flags.length} total flag(s)"; br
517
+ end
518
+ }
519
+ }
520
+ end
521
+ }
522
+ }
523
+ end
524
+ end
525
+ EOS
526
+
527
+ p {
528
+ text "Although we really haven’t used the true advantages of Fortitude here, because we haven’t refactored "
529
+ text "anything yet, we can already see a few nice things about this:"
530
+ }
531
+
532
+ ul {
533
+ li {
534
+ strong "Parameter Declarations"; text ": The "; code "needs"; text " declarations at the top of the "
535
+ text "Fortitude class show you, at a glance, exactly what you have to pass into this view to render it. "
536
+ text "Every single Fortitude view or partial has this, and it’s guaranteed to be correct: if you don’t "
537
+ text "declare something as a "; code "need"; text ", you’ll be unable to render it in the view. (You can "
538
+ text "also supply defaults to make these optional, but we’re getting ahead of ourselves.)"
539
+ }
540
+ li {
541
+ strong "Logic vs. Rendering"; text ": Although a common convention in Rails code is to use braces "
542
+ text "for single-line blocks and "; code "do...end"; text " for multiline blocks, the convention is "
543
+ text "different in Fortitude code, and for good reason. In Fortitude code, we use braces to delimit actual "
544
+ text "HTML element contents, and "; code "do...end"; text " to delimit Ruby logic. This lets the eye scan "
545
+ text "the class easily and see exactly where there’s logic and where there’s HTML."
546
+ }
547
+ li {
548
+ strong "Cleanliness"; text ": This is, of course, a matter of personal preference, and not all people "
549
+ text "will feel the same way. However, to your author’s eye, the lack of all of the ERb "
550
+ code "<%= ... %>"; text " symbols cluttering up any place there’s logic in the view makes it quite a bit "
551
+ text "cleaner to read."
552
+ }
553
+ }
554
+
555
+ p {
556
+ text "However, again, the syntax isn’t really the point of Fortitude — the real point is the refactoring that "
557
+ text "the syntax allows you to do. Let’s take a look at that next."
558
+ }
559
+
560
+ h5 "A Beautifully-Refactored View"
561
+
562
+ p "Let’s just look at the fully-refactored view right off the bat and see what we’ve been able to do:"
563
+
564
+ fortitude '/app/views/admin/users/user_reporting.html.rb', <<-EOS
565
+ class Views::Admin::Users::UserReporting < Views::Base
566
+ needs :time_span_days
567
+ needs :blocked_users, :blocked_user_reasons
568
+ needs :reported_users, :reported_user_reasons
569
+ needs :top_reporting_users, :top_reporting_user_details
570
+
571
+ def content
572
+ blocked_users_table
573
+ reported_users_table
574
+ reporting_users_table
575
+ end
576
+
577
+ def blocked_users_table
578
+ users_table("Blocked Users", :blocked_users, [ "Blocked Because", "Blocked By" ], blocked_users) do |user|
579
+ blocked_details = blocked_user_reasons[user.id]
580
+ td blocked_details[:reason]
581
+ td blocked_details[:admin_user]
582
+ end
583
+ end
584
+
585
+ def reported_users_table
586
+ users_table("Reported Users", :reported_users, [ "Reported For", "Report Text", "Reported By" ], reported_users) do |user|
587
+ report_details = reported_user_reasons[user.id]
588
+ td report_details[:abuse_flag]
589
+ td report_details[:abuse_text]
590
+ td report_details[:abuse_reporter]
591
+ end
592
+ end
593
+
594
+ def reporting_users_table
595
+ users_table("Most Reporting Users", :reporting_users, [ "Total Reported Users", "Reported-For Breakdown" ], top_reporting_users) do |user|
596
+ reporting_details = top_reporting_user_details[user.id]
597
+
598
+ td(reporting_details[:flags].uniq { |f| f.reported_user_id }.length)
599
+ td {
600
+ grouped_by_flag = reporting_details[:flags].group_by { |f| f.flag_type }
601
+ grouped_by_flag.each do |flag_type, flags|
602
+ text "Flag"; strong flag_type; text ": \#{flags.length} total flag(s)"; br
603
+ end
604
+ }
605
+ end
606
+ end
607
+
608
+ def users_table(title_prefix, css_class, extra_columns, users)
609
+ h1 "\#{title_prefix} in the last \#{time_span_days} days: "
610
+
611
+ table(:class => "\#{css_class} user_list") {
612
+ thead {
613
+ tr {
614
+ ([ "User ID", "Username", "Email", "Last Login" ] + extra_columns).each do |header|
615
+ th header
616
+ end
617
+ }
618
+ }
619
+
620
+ tbody {
621
+ users.each do |user|
622
+ tr {
623
+ td user.id
624
+ td user.username
625
+ td {
626
+ if user.email
627
+ text user.email
628
+ else
629
+ span "(none available)", :class => :no_email
630
+ end
631
+ }
632
+ td time_ago_in_words(user.last_login_at)
633
+
634
+ yield user
635
+ }
636
+ end
637
+ }
638
+ }
639
+ end
640
+ end
641
+ EOS
642
+ end
643
+
644
+ def fortitude_benefits
645
+ p {
646
+ text "This is such an enormous improvement over either the original ERb view or the refactored ERb view "
647
+ text "that it’s hard to know where to begin. Let’s try enumerating the advantages, over and above the "
648
+ text "ones we mentioned above that are inherent to any Fortitude code:"
649
+ }
650
+
651
+ ul {
652
+ li {
653
+ strong "Readability of Overall Structure"; text ": The overall organization of this view is instantly "
654
+ text "obvious with one glance at the "; code "#content"; text " method (the entry point of every "
655
+ text "Fortitude view or partial): it contains a table of blocked users, a table of reported users, "
656
+ text "and a table of reporting users."
657
+ }
658
+ li {
659
+ strong "Readability of the Table"; text ": Our table is now in "; em "just one method"; text ". It’s "
660
+ text "completely clear what the structure of that table is, exactly how it works, and exactly where "
661
+ text "callers can pass in data or code that might change it."
662
+ }
663
+ li {
664
+ strong "Variation in Each Table"; text ": Similarly, it’s easy to see exactly how each table "
665
+ text "differs from the others: it’s exactly the code present in the method ("; em "e.g."; text ", "
666
+ code "#blocked_users_table"; text ", "; code "#reported_users_table"; text ", and so on)."
667
+ }
668
+ li {
669
+ strong "Lack of File Clutter"; text ": Our view is now "; em "one single file"; " again. The way we "
670
+ text "refactor our code is completely independent of the number of files we create for it, allowing us "
671
+ text "to keep like code together. If we have eight views in this controller, we’ll have just eight "
672
+ text "files in "; code "app/views/admin/users"; text ", and it’s crystal-clear exactly what each one is "
673
+ text "for. (Of course, you are more than welcome to factor out Fortitude code into separate files if you "
674
+ text "want — it just isn’t "; em "required"; text " in the same way it is with ERb, HAML, or other "
675
+ text "traditional templating engines.)"
676
+ }
677
+ li {
678
+ strong "Consistent"; text ": At no point do we build HTML by interpolating Ruby strings; it’s all just "
679
+ text "Fortitude code."
680
+ }
681
+ li {
682
+ strong { em "Safe" }; text ": We "; em "never"; text " have to call "; code "#h"; text " anywhere here, "
683
+ text "nor do we even have to think about HTML escaping. It’s all just handled for us, as it should be, "
684
+ text "with no risk of XSS attacks. (This doesn’t mean it’s "; em "impossible"; text " to create XSS "
685
+ text "vulnerabilities using Fortitude, of course — but it’s much, much more difficult, because you "
686
+ text "effectively never have to compose HTML using Ruby strings.)"
687
+ }
688
+ li {
689
+ strong "Indentation (of Source)"; text ": It’s obvious how the source should be indented, there’s only "
690
+ text "one reasonable way to do it, and any editor "
691
+ text "capable of indenting Ruby properly will automatically indent Fortitude properly."
692
+ }
693
+ li {
694
+ p {
695
+ strong "Indentation (of Output)"; text ": Because Fortitude understands the structure of your code, "
696
+ text "it "; em "always"; text " produces perfectly-formatted output. Here’s what the equivalent to the "
697
+ text "poorly-formatted HTML output from ERb above looks like if it’s coming from Fortitude:"
698
+ }
699
+
700
+ html_source <<-EOS
701
+ <html>
702
+ <head>
703
+ ...
704
+ </head>
705
+ <body>
706
+ ...
707
+ <h1>Most Reporting Users in the last 30 days:</h1>
708
+
709
+ <table class="reporting_users user_list">
710
+ <thead>
711
+ <tr>
712
+ <th>User ID</th>
713
+ <th>Username</th>
714
+ <th>Email</th>
715
+ <th>Last Login</th>
716
+ <th>Total Reported Users</th>
717
+ <th>Reported-For Breakdown</th>
718
+ </tr>
719
+ </thead>
720
+ <tbody>
721
+ <tr>
722
+ <td>12345</td>
723
+ <td>jane_doe_1</td>
724
+ <td>jdoe@example.com</td>
725
+ <td>about 2 hours ago</td>
726
+ <td>17</td>
727
+ <td>
728
+ Flag <strong>Spam</strong>: 27 total flag(s)<br>
729
+ Flag <strong>Personal Attack</strong>: 17 total flag(s)<br>
730
+ Flag <strong>Off-Topic</strong>: 9 total flag(s)<br>
731
+ </td>
732
+ </tr>
733
+ </tbody>
734
+ </table>
735
+ ...
736
+ </body>
737
+ </html>
738
+ EOS
739
+
740
+ p {
741
+ text "(In production, Fortitude will, by default, emit this code with no whitespace at all, reducing your "
742
+ text "page weight significantly — also something that ERb cannot do.)"
743
+ }
744
+ }
745
+ }
746
+
747
+ p {
748
+ text "Altogether, Fortitude has helped us refactor this view in a way we simply couldn’t have using any "
749
+ text "traditional templating engine. "
750
+ }
751
+
752
+ p {
753
+ text "One of the reasons the complex ERb refactoring above may seem unfamiliar to many readers "
754
+ text "is simply that the refactoring ends up being so complex that most teams simply don’t do it — they leave "
755
+ text "this view un-refactored, like the original example, since it actually is cleaner in many ways than "
756
+ text "the refactored version. And, like before, this doesn’t mean that the original is actually particularly "
757
+ em "good"; text "; it contains an awful lot of duplication — it’s just that the tools traditional templating "
758
+ text "engines give you are insufficient to refactor this code well."
759
+ }
760
+
761
+ p {
762
+ text " By providing you all the power of Ruby "
763
+ text "to refactor your code, Fortitude allows you to refactor this code into something that’s clean, clear, "
764
+ text "and actually a joy to work with."
765
+ }
766
+
767
+ p {
768
+ text "Next, we’ll take a look at how Fortitude lets you use inheritance to beautifully and effectively factor "
769
+ text "a set of views that all have a lot in common, but differ in their exact details."
770
+ }
771
+ end
772
+ end
773
+ end
774
+ end