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,564 @@
1
+ require 'source/why/example_page'
2
+
3
+ module Views
4
+ module Why
5
+ class CommonalityAndInheritance < Views::Why::ExamplePage
6
+ def example_intro
7
+ p {
8
+ text "In our last example, we saw how Fortitude allows you to share code among methods in the same "
9
+ text "view and how it lets you pass blocks to allow very flexible reuse and customization of that code."
10
+ }
11
+
12
+ p {
13
+ text "Here, we’ll explore how Fortitude allows you to use Ruby’s inheritance mechanism to easily factor "
14
+ text "out commonality among several related views. Using inheritance is one of the most powerful "
15
+ text "features of Fortitude."
16
+ }
17
+ end
18
+
19
+ def example_description
20
+ p {
21
+ text "Imagine we’re building the initial phases of a social network, and the time has come to create our "
22
+ text "main social feed. This feed is going to list various events: friends who have accepted our friend "
23
+ text "request, new posts in groups we follow, and even new features we’ve introduced to the site."
24
+ }
25
+
26
+ p {
27
+ text "This presents an interesting challenge for building views. The views for these feed items are likely "
28
+ text "to have quite a bit in common — yet almost all of it is going to be customized at "; em "some"
29
+ text " point. Still, let’s start simply, with just the three feed items mentioned above."
30
+ }
31
+
32
+ p {
33
+ text "First, the “accepted friend request” view:"
34
+ }
35
+
36
+ erb 'app/views/feed/items/accepted_friend_request.html.erb', <<-EOS
37
+ <div class="feed_item feed_item_accepted_friend_request" id="feed_item_<%= item.id %>">
38
+ <div class="feed_item_header">
39
+ <%= image_tag 'new_friend.png', :class => 'feed_item_image' %>
40
+ <h5><strong><%= item.accepting_user.first_name %></strong> accepted your friend request!</h5>
41
+ </div>
42
+
43
+ <div class="feed_item_body">
44
+ <p>
45
+ <a href="<%= profile_url(item.accepting_user) %>">
46
+ <img src="<%= item.accepting_user.profile_image %>">
47
+ <strong><%= item.accepting_user.full_name %></strong> is now your friend!
48
+ </a>
49
+ </p>
50
+ <p>You should say hello! It’s only <%= ((Time.now - item.accepting_user.date_of_birth) / 1.day) %> days until their birthday!</p>
51
+ </div>
52
+
53
+ <div class="feed_item_footer">Posted at <%= item.created_at %></div>
54
+ </div>
55
+ EOS
56
+
57
+ p {
58
+ text "Now, the “new posts in groups you follow” view:"
59
+ }
60
+
61
+ erb 'app/views/feed/items/new_group_posts.html.erb', <<-EOS
62
+ <div class="feed_item feed_item_new_posts" id="feed_item_<%= item.id %>">
63
+ <div class="feed_item_header">
64
+ <%= image_tag 'new_post.png', :class => 'feed_item_image' %>
65
+ <h5>New posts in <%= item.new_groups.length %> groups!</h5>
66
+ </div>
67
+
68
+ <div class="feed_item_body">
69
+ <p>There have been new posts in the following groups:
70
+ <ul>
71
+ <% item.new_groups.each do |group| %>
72
+ <li><a href="<%= group_url(group) %>"><strong><%= group.title %></strong> (<%= group.new_posts %> new posts)</a></li>
73
+ <% end %>
74
+ </ul>
75
+ </p>
76
+ </div>
77
+
78
+ <div class="feed_item_footer">Posted at <%= item.created_at %></div>
79
+ </div>
80
+ EOS
81
+
82
+ p {
83
+ text "And, finally, the “new feature” view:"
84
+ }
85
+
86
+ erb 'app/views/feed/items/new_feature.html.erb', <<-EOS
87
+ <div class="feed_item feed_item_new_feature" item="feed_item_<%= item.id %>">
88
+ <div class="feed_item_header">
89
+ <%= image_tag 'features/custom_groups.png', :class => 'feed_item_image' %>
90
+ <h3>New features have launched!</h3>
91
+ </div>
92
+
93
+ <div class="feed_item_body">
94
+ <h5>We’ve been improving the site for your benefit!</h5>
95
+ <p>You can now create custom groups! We know you’ve all been asking for this feature for a while,
96
+ and it’s now ready for your use! <a href="<%= custom_groups_url %>">Click here</a> to create one!</p>
97
+ </div>
98
+
99
+ <div class="feed_item_footer">Posted at <%= item.created_at %></div>
100
+ </div>
101
+ EOS
102
+
103
+ p {
104
+ text "(Please note that we’ve simplified a lot of these views for our example. For example, you’d never "
105
+ text "want to show a user a raw Ruby "; code "Time"; text " value as we’ve done here.)"
106
+ }
107
+
108
+ p {
109
+ text "We can easily notice a lot of commonality in the views above:"
110
+ }
111
+
112
+ ul {
113
+ li {
114
+ strong "Container"; text ": All the views have a similar surrounding "; code "div"; text " that contains "
115
+ text "a per-view "; code "class"; text " and a per-feed-item "; code "id"; text "."
116
+ }
117
+ li {
118
+ strong "Header"; text ": Each view has a header that’s surrounded by a "; code "div"; text " that contains "
119
+ text "an image and some per-view HTML."
120
+ }
121
+ li {
122
+ strong "Body"; text ": Each view has a body that’s surrounded by a "; code "div"; text " that contains "
123
+ text "arbitrary HTML for that view — the real contents of our feed item."
124
+ }
125
+ li {
126
+ strong "Footer"; text ": Each view also has a footer that’s identical for all views."
127
+ }
128
+ }
129
+ end
130
+
131
+ def using_standard_engines
132
+ p {
133
+ text "Using traditional templating engines, what can we do here? Well, have the same tools at our disposal "
134
+ text "as always: partials and helpers. We can factor out commonality using these tools, "
135
+ text "potentially using "; code "capture"; text " to allow us to write HTML we pass into a view using "
136
+ text "ERb instead of Ruby string interpolation."
137
+ }
138
+
139
+ p {
140
+ text "Let’s first see what this looks like without using "; code "capture"; text ", and then we’ll see how "
141
+ text "or if using "; code "capture"; text " improves things."
142
+ }
143
+
144
+ h5 "Using Partials and Helpers"
145
+
146
+ p {
147
+ text "If we do our very best with this, here’s what we can end up with. We begin with a partial that "
148
+ text "starts the header of our view:"
149
+ }
150
+
151
+ erb 'app/views/feed/items/_feed_item_header.html.erb', <<-EOS
152
+ <div class="feed_item feed_item_<%= outer_css_class %>" id="feed_item_<%= item.id %>">
153
+ <div class="feed_item_header">
154
+ <%= image_tag header_icon_image, :class => 'feed_item_image' %>
155
+ EOS
156
+
157
+ p {
158
+ text "We have a really awkward situation going on between the header and the body, where we need to "
159
+ text "close the "; code "div"; text " we opened, then open another one. This seems a bit heavyweight for "
160
+ text "a partial, so let’s use a helper:"
161
+ }
162
+
163
+ ruby 'app/helpers/feed_helper.rb', <<-EOS
164
+ def feed_item_header_closer
165
+ "</div><div class=\"feed_item_body\">"
166
+ end
167
+ EOS
168
+
169
+ p {
170
+ text "Next, we have the body of the feed item, which we’ll leave to the individual view itself. Finally, "
171
+ text "we have the rest of the item template:"
172
+ }
173
+
174
+ erb 'app/views/feed/items/_feed_item_footer.html.erb', <<-EOS
175
+ </div>
176
+
177
+ <div class="feed_item_footer">Posted at <%= item.created_at %></div>
178
+ </div>
179
+ EOS
180
+
181
+ p {
182
+ text "Given these partials and helpers, what do our views now look like? First, let’s look at the "
183
+ text "“accepted friend request” view:"
184
+ }
185
+
186
+ erb 'app/views/feed/items/accepted_friend_request.html.erb', <<-EOS
187
+ <%= render :partial => 'feed_item_header',
188
+ :locals => { :outer_css_class => 'accepted_friend_request',
189
+ :item => item, :header_icon_image => 'new_friend.png' } %>
190
+ <h5><strong><%= item.accepting_user.first_name %></strong> accepted your friend request!</h5>
191
+ <%= feed_item_header_closer %>
192
+ <p>
193
+ <a href="<%= profile_url(item.accepting_user) %>">
194
+ <img src="<%= item.accepting_user.profile_image %>">
195
+ <strong><%= item.accepting_user.full_name %></strong> is now your friend!
196
+ </a>
197
+ </p>
198
+ <p>You should say hello! It’s only <%= ((Time.now - item.accepting_user.date_of_birth) / 1.day) %> days until their birthday!</p>
199
+ <%= render :partial => 'feed_item_footer', :locals => { :item => item } %>
200
+ EOS
201
+
202
+ p {
203
+ text "This is our “new posts in groups you follow” view:"
204
+ }
205
+
206
+ erb 'app/views/feed/items/new_group_posts.html.erb', <<-EOS
207
+ <%= render :partial => 'feed_item_header',
208
+ :locals => { :outer_css_class => 'new_posts',
209
+ :item => item, :header_icon_image => 'new_post.png' } %>
210
+ <h5>New posts in <%= item.new_groups.length %> groups!</h5>
211
+ <%= feed_item_header_closer %>
212
+ <p>There have been new posts in the following groups:
213
+ <ul>
214
+ <% item.new_groups.each do |group| %>
215
+ <li><a href="<%= group_url(group) %>"><strong><%= group.title %></strong> (<%= group.new_posts %> new posts)</a></li>
216
+ <% end %>
217
+ </ul>
218
+ </p>
219
+ <%= render :partial => 'feed_item_footer', :locals => { :item => item } %>
220
+ EOS
221
+
222
+ p {
223
+ text "And this is our “new feature” view:"
224
+ }
225
+
226
+ erb 'app/views/feed/items/new_feature.html.erb', <<-EOS
227
+ <%= render :partial => 'feed_item_header',
228
+ :locals => { :outer_css_class => 'new_feature',
229
+ :item => item, :header_icon_image => 'custom_groups.png' } %>
230
+ <h3>New features have launched!</h3>
231
+ <%= feed_item_header_closer %>
232
+ <h5>We’ve been improving the site for your benefit!</h5>
233
+ <p>You can now create custom groups! We know you’ve all been asking for this feature for a while,
234
+ and it’s now ready for your use! <a href="<%= custom_groups_url %>">Click here</a> to create one!</p>
235
+ <%= render :partial => 'feed_item_footer', :locals => { :item => item } %>
236
+ EOS
237
+
238
+ p {
239
+ text "Is this an improvement? Yes — well, "; em "maybe"; text ". We’ve successfully removed some of the commonality, "
240
+ text "although at the price of introducing a lot of verbosity. We’ve also added some serious weirdness, "
241
+ text "like "; code "div"; text "s that get opened in one partial and closed in another — all of which is, "
242
+ text "of course, added opportunity for error. All in all, it certainly isn’t clean or clear; once again, "
243
+ text "extracting the commonality has come at a serious cost in comprehensibility and readability."
244
+ }
245
+
246
+ p {
247
+ text "Let’s see if using "; code "capture"; text " makes this much better."
248
+ }
249
+
250
+ h5 {
251
+ text "Using "; code "capture"
252
+ }
253
+
254
+ p {
255
+ text "The "; code "capture"; text " method isn’t particularly common or elegant, but it can be useful in "
256
+ text "situations like this to pass HTML into other views. If we use this, let’s see what our shared partial "
257
+ text "looks like first:"
258
+ }
259
+
260
+ erb 'app/views/feed/items/_feed_item_base.html.erb', <<-EOS
261
+ <div class="feed_item feed_item_<%= outer_css_class %>" id="feed_item_<%= item.id %>">
262
+ <div class="feed_item_header">
263
+ <%= image_tag header_icon_image, :class => 'feed_item_image' %>
264
+ <%= header_html %>
265
+ </div>
266
+
267
+ <div class="feed_item_body">
268
+ <%= body_html %>
269
+ </div>
270
+
271
+ <div class="feed_item_footer">Posted at <%= item.created_at %></div>
272
+ </div>
273
+ EOS
274
+
275
+ p {
276
+ text "This certainly looks a lot better than our previous solution of two partials and a helper. It’s simpler, "
277
+ text "and keeps the overall structure of the partial much clearer."
278
+ }
279
+
280
+ p {
281
+ text "Given this, what do our actual feed items look like now?"
282
+ }
283
+
284
+ erb 'app/views/feed/items/accepted_friend_request.html.erb', <<-EOS
285
+ <% header_html = capture do %>
286
+ <h5><strong><%= item.accepting_user.first_name %></strong> accepted your friend request!</h5>
287
+ <% end %>
288
+ <% body_html = capture do %>
289
+ <p>
290
+ <a href="<%= profile_url(item.accepting_user) %>">
291
+ <img src="<%= item.accepting_user.profile_image %>">
292
+ <strong><%= item.accepting_user.full_name %></strong> is now your friend!
293
+ </a>
294
+ </p>
295
+ <p>You should say hello! It’s only <%= ((Time.now - item.accepting_user.date_of_birth) / 1.day) %> days until their birthday!</p>
296
+ <% end %>
297
+ <%= render :partial => 'feed_item_base', :locals => {
298
+ :outer_css_class => 'accepted_friend_request',
299
+ :item => item,
300
+ :header_icon_image => 'new_friend.png',
301
+ :header_html => header_html,
302
+ :body_html => body_html
303
+ } %>
304
+ EOS
305
+
306
+ erb 'app/views/feed/items/new_group_posts.html.erb', <<-EOS
307
+ <% header_html = capture do %>
308
+ <h5>New posts in <%= item.new_groups.length %> groups!</h5>
309
+ <% end %>
310
+ <% body_html = capture do %>
311
+ <p>There have been new posts in the following groups:
312
+ <ul>
313
+ <% item.new_groups.each do |group| %>
314
+ <li><a href="<%= group_url(group) %>"><strong><%= group.title %></strong> (<%= group.new_posts %> new posts)</a></li>
315
+ <% end %>
316
+ </ul>
317
+ </p>
318
+ <% end %>
319
+ <%= render :partial => 'feed_item_base', :locals => {
320
+ :outer_css_class => 'new_posts',
321
+ :item => item,
322
+ :header_icon_image => 'new_post.png',
323
+ :header_html => header_html,
324
+ :body_html => body_html
325
+ } %>
326
+ EOS
327
+
328
+ erb 'app/views/feed/items/new_feature.html.erb', <<-EOS
329
+ <% header_html = capture do %>
330
+ <h3>New features have launched!</h3>
331
+ <% end %>
332
+ <% body_html = capture do %>
333
+ <h5>We’ve been improving the site for your benefit!</h5>
334
+ <p>You can now create custom groups! We know you’ve all been asking for this feature for a while,
335
+ and it’s now ready for your use! <a href="<%= custom_groups_url %>">Click here</a> to create one!</p>
336
+ <% end %>
337
+ <%= render :partial => 'feed_item_base', :locals => {
338
+ :outer_css_class => 'new_feature',
339
+ :item => item,
340
+ :header_icon_image => 'custom_groups.png',
341
+ :header_html => header_html,
342
+ :body_html => body_html
343
+ } %>
344
+ EOS
345
+
346
+ p {
347
+ text "Alas, although we have a much nicer shared partial this time around, the views have become "
348
+ text "really verbose and quite messy. The use of "; code "capture"; text " clutters up the code and causes a lot of "
349
+ text "action-at-a-distance, and the shared partial takes enough inputs at this point that just calling it "
350
+ text "requires passing many variables that create further visual clutter."
351
+ }
352
+ end
353
+
354
+ def standard_engine_issues
355
+ p {
356
+ text "Hopefully it’s not an overstatement to say, simply: "; em "ugh"; text ". We have a lot of commonality "
357
+ text "in the views we started with that it seems like we "; em "ought"; text " to be able to factor out well, "
358
+ text "and yet both major options we have available don’t do a great job. The result feels messy and inelegant."
359
+ }
360
+
361
+ p {
362
+ text "Let’s dive in deeper and look at some of the specific issues with the results:"
363
+ }
364
+
365
+ ul {
366
+ li {
367
+ strong "Disappearing Structure"; text ": In both refactorings, the overall structure of our views has "
368
+ text "effectively vanished — when using partials and helpers, from the shared code; when using "
369
+ code "capture"; text ", from the callers. This makes the code a lot harder to read, and, more importantly, "
370
+ text "much harder to reason about: it’s all too easy to omit a closing tag, add an extra opening tag, or "
371
+ text "just have a really hard time figuring out how and where to change something. If you wanted to create "
372
+ text "a structure prone to generating lots of bugs, this is almost exactly what you’d do. "
373
+ }
374
+ li {
375
+ strong "Verbosity"; text ": Our refactored code is almost as long as the code it replaces in both cases, "
376
+ text "and a "; em "lot"; text " harder to read. Is this really an improvement?"
377
+ }
378
+ li {
379
+ strong "Lack of Flexibility"; text ": Now consider in both cases what it would take to, for example, be "
380
+ text "able to customize the footer — which is common to all views now, but which you can certainly imagine "
381
+ text "being customized at some point in the near future. With partials and helpers, you suddenly have to "
382
+ text "split the shared code into "; em "another"; text " partial; with "; code "capture"; text ", it’s "
383
+ text "easier, but also involves a gross defaulting of HTML in the shared view and adds more verbosity "
384
+ text "any place it’s customized."
385
+ }
386
+ }
387
+ end
388
+
389
+ def using_fortitude
390
+ p {
391
+ text "OK. So, how can Fortitude help? Because each Fortitude view or partial is simply a Ruby class, we can "
392
+ text "define a base view class here outlining the general structure, and with simple method calls for "
393
+ text "overridden or missing content:"
394
+ }
395
+
396
+ fortitude 'app/views/feed/items/feed_item.html.rb', <<-EOS
397
+ class Views::Feed::Items::FeedItem < Views::Base
398
+ needs :item
399
+
400
+ def content
401
+ div(:class => [ "feed_item", "feed_item_\#{outer_css_class}" ], :id => "feed_item_\#{item.id}") {
402
+ div(:class => 'feed_item_header') {
403
+ image_tag header_icon_image, :class => 'feed_item_image'
404
+ header_content
405
+ }
406
+
407
+ div(:class => 'feed_item_body') { body_content }
408
+
409
+ div("Posted at \#{item.created_at}", :class => 'feed_item_footer')
410
+ }
411
+ end
412
+
413
+ def header_content
414
+ raise "You must implement this method in \#{self.class.name}"
415
+ end
416
+
417
+ def body_content
418
+ raise "You must implement this method in \#{self.class.name}"
419
+ end
420
+
421
+ def outer_css_class
422
+ self.class.name.demodulize.underscore
423
+ end
424
+ end
425
+ EOS
426
+
427
+ p {
428
+ text "Our base class clearly defines the overall structure of all feed item views, and that it "
429
+ code "need"; text "s an "; code "item"; text " in order to be rendered. The "; code "header_content"
430
+ text " and "; code "body_content"; text " methods are left unimplemented, to be provided by subclasses."
431
+ }
432
+
433
+ p {
434
+ text "We’ve also used a neat trick: because this is Ruby code, we’ve used a little bit of metaprogramming "
435
+ text "convention-over-configuration to calculate the "; code "outer_css_class"; text ", instead of having "
436
+ text "to pass it in. This both makes callers cleaner and eliminates a source of inconsistency or error, "
437
+ text "because "; code "outer_css_class"; text " can no longer differ from the name of the view itself "
438
+ text "at all. And yet, exactly because it’s a separate method, if a subclass needed to override this class, "
439
+ text "it could, extremely easily."
440
+ }
441
+
442
+ p {
443
+ text "Given this, what do these subclasses look like? We’ll start with the “accepted friend request” view, "
444
+ text "which turns out to be the most complex one:"
445
+ }
446
+
447
+ fortitude 'app/views/feed/items/accepted_friend_request.html.rb', <<-EOS
448
+ class Views::Feed::Items::AcceptedFriendRequest < FeedItem
449
+ def header_content
450
+ h5 {
451
+ strong item.accepting_user.first_name; text " accepted your friend request!"
452
+ }
453
+ end
454
+
455
+ def body_content
456
+ p {
457
+ a(:href => profile_url(item.accepting_user)) {
458
+ img(:src => item.accepting_user.profile_image)
459
+ strong item.accepting_user.full_name; text " is now your friend!"
460
+ }
461
+ }
462
+ p "You should say hello! It’s only \#{days_until_birthday} days until their birthday!"
463
+ end
464
+
465
+ private
466
+ def days_until_birthday
467
+ (Time.now - item.accepting_user.date_of_birth) / 1.day
468
+ end
469
+ end
470
+ EOS
471
+
472
+ p {
473
+ text "From the view above, it’s hopefully immediately obvious what content "; code "AcceptedFriendRequest"
474
+ text " supplies (its two public methods), and where it goes (since they have clear names). Further, we’ve "
475
+ text "used Fortitude’s ability to add “helper methods” to just a single class to easily factor out the "
476
+ code "days_until_birthday"; text " method."
477
+ }
478
+
479
+ p {
480
+ text "Now, let’s look at the “new group posts” view:"
481
+ }
482
+
483
+ fortitude 'app/views/feed/items/new_group_posts.html.rb', <<-EOS
484
+ class Views::Feed::Items::NewGroupPosts < FeedItem
485
+ def header_content
486
+ h5 "New posts in \#{item.new_groups.length} groups!"
487
+ end
488
+
489
+ def body_content
490
+ p {
491
+ text "There have been new posts in the following groups:"
492
+ ul {
493
+ item.new_groups.each do |group|
494
+ li {
495
+ a(:href => group_url(group)) {
496
+ strong group.title; text " (\#{group.new_posts} new posts)"
497
+ }
498
+ }
499
+ end
500
+ }
501
+ }
502
+ end
503
+ end
504
+ EOS
505
+
506
+ p {
507
+ text "And the “new feature” view:"
508
+ }
509
+
510
+ fortitude 'app/views/feed/items/new_feature.html.rb', <<-EOS
511
+ class Views::Feed::Items::NewFeature < FeedItem
512
+ def header_content
513
+ h3 "New features have launched!"
514
+ end
515
+
516
+ def body_content
517
+ h5 "We’ve been improving the site for your benefit!"
518
+ p {
519
+ text "You can now create custom groups! We know you’ve all been asking for this feature for a while, "
520
+ text "and it’s now ready for your use! "; a("Click here", :href => custom_groups_url); text " to create one!"
521
+ }
522
+ end
523
+ end
524
+ EOS
525
+
526
+ p {
527
+ text "At this point, these views almost seem positively boring — they each contain exactly what you’d "
528
+ text "expect them to contain, with no surprises at all. Given that truly well-factored code often seems "
529
+ text "boring because it’s so straightforward, we’ll take that as a good thing."
530
+ }
531
+ end
532
+
533
+ def fortitude_benefits
534
+ p {
535
+ text "What have we done here? Put simply, we’ve leveraged Ruby’s built-in inheritance mechanism — "
536
+ text "a mechanism every single Ruby programmer on your team already knows extremely well — to build views "
537
+ text "that are far more comprehensible and maintainable than anything we could possibly have achieved "
538
+ text "with traditional templating engines."
539
+ }
540
+
541
+ p {
542
+ text "In many ways, the key behind Fortitude is exactly that it "; em "doesn’t"; text " invent some "
543
+ text "brand-new paradigm for writing your view code. Instead, it allows you to leverage "
544
+ text "all the techniques you already have for factoring the rest of your application, and simply lets you "
545
+ text "apply them to your views."
546
+ }
547
+
548
+ p {
549
+ text "Imagine, for example, that you finally "; em "do"; text " need to customize that shared footer "
550
+ text "in at least one feed-item view. What do you do? Simple: extract it into a method in the base class, "
551
+ text "and override it in whichever view you need to customize it in. You can even easily "
552
+ text "call "; code "super"; text " (or not), either before, after, or in the middle of the overridden "
553
+ text "method, and it behaves exactly how you’d expect, inserting the default footer contents at exactly "
554
+ text "that point in your view."
555
+ }
556
+
557
+ p {
558
+ text "Next, we’ll see how using Fortitude classes can allow us to create a contextual, elegant "
559
+ text "“mini-language” for building complex views very easily."
560
+ }
561
+ end
562
+ end
563
+ end
564
+ end