ramaze 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. data/Rakefile +42 -0
  2. data/doc/allison/LICENSE +184 -0
  3. data/doc/allison/README +37 -0
  4. data/doc/allison/allison.css +300 -0
  5. data/doc/allison/allison.gif +0 -0
  6. data/doc/allison/allison.js +307 -0
  7. data/doc/allison/allison.rb +287 -0
  8. data/doc/allison/cache/BODY +588 -0
  9. data/doc/allison/cache/CLASS_INDEX +4 -0
  10. data/doc/allison/cache/CLASS_PAGE +1 -0
  11. data/doc/allison/cache/FILE_INDEX +4 -0
  12. data/doc/allison/cache/FILE_PAGE +1 -0
  13. data/doc/allison/cache/FONTS +1 -0
  14. data/doc/allison/cache/FR_INDEX_BODY +1 -0
  15. data/doc/allison/cache/IMGPATH +1 -0
  16. data/doc/allison/cache/INDEX +1 -0
  17. data/doc/allison/cache/JAVASCRIPT +307 -0
  18. data/doc/allison/cache/METHOD_INDEX +4 -0
  19. data/doc/allison/cache/METHOD_LIST +1 -0
  20. data/doc/allison/cache/SRC_PAGE +1 -0
  21. data/doc/allison/cache/STYLE +322 -0
  22. data/doc/allison/cache/URL +1 -0
  23. data/doc/readme_chunks/principles.txt +33 -18
  24. data/doc/tutorial/todolist.html +599 -0
  25. data/doc/tutorial/todolist.txt +230 -230
  26. data/examples/identity.rb +21 -0
  27. data/examples/nitro_form.rb +22 -0
  28. data/lib/ramaze/controller.rb +1 -1
  29. data/lib/ramaze/dispatcher.rb +10 -4
  30. data/lib/ramaze/helper/{openid.rb → identity.rb} +15 -6
  31. data/lib/ramaze/helper/nitroform.rb +10 -0
  32. data/lib/ramaze/helper/stack.rb +1 -1
  33. data/lib/ramaze/inform.rb +18 -8
  34. data/lib/ramaze/snippets/kernel/aquire.rb +3 -3
  35. data/lib/ramaze/snippets/object/traits.rb +1 -1
  36. data/lib/ramaze/snippets/ramaze/caller_info.rb +17 -1
  37. data/lib/ramaze/snippets/ramaze/caller_lines.rb +1 -1
  38. data/lib/ramaze/store/yaml.rb +10 -1
  39. data/lib/ramaze/template/ezamar.rb +10 -5
  40. data/lib/ramaze/trinity/request.rb +12 -2
  41. data/lib/ramaze/version.rb +1 -1
  42. data/lib/ramaze.rb +5 -3
  43. data/spec/public/error404.xhtml +1 -0
  44. data/spec/spec_all.rb +21 -19
  45. data/spec/spec_helper.rb +1 -1
  46. data/spec/tc_error.rb +18 -4
  47. data/spec/tc_helper_cache.rb +1 -1
  48. data/spec/tc_helper_flash.rb +1 -2
  49. metadata +32 -4
@@ -0,0 +1 @@
1
+ http://blog.evanweaver.com/articles/2006/06/02/allison
@@ -1,41 +1,56 @@
1
1
  There are some basic principles that Ramaze tries to follow:
2
2
 
3
- * Test everything
3
+ * KISS (Keep It Super Simple)
4
4
 
5
- What use is a wonderful application if it doesn't work?
5
+ Ramaze doesn't introduce any major change of paradigm for everyone familiar
6
+ with Ruby and the basics of Web-development.
6
7
 
8
+ * POLS (Principle Of Least Surprise)
7
9
 
8
- * Document everything
9
-
10
- Documentation is the glue between the code and the programmers brain
10
+ Ramaze tries to be intuitive and easy to learn. Most functionality is built in
11
+ a way to help, not to obfuscate or confuse.
11
12
 
13
+ * Modular design
12
14
 
13
- * Keep It Super Simple (KISS)
15
+ Use what you want and how you want it.
14
16
 
15
- Most things should be understandable after reading them once
17
+ Through Ruby Ramaze provides one of the most powerful programming-languages
18
+ available, giving you full control over your system.
16
19
 
20
+ Even the most essential parts of Ramaze can easily be replaced and/or modified
21
+ without losing the advantage of the whole framework.
17
22
 
18
- * Principle Of Least Surprise (POLS)
23
+ * Minimal dependencies
19
24
 
20
- Going the way of ruby
25
+ Nothing besides Ruby is required for the basic features.
21
26
 
27
+ Of course you can take advantage of several wonderful libraries, but Ramaze is
28
+ built in a way to be run on any basic setup.
22
29
 
23
- * Modular design
30
+ * Documentation
24
31
 
25
- Making it as simple as possible to extract parts
32
+ Document everything, classes, modules, methods, configuration...
26
33
 
34
+ Through 100% documentation Ramaze gives the developer easy and solid
35
+ understanding of the underlying concepts and functionality.
27
36
 
28
- * Minimal dependencies
37
+ * Open development
29
38
 
30
- In case a dependency is not met use a simple fall-back instead
39
+ Everyone is welcome to contribute to Ramaze in the easiest way possible. The
40
+ repository is open for patches passing the Test-suite.
31
41
 
42
+ * Examples
32
43
 
33
- * Provide as many examples as possible
44
+ Everyone learns different, some only read the source, others browse
45
+ documentation, but everyone loves examples for a quick and painless start.
34
46
 
35
- Examples are a superior way of getting a quick start into everything
47
+ Ramaze addresses this need and offers a wide variety of examples of usage,
48
+ basic functionality, project-layout and more advanced applications.
36
49
 
50
+ * Fully BDD (Behaviour Driven Design)
37
51
 
38
- * Open development
52
+ Ramaze has a very complete set of so-called specifications built by RSpec.
53
+ These specs define the way Ramaze has to behave.
39
54
 
40
- I happily accept all patches or feature-requests that you may have,
41
- as long as they comply with these principles
55
+ The specs are checked every time a new patch is pushed into the repository,
56
+ deciding whether the changes the patch applies are valid and don't break the framework.
@@ -0,0 +1,599 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html>
4
+ <head>
5
+ <title>Ramaze Tutorial: Todolist</title>
6
+ <style>
7
+ body {
8
+ background: #eee;
9
+ }
10
+ code {
11
+ background: #ddd;
12
+ }
13
+ pre code {
14
+ background: #ddd;
15
+ width: 70%;
16
+ display: block;
17
+ margin: 1em;
18
+ padding: 0.7em;
19
+ overflow: auto;
20
+ }
21
+ </style>
22
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
23
+ </head>
24
+ <body>
25
+ <h1>To-do List Tutorial</h1>
26
+
27
+ <p>Welcome to our official tutorial, the mandatory to-do list.
28
+ I'm writing this while doing the steps to assure it will work for you.</p>
29
+
30
+ <p>The tutorial assumes that you have Ramaze installed already. The easiest way to
31
+ do that is <code>gem install ramaze</code>, for other ways of installation please see the
32
+ documentation at http://ramaze.rubyforge.org</p>
33
+
34
+ <p>Should you encounter any problems while doing this tutorial, this might either
35
+ be because Ramaze changed (which happens very often while it is still young)
36
+ or I actually made some mistake while writing it.</p>
37
+
38
+ <p>In either case it would make me (and all other poor fellows who happen to try
39
+ this tutorial) very happy if you could spare some time and report the issue
40
+ either on the Bug tracker at http://rubyforge.org/projects/ramaze or just
41
+ drop by on IRC ( irc.freenode.org channel: #ramaze ).</p>
42
+
43
+ <p>There is also a Mailing list available where you can keep yourself updated on
44
+ what is going on with little effort, it is also located on the project-page at
45
+ RubyForge.</p>
46
+
47
+ <p>Thanks in advance.
48
+ The author of the tutorial, Michael 'manveru' Fellinger</p>
49
+
50
+ <h2>First Step, Create</h2>
51
+
52
+ <p>We are using <code>ramaze --create todolist</code> to create a new application.
53
+ Ramaze will then create the directory and fill it with a skeleton of a quite
54
+ sophisticated hello-world example out of which we will create the actual
55
+ to-do list.</p>
56
+
57
+ <p>So run:</p>
58
+
59
+ <pre><code>$ ramaze --create todolist
60
+ </code></pre>
61
+
62
+ <p>done.</p>
63
+
64
+ <h2>Second Step, M, like Model</h2>
65
+
66
+ <p>Ramaze comes at the moment only with a simple wrapper of the YAML::Store.
67
+ So we are going to base this on the tools available, you can just do the same
68
+ with your ORM or database of choice.</p>
69
+
70
+ <p>So first, edit the <code>src/model.rb</code>, it is filled with the definition of a simple
71
+ YAML::Store already, so we are just gonna modify it a bit to use our wrapper.</p>
72
+
73
+ <p>Instead of 'yaml/store' use:</p>
74
+
75
+ <pre><code>require 'ramaze/store/default'
76
+ </code></pre>
77
+
78
+ <p>And further:</p>
79
+
80
+ <pre><code>TodoList = Store::Default.new 'todolist.yaml'
81
+ </code></pre>
82
+
83
+ <p>To have a base to start off of, let's add some items as well.</p>
84
+
85
+ <pre><code>{
86
+ 'Laundry' =&gt; {:done =&gt; false},
87
+ 'Wash dishes' =&gt; {:done =&gt; false},
88
+
89
+ }.each do |title, parameters|
90
+ TodoList[title] = parameters
91
+ end
92
+ </code></pre>
93
+
94
+ <h2>Third Step, V, like View</h2>
95
+
96
+ <p>Now let's get our hands dirty and just edit the templates for our to-do list.</p>
97
+
98
+ <p>Start with editing <code>template/index.xhtml</code>, it is using the default templating
99
+ of Ramaze, called Ezamar.</p>
100
+
101
+ <p>Let's put some things in there, I'll explain the syntax as we go, it's quite
102
+ simple.</p>
103
+
104
+ <pre><code>&lt;html&gt;
105
+ &lt;head&gt;
106
+ &lt;title&gt;TodoList&lt;/title&gt;
107
+ &lt;/head&gt;
108
+ &lt;body&gt;
109
+ &lt;h1&gt;TodoList&lt;/h1&gt;
110
+ &lt;ul&gt;
111
+ &lt;?r
112
+ TodoList.each do |title, parameters|
113
+ status = parameters[:done] ? 'done' : 'not done'
114
+ ?&gt;
115
+ &lt;li&gt;#{title}: #{status}&lt;/li&gt;
116
+ &lt;?r end ?&gt;
117
+ &lt;/ul&gt;
118
+ &lt;/body&gt;
119
+ &lt;/html&gt;
120
+ </code></pre>
121
+
122
+ <p>I will assume that you are familiar with basic Ruby already, so let's
123
+ concentrate on the things new here.</p>
124
+
125
+ <p><?r ?> defines an area of ruby-code. Late when the template is transformed into
126
+ pure Ruby it will be evaluated. We iterate over the TodoList model and pass the
127
+ title and parameters into a block. In that block we can just get the values
128
+ of title and status (which we define based on the parameters) displayed on the
129
+ page.</p>
130
+
131
+ <p>The whole Template would expand to something like this (only showing the
132
+ interesting part)</p>
133
+
134
+ <pre><code>&lt;ul&gt;
135
+ &lt;li&gt;Laundry: not done&lt;/li&gt;
136
+ &lt;li&gt;Wash dishes: not done&lt;/li&gt;
137
+ &lt;/ul&gt;
138
+ </code></pre>
139
+
140
+ <p>That wasn't too bad, huh?</p>
141
+
142
+ <p>Now, so we can get our instant pleasure of seeing the result of our (hard) work,
143
+ let's see how to start ramaze.</p>
144
+
145
+ <p>In the <code>todolist</code> directory run <code>ramaze</code>.</p>
146
+
147
+ <p>This will start an instance of Ramaze and run your application on it. You can
148
+ now access it by browsing to http://localhost:7000/</p>
149
+
150
+ <p>7000 is the default-port Ramaze will run on, to change it you can just run
151
+ <code>ramaze -p 7070</code> or similar. Also be sure to look at the output of
152
+ <code>ramaze --help</code> to see some other options.</p>
153
+
154
+ <h2>Fourth Step, C, like Controller</h2>
155
+
156
+ <p>The last part of the MVC-paradigm is the Controller.</p>
157
+
158
+ <p>Wouldn't it be nice to have a way to add and remove items on our to-do list?
159
+ Editing the model every time would be quite tiresome and limited.</p>
160
+
161
+ <p>Well, come along, I'll introduce you to Controller.</p>
162
+
163
+ <p>In the way MVC is structured, the Controller provides the data in a nice way
164
+ for the View, removing all of the data-preparation and most of the logic from
165
+ the templates. This makes it firstly simple to change the fronted of your
166
+ application and secondly provides excellent ways of changing the complete
167
+ Structure of the Model or View independent from each other.</p>
168
+
169
+ <p>OK, enough of the theory, you will see the benefits in an instant. Go on and
170
+ edit the file <code>src/controller/main.rb</code>.</p>
171
+
172
+ <p>The contents of it are like following:</p>
173
+
174
+ <pre><code>class MainController &lt; Controller
175
+ def index
176
+ "Hello, World"
177
+ end
178
+ end
179
+ </code></pre>
180
+
181
+ <p>The only method right now is #index, with a simple and for the moment quite
182
+ useless "Hello, World". The relationship between the methods on the controller
183
+ and the templates is 1:1, so the method #index is combined with the template
184
+ <code>index.xhtml</code>. This combination is called an <code>action</code>.</p>
185
+
186
+ <p>Let's get back to editing and change the index-method to this:</p>
187
+
188
+ <pre><code>def index
189
+ @tasks = TodoList.content
190
+ @tasks.each do |title, parameters|
191
+ status = parameters[:done] ? 'done' : 'not done'
192
+ @tasks[title] = status
193
+ end
194
+ end
195
+ </code></pre>
196
+
197
+ <p>This will take care of the logic inside the template, which now should be
198
+ changed to do following:</p>
199
+
200
+ <pre><code>&lt;html&gt;
201
+ &lt;head&gt;
202
+ &lt;title&gt;TodoList&lt;/title&gt;
203
+ &lt;/head&gt;
204
+ &lt;body&gt;
205
+ &lt;h1&gt;TodoList&lt;/h1&gt;
206
+ &lt;a href="/new"&gt;New Task&lt;/a&gt;
207
+ &lt;?r if @tasks.empty? ?&gt;
208
+ No Tasks
209
+ &lt;?r else ?&gt;
210
+ &lt;ul&gt;
211
+ &lt;?r @tasks.each do |title, status| ?&gt;
212
+ &lt;li&gt;#{title}: #{status}&lt;/li&gt;
213
+ &lt;?r end ?&gt;
214
+ &lt;/ul&gt;
215
+ &lt;?r end ?&gt;
216
+ &lt;/body&gt;
217
+ &lt;/html&gt;
218
+ </code></pre>
219
+
220
+ <p>The rest of the template can stay the same.</p>
221
+
222
+ <p>Now, if you browse to http://localhost:7000/ again you will not notice any
223
+ change, which is how it should be. The only change is that if there are no
224
+ Tasks it will say so.</p>
225
+
226
+ <p>Some things you should know:</p>
227
+
228
+ <ul>
229
+ <li>Instance-variables defined in the Controller are available in the View.</li>
230
+ <li>The return-value of the Controller does not matter (in this case).</li>
231
+ </ul>
232
+
233
+ <h2>Fifth Step, getting dynamic</h2>
234
+
235
+ <p>We set out to build the ultimate to-do list, but there are still some things
236
+ missing. First off, we want to add new tasks, so let's get that done.</p>
237
+
238
+ <p>Add a link on the <code>template/index.xhtml</code> like this:</p>
239
+
240
+ <pre><code>&lt;h1&gt;TodoList&lt;/h1&gt;
241
+ &lt;a href="/new"&gt;New Task&lt;/a&gt;
242
+ </code></pre>
243
+
244
+ <p>Open a new file <code>template/new.xhtml</code> with a form to add a new task.</p>
245
+
246
+ <pre><code>&lt;html&gt;
247
+ &lt;head&gt;
248
+ &lt;title&gt;TodoList&lt;/title&gt;
249
+ &lt;/head&gt;
250
+ &lt;body&gt;
251
+ &lt;h1&gt;New Task&lt;/h1&gt;
252
+ &lt;a href="/"&gt;Back to TodoList&lt;/a&gt;
253
+ &lt;form method="POST" action="create"&gt;
254
+ Task: &lt;input type="text" name="title" /&gt;&lt;br /&gt;
255
+ &lt;inpyt type="submit" /&gt;
256
+ &lt;/form&gt;
257
+ &lt;/body&gt;
258
+ &lt;/html&gt;
259
+ </code></pre>
260
+
261
+ <p>We will not need a method for this on our controller, in fact, actions can
262
+ consist of either method and template or only one of them. The Controller
263
+ can act as a View and the View as Controller (if it returns a String and there
264
+ is no template).</p>
265
+
266
+ <p>If you try to use this form you will notice that we have not yet defined a way
267
+ to actually create the task.</p>
268
+
269
+ <p>You will get the default Ramaze error-page instead. Please take your time to
270
+ explore it and see how Ramaze reacted on the error.</p>
271
+
272
+ <p>It will show you the back trace and what state the application is in at the
273
+ moment, the request and response and the contents of the session. This is very
274
+ useful for debugging and development, you can provide your own set of
275
+ error-pages before going into production (or deactivate them fully) though.</p>
276
+
277
+ <p>OK, let's implement the action for #create, all we want to do is take the
278
+ requests parameters and create a new task for it, this looks like following on
279
+ your MainController.</p>
280
+
281
+ <pre><code>def create
282
+ title = request['title']
283
+ TodoList[title] = {:done =&gt; false}
284
+ redirect R(self)
285
+ end
286
+ </code></pre>
287
+
288
+ <p>That's all folks!</p>
289
+
290
+ <p>we get the title from the request-object, put it into our TodoList as undone
291
+ and redirect back to the mapping of the current Controller ('/' in this case).</p>
292
+
293
+ <p>Now you can create as many tasks as you want, please don't get overworked ;)</p>
294
+
295
+ <h2>Sixth Step, open and close tasks</h2>
296
+
297
+ <p>Since the nature of tasks is to be done eventually
298
+ we will need some way to mark it as done or open tasks again.</p>
299
+
300
+ <p>Jump into <code>template/index.xhtml</code> and do the following:</p>
301
+
302
+ <pre><code>&lt;?r @tasks.each do |title, status, toggle| ?&gt;
303
+ &lt;li&gt;
304
+ #{title}: #{status} - #{toggle}
305
+ &lt;/li&gt;
306
+ &lt;?r end ?&gt;
307
+ </code></pre>
308
+
309
+ <p>We added a new element here, <code>toggle</code>, the Controller should give us
310
+ a link to change the status corresponding to the status of the task, so off
311
+ we go and change the index method on the controller once again:</p>
312
+
313
+ <pre><code>def index
314
+ @tasks = []
315
+ TodoList.original.each do |title, parameters|
316
+ if parameters[:done]
317
+ status = 'done'
318
+ toggle = link( R( self, :open, CGI.escape(title) ), :title =&gt; 'Open Task' )
319
+ else
320
+ status = 'not done'
321
+ toggle = link( R( self, :close, CGI.escape(title) ), :title =&gt; 'Close Task' )
322
+ end
323
+ @tasks &lt;&lt; [title, status, toggle]
324
+ end
325
+ @tasks.sort!
326
+ end
327
+ </code></pre>
328
+
329
+ <p>Wow, quite some new stuff here. Let me explain that in detail.</p>
330
+
331
+ <p>We first decide whether a task is done or not, then go on and provide a link to
332
+ toggle the status, link and R are both methods that help you do that.
333
+ the result will be something like:</p>
334
+
335
+ <pre><code>&lt;a href="/open/Wash+dishes"&gt;Close Task&lt;/a&gt;
336
+ </code></pre>
337
+
338
+ <p>R actually is responsible to build the links href, for more information please
339
+ take a look at the RDoc for LinkHelper.</p>
340
+
341
+ <p>Also, you might have noticed that the tasks were changing order on every reload,
342
+ which is because we were using an Hash, which are per definition unsorted, so
343
+ now we use an array to hold our tasks and sort it.</p>
344
+
345
+ <p>Now back again to <code>template/index.xhtml</code> and change it as follows:</p>
346
+
347
+ <pre><code>&lt;?r @tasks.each do |title, status, toggle| ?&gt;
348
+ &lt;li&gt;
349
+ #{title}: #{status} [ #{toggle} ]
350
+ &lt;/li&gt;
351
+ &lt;?r end ?&gt;
352
+ </code></pre>
353
+
354
+ <p>As usual, the things not changed are omitted for terseness.</p>
355
+
356
+ <p>And as usual since the links for open and close don't lead anywhere, add the
357
+ corresponding methods to the Controller:</p>
358
+
359
+ <pre><code>def open title
360
+ task_status title, false
361
+ redirect R(self)
362
+ end
363
+
364
+ def close title
365
+ task_status title, true
366
+ redirect R(self)
367
+ end
368
+
369
+ private
370
+
371
+ def task_status title, status
372
+ task = TodoList[title]
373
+ task[:done] = status
374
+ TodoList[title] = task
375
+ end
376
+ </code></pre>
377
+
378
+ <p>Oh, now what have we got here?
379
+ private declares that methods from here on are only to be used within the
380
+ Controller itself, we define an #task_status method that takes the title and
381
+ status to be set so we don't have to repeat that code in <em>#open</em> and <em>#close</em>
382
+ and follow the DRY (Don't repeat yourself) paradigm.</p>
383
+
384
+ <p>Another thing we have not encountered so far is that you can define your public
385
+ methods to take parameters on their own, they will be calculated from requests.</p>
386
+
387
+ <pre><code>'/open/Wash+dishes'
388
+ </code></pre>
389
+
390
+ <p>will translate into:</p>
391
+
392
+ <pre><code>open('Wash dishes')
393
+ </code></pre>
394
+
395
+ <p>Which in turn will call task_status('Wash dishes', false)</p>
396
+
397
+ <p>That's it, go on and try it :)</p>
398
+
399
+ <h2>Seventh Step, delete tasks</h2>
400
+
401
+ <p>Well, creating, opening and closing work now, one of the things you will
402
+ consider is to delete a task permanently.</p>
403
+
404
+ <p>This is just two little changes away, so let's add the link for deletion in our
405
+ Controller:</p>
406
+
407
+ <pre><code>delete = link( R( self, :delete, CGI.escape(title) ), :title =&gt; 'Delete' )
408
+ @tasks &lt;&lt; [title, status, toggle, delete]
409
+ </code></pre>
410
+
411
+ <p>and an corresponding method while we're at it:</p>
412
+
413
+ <pre><code>def delete title
414
+ TodoList.delete title
415
+ redirect R(self)
416
+ end
417
+ </code></pre>
418
+
419
+ <p>Now jumping to <code>template/index.xhtml</code> again, change it so it shows the link:</p>
420
+
421
+ <pre><code>&lt;?r @tasks.each do |title, status, toggle, delete| ?&gt;
422
+ &lt;li&gt;
423
+ #{title}: #{status} [ #{toggle} | #{delete} ]
424
+ &lt;/li&gt;
425
+ &lt;?r end ?&gt;
426
+ </code></pre>
427
+
428
+ <p>Voilà, you now have acquired the Certificate of Ramazeness, our accounting-
429
+ section will contact you within the next few days.</p>
430
+
431
+ <p>Just kidding, but that really are the basics, in the next few steps I will
432
+ explain some more advanced concepts of Ramaze and the templating.</p>
433
+
434
+ <h2>Eighth Step, Elements</h2>
435
+
436
+ <pre><code>&lt;Page&gt;&lt;/Page&gt;
437
+ </code></pre>
438
+
439
+ <p>This is called an Element, Ramaze will go and search for a class that matches
440
+ the name Page and responds to <em>#render</em>. Then it will go and hand the content in
441
+ between to that Element.</p>
442
+
443
+ <p>Sounds weird?</p>
444
+
445
+ <p>Let us have a look at our templates, they all got some repetitive stuff, like:</p>
446
+
447
+ <pre><code>&lt;html&gt;
448
+ &lt;head&gt;
449
+ &lt;title&gt;TodoList&lt;/title&gt;
450
+ &lt;/head&gt;
451
+ &lt;body&gt;
452
+ &lt;h1&gt;some title&lt;/h1&gt;
453
+ &lt;/body&gt;
454
+ &lt;/html&gt;
455
+ </code></pre>
456
+
457
+ <p>How about replacing that with something short and nice:</p>
458
+
459
+ <pre><code>&lt;Page title="TodoList"&gt;
460
+ your other content
461
+ &lt;/Page&gt;
462
+ </code></pre>
463
+
464
+ <p>Would be nice of course, and when you start having more templates it makes an
465
+ awful lot of sense to change the enclosing stuff in one place.</p>
466
+
467
+ <p>So let's apply DRY here as well.</p>
468
+
469
+ <p>Take a look at the <code>src/element/page.rb</code></p>
470
+
471
+ <pre><code>class Page &lt; Element
472
+ def render
473
+ %{
474
+ &lt;html&gt;
475
+ &lt;head&gt;
476
+ &lt;title&gt;Welcome to Ramaze&lt;/title&gt;
477
+ &lt;/head&gt;
478
+ &lt;body&gt;
479
+ #{content}
480
+ &lt;/body&gt;
481
+ &lt;/html&gt;
482
+ }
483
+ end
484
+ end
485
+ </code></pre>
486
+
487
+ <p>Alright, most things we need are in place already, the most important thing
488
+ is the <em>#content</em> method that we call with <em>#{content}</em> inside the string in
489
+ <em>#render</em>.</p>
490
+
491
+ <p>Just adopt it to your liking, I'll just use the things we had in our templates
492
+ so far:</p>
493
+
494
+ <pre><code>class Page &lt; Element
495
+ def render
496
+ %{
497
+ &lt;html&gt;
498
+ &lt;head&gt;
499
+ &lt;title&gt;TodoList&lt;/title&gt;
500
+ &lt;/head&gt;
501
+ &lt;body&gt;
502
+ &lt;h1&gt;#{@hash['title']}&lt;/h1&gt;
503
+ #{content}
504
+ &lt;/body&gt;
505
+ &lt;/html&gt;
506
+ }
507
+ end
508
+ end
509
+ </code></pre>
510
+
511
+ <p>Please note that the @hash is filled with the things you pass as parameters
512
+ to tye Page-tag.</p>
513
+
514
+ <p>And let's change our templates as well.</p>
515
+
516
+ <p>First the <code>template/index.xhtml</code></p>
517
+
518
+ <pre><code>&lt;Page title="TodoList"&gt;
519
+ &lt;a href="/new"&gt;New Task&lt;/a&gt;
520
+ &lt;?r if @tasks.empty? ?&gt;
521
+ No Tasks
522
+ &lt;?r else ?&gt;
523
+ &lt;ul&gt;
524
+ &lt;?r @tasks.each do |title, status, toggle, delete| ?&gt;
525
+ &lt;li&gt;
526
+ #{title}: #{status} [ #{toggle} | #{delete} ]
527
+ &lt;/li&gt;
528
+ &lt;?r end ?&gt;
529
+ &lt;/ul&gt;
530
+ &lt;?r end ?&gt;
531
+ &lt;/Page&gt;
532
+ </code></pre>
533
+
534
+ <p>and the <code>template/new.xhtml</code></p>
535
+
536
+ <pre><code>&lt;Page title="New Task"&gt;
537
+ &lt;a href="/"&gt;Back to TodoList&lt;/a&gt;
538
+ &lt;form method="POST" action="create"&gt;
539
+ Task: &lt;input type="text" name="title" /&gt;&lt;br /&gt;
540
+ &lt;input type="submit" /&gt;
541
+ &lt;/form&gt;
542
+ &lt;/Page&gt;
543
+ </code></pre>
544
+
545
+ <p>Alright, now just go and look at the result in the browser, try changing
546
+ the things inside the Element and look at how it behaves.</p>
547
+
548
+ <h2>Ninth Step, Prettify</h2>
549
+
550
+ <p>Let's structure the data inside the list a little bit, in this case into a table to get it line up properly and look actually structured.</p>
551
+
552
+ <p>So, from what we have right now:</p>
553
+
554
+ <pre><code>&lt;ul&gt;
555
+ &lt;?r @tasks.each do |title, status, toggle, delete| ?&gt;
556
+ &lt;li&gt;
557
+ #{title}: #{status} [ #{toggle} | #{delete} ]
558
+ &lt;/li&gt;
559
+ &lt;?r end ?&gt;
560
+ &lt;/ul&gt;
561
+ </code></pre>
562
+
563
+ <p>To something like this:</p>
564
+
565
+ <pre><code>&lt;table&gt;
566
+ &lt;?r @tasks.each do |title, status, toggle, delete| ?&gt;
567
+ &lt;tr&gt;
568
+ &lt;td class="title"&gt; #{title} &lt;/td&gt;
569
+ &lt;td class="status"&gt; #{status} &lt;/td&gt;
570
+ &lt;td class="toggle"&gt; #{toggle} &lt;/td&gt;
571
+ &lt;td class="delete"&gt; #{delete} &lt;/td&gt;
572
+ &lt;/tr&gt;
573
+ &lt;?r end ?&gt;
574
+ &lt;/table&gt;
575
+ </code></pre>
576
+
577
+ <p>And, since we have proper classes to address some style sheets, jump into the Page element and add some style sheet like that:</p>
578
+
579
+ <pre><code>&lt;head&gt;
580
+ &lt;title&gt;TodoList&lt;/title&gt;
581
+ &lt;style&gt;
582
+ table { width: 100%; }
583
+ tr { background: #efe; width: 100%; }
584
+ tr:hover { background: #dfd; }
585
+ td.title { font-weight: bold; width: 60%; }
586
+ td.status { margin: 1em; }
587
+ a { color: #3a3; }
588
+ &lt;/style&gt;
589
+ &lt;/head&gt;
590
+ </code></pre>
591
+
592
+ <p>That looks quite a bit nicer, right?
593
+ And yes, if you don't like tables (though this is an entirely legit use in my opinion, you can just do it like you want, using nested lists or divs/spans, replacing the open/close and delete links with nice images and changing the style according to the status.</p>
594
+
595
+ <p>I will leave this as an exercise to the reader.</p>
596
+
597
+ <p>To be continued...</p>
598
+ </body>
599
+ </html>