forked from kprevas/ronin
/
tutorial.html
922 lines (716 loc) · 45.9 KB
/
tutorial.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>The Ronin Web Framework</title>
<!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css" rel="stylesheet">
<link href="http://twitter.github.com/bootstrap/assets/css/bootstrap-responsive.css" rel="stylesheet">
<link href="http://twitter.github.com/bootstrap/assets/js/google-code-prettify/prettify.css" rel="stylesheet">
<link href="./css/site.css" rel="stylesheet">
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-21326690-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</head>
<body>
<div id="topbar" class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="/">Ronin</a>
<div class="nav-collapse">
<ul class="nav">
<li><a href="index.html">Home</a></li>
<li class="dropdown active">
<a class="dropdown-toggle" data-toggle="dropdown">Tutorial<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a onclick="scrollToSection('why')">Why?</a></li>
<li><a onclick="scrollToSection('getting_started')">Getting Started</a></li>
<li><a onclick="scrollToSection('app_layout')">Application Layout</a></li>
<li><a onclick="scrollToSection('starting_up')">Starting The Dev Server</a></li>
<li><a onclick="scrollToSection('hello_world')">Hello World</a></li>
<li><a onclick="scrollToSection('parameters')">Controller & View Params</a></li>
<li><a onclick="scrollToSection('layouts')">Layout and Blocks</a></li>
<li><a onclick="scrollToSection('database')">Database Interaction</a></li>
<li><a onclick="scrollToSection('create_entity')">Creating Entities</a></li>
<li><a onclick="scrollToSection('edit_entity')">Editing Entities</a></li>
<li><a onclick="scrollToSection('links')">Links</a></li>
<li><a onclick="scrollToSection('relationships')">Relationships</a></li>
<li><a onclick="scrollToSection('querying')">Querying The Database</a></li>
<li><a onclick="scrollToSection('next_steps')">Next Steps</a></li>
</ul>
</li>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown">Docs<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="docs.html#installing">Installing Ronin</a></li>
<li><a href="docs.html#server_config">Server Configuration</a></li>
<li><a href="docs.html#controllers">Controllers</a></li>
<li><a href="docs.html#controller_args">Controller Arguments</a></li>
<li><a href="docs.html#json_support">JSON Support</a></li>
<li><a href="docs.html#views">Views</a></li>
<li><a href="docs.html#link_targets">Link Targets</a></li>
<li><a href="docs.html#logging_tracing">Logging & Tracing</a></li>
<li><a href="docs.html#caching">Caching</a></li>
<li><a href="docs.html#file_upload">File Upload</a></li>
<li><a href="docs.html#user_auth">User Authentication</a></li>
<li><a href="docs.html#csrf">CSRF Protection</a></li>
<li><a href="docs.html#jsonp">JSONP Support</a></li>
<li><a href="docs.html#admin_console">Admin Console</a></li>
<li><a href="docs.html#aardvark">Aardvark</a></li>
<li><a href="docs.html#cron">Cron Jobs</a></li>
<li><a href="docs.html#misc">Misc</a></li>
</ul>
</li>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown">Tosa<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="tosa.html#intro">Introduction</a></li>
<li><a href="tosa.html#schema">Schema Guidelines</a></li>
<li><a href="tosa.html#crud">Creating, Etc.</a></li>
<li><a href="tosa.html#finding">Finding</a></li>
<li><a href="tosa.html#fks">Foreign Keys</a></li>
<li><a href="tosa.html#transactions">Transactions</a></li>
<li><a href="tosa.html#ronin">Tosa & Ronin</a></li>
</ul>
</li>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown">Plugins<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="plugins.html#romail">Mail</a></li>
<li><a href="plugins.html#roninws">Web Services</a></li>
<li><a href="plugins.html#goson">JSON</a></li>
<li><a href="plugins.html#coffee">CoffeeScript</a></li>
<li><a href="plugins.html#less">LessCSS</a></li>
</ul>
</li>
<li><a href="ide.html">IDE</a></li>
</ul>
</div>
<!--/.nav-collapse -->
</div>
</div>
</div>
<div class="container">
<section id="why">
<div class="page-header">
<h1>Why Ronin?</h1>
</div>
<p>Imagine you're developing a web application with a typical web framework. You've noticed a bug on some page of
your app... how do you fix it? Maybe you'll look for some distinctive text on the page, then search for that text
in your project using your favorite IDE. Say the text turns up a template which was used to generate the page.</p>
<p>Now you have to track down the data that was used to render the template, which means you need to find the code
that retrieved the data, and maybe an XML configuration file which specifies how that data was mapped to an
identifier used in the template. If you're not intimately familiar with the inner workings of the app, and of the
framework you're using, you've got a lot of searching to do.</p>
<p>With Ronin, everything is out in the open - there's almost no reflective magic or arcane XML files. The URL in
your browser tells you which method on which class was called to generate the current page. That method contains a
call to render the template for the page, and an explicit set of parameters providing the data for the template.
If you're using IntelliJ, you can navigate between all these different pieces with a single click.</p>
<p>And best of all, should you make a mistake - misspell a file name, or leave out a parameter - there will be a
compilation error, so you'll know right away instead of having to exhaustively walk through your app (or maintain
100% unit test coverage).</p>
</section>
<section id="getting_started">
<div class="page-header">
<h1>Getting Started</h1>
</div>
<p>Here's what you need to get started with Ronin:</p>
<ul>
<li>Java 6 or later.</li>
<li><a href="https://github.com/vark/Aardvark/downloads">Aardvark</a>, a build system for Gosu, version 0.4.</li>
</ul>
<p>With aardvark on your path, you can run the remote Ronin installation script:</p>
<pre>
$ vark -url http://ronin-web.org/init.vark init -name <em>my_app</em></pre>
<p>Where <em>my_app</em> is whatever name you would like for your Ronin application. (If you are on Windows, make sure your current directory doesn't contain any spaces - the H2 database that Ronin uses for development has problems with spaces in the path.) You will see output like this:
</p>
<pre>
Buildfile: /private/var/folders/yy/1h6qk8t52rjczystgf79s5nm0000gn/T/build6584956297061779085.vark
Done parsing Aardvark buildfile in 1839 ms
init:
[get] Getting: http://ronin-web.org/ronin-template.zip
[get] To: /var/folders/yy/1h6qk8t52/T/ronin-template8153128738706862212zip
[unzip] Expanding: /var/folders/yy/1h6qk8t52/T/ronin-template8153128738706862212zip into ./my_app
[echo] Created a new ronin application at ./my_app.
[echo] To start your ronin app, cd my_app; vark server
BUILD SUCCESSFUL
Total time: 0 seconds</pre>
<p>Great, you've got a Ronin application set up. Let's look at how it is laid out:</p>
</section>
<section id="app_layout">
<div class="page-header">
<h1>Ronin Application Layout</h1>
</div>
<p>Ronin applications are laid out in a very simple manner:</p>
<div class="inset">
<dl>
<dt>/build.vark</dt>
<dd>This is the <a href="http://vark.github.com/">Aardvark</a> file for your project, and can be used for build
scripting.
</dd>
<dt>/pom.xml</dt>
<dd>This is the dependency file for your Ronin application. Ronin does not use Maven for building, but leverages
it for dependencies and IDE support.
</dd>
<dt>/src</dt>
<dd>This is where all your source will go.
<dl>
<dt>/src/config/RoninConfig.gs</dt>
<dd>This Gosu class allows you to configure the Ronin application.</dd>
<dt>/src/controller</dt>
<dd>This is where your Ronin controllers will go.</dd>
<dt>/src/view</dt>
<dd>This is where your Gosu templates will go (if you are using templates for your UI).</dd>
<dt>/src/db</dt>
<dd>This is where your .ddl and .sql files will go (if you are using Tosa to connect to a SQL database).</dd>
</dl>
</dd>
<dt>/test</dt>
<dd>This is where all your tests will go.</dd>
<dt>/html</dt>
<dd>This is the root of the Ronin WAR file.
<dl>
<dt>/html/WEB-INF/web.xml</dt>
<dd>This is a thin web.xml file that bootstraps Ronin and Gosu in standard server environments.</dd>
<dt>/html/public</dt>
<dd>This folder can be used for static resources (e.g. images, CSS files, and Javascript).</dd>
</dl>
</dd>
</dl>
</div>
<p>Note that there is very little configuration, besides dependencies in the <code>pom.xml</code> file. Ronin biases
towards source code and away from configuration files, leveraging Gosu's flexible syntax and Open Type System.
This makes things obvious and debuggable.</p>
</section>
<section id="starting_up">
<div class="page-header">
<h1>Starting The Dev Server</h1>
</div>
<p>As the installation script says, you can change into your application's main directory and start up the built-in
webserver (Jetty):</p>
<pre>
$ cd my_app
$ vark server
1 [main] INFO Ronin - Environment properties are: {}
1 [main] WARN Ronin - The DCEVM is not available, Ronin will use classloaders for hotswapping
182 [main] INFO Ronin - H2 DB started at jdbc:h2:file:runtime/h2/devdb
2455 [main] INFO Ronin - H2 web console started at http://localhost:8082/frame.jsp?jsessionid=9bca2a3194f8e6a7
2455 [main] INFO Ronin -
You can connect to your database using "jdbc:h2:file:runtime/h2/devdb" as your url, and a blank username/password
2492 [main] INFO org.eclipse.jetty.util.log - jetty-8.0.0.M2
2612 [main] INFO org.eclipse.jetty.util.log - NO JSP Support for /
11324 [main] INFO org.eclipse.jetty.util.log - Started SelectChannelConnector@0.0.0.0:8080
11325 [main] INFO Ronin -
11325 [main] INFO Ronin - Your Ronin App is listening at http://localhost:8080
</pre>
<p>At this point, you should be able to see your application at <a href="http://localhost:8080/">http://localhost:8080/</a>.
</p>
</section>
<section id="hello_world">
<div class="page-header">
<h1>Hello World</h1>
</div>
<p>So now you've got a server running, but it's not necessarily doing anything interesting. If you try to access it
from your browser, you'll get a placeholder page.</p>
<p>In Ronin, there are generally two components involved in responding to a user's request: a controller and a view.
The controller will perform any necessary actions, and then will delegate to a view, which will create the HTML
(or other response) that is sent back to the user's browser.</p>
<h2>Creating A Controller</h2>
<p>Let's create the simplest possible controller. A Ronin controller is defined via a Gosu class. Classes
in Gosu are very similar to classes in Java; they are defined in their own file, and the name of the file (plus
the directory in which it lives) determines the name of the class. Ronin has a special rule that controller
classes must live in the "controller" package (<code>my_app/src/controller</code>). Since our goal is to create a
blogging application, let's create a controller class which will eventually contain all of the code for viewing
and manipulating blog posts.</p>
<p>Add create and add the following code in the <code>src/controller/PostCx.gs</code> file:</p>
<pre class="prettyprint linenums">
package controller
uses ronin.RoninController
class PostCx extends RoninController {
}</pre>
<p>Let's take a moment to examine the anatomy of this class. The <code>package</code> statement simply restates
which package this class lives in. The <code>uses</code> statement identifies a class in another package which we
want to reference in this class; this is the equivalent of <code>import</code> in Java. Finally, the
<code>class</code> statement restates the name of the class, and identifies its superclass.</p>
<p>Note that unlike a Java import statement, the uses statement doesn't end in a semicolon. Ending statements with a
semicolon is not required in Gosu (though it is permitted).</p>
<p>When I tell you later on to add a uses statement to this class, add it after the existing uses statement and
before the class statement.</p>
<p>Throughout this tutorial, we'll be using a convention where controller classes have names ending in "Cx" (short
for "Controller"). In general you may name a controller class however you like.</p>
<p>Now let's add a function to this class. Each function on the controller class will be responsible for responding
to requests from a single URL.</p>
<p>Add this code to your class:</p>
<pre class="prettyprint linenums">
function viewPost() : String {
return "<html><body>This is a post</body></html>"
}</pre>
<p>Gosu functions are defined using the <code>function</code> keyword. They have public access by default, and are
assumed to have a void return value unless specified otherwise. This method returns a
<code>java.lang.String</code>.</p>
<p>This is the simplest possible controller method: it does not even delegate to a view. Instead, it simply returns
a string value directly. Now that you have created a controller function, you can invoke it by simply navigating
to:</p>
<p><a href="http://localhost:8080/PostCx/viewPost">http://localhost:8080/PostCx/viewPost</a></p>
<p>While returning strings directly like this can be handy, it is much more common to use a Gosu Template as a view. Let's look at
how to do that.</p>
<h2>Creating a View</h2>
<p>Create the following file in <code>src/view/ViewPost.gst</code>:</p>
<pre class="prettyprint linenums">
<%@ extends ronin.RoninTemplate %>
<html>
<body>
This is a post.
</body>
</html></pre>
<p> A Gosu template is a special kind of Gosu file (with the extension .gst) which mixes plain text with Gosu code to
produce the desired output (somewhat like a JSP). Ronin does not require that views live in the "view" package,
but it is the conventional place to put them.</p>
<p>Note that templates are a built-in part of the Gosu language, and are not specific to Ronin.</p>
<p>The first line of this template is a <b>template directive</b>, as it is surrounded by <code><%@</code> and <code>%></code>. The extends directive
defines a "superclass" for this template; this isn't a true superclass in terms of inheritance, but it allows the
template to call static methods on the given class without qualifying them. In this case, we're extending
<code>RoninTemplate</code>, which contains some useful methods for Ronin templates.</p>
<p>Now let's use this view template, rather than returning a string. Change the code in <code>PostCx.gs</code> to this:</p>
<pre class="prettyprint linenums">
function viewPost() {
view.ViewPost.render(Writer)
}</pre>
<p>and reload the page. You should see the same output.</p>
<p>Note that the method now does not declare a return type, making it void. Rather than returning a value, all
response writing is delegated to the <code>ViewPost</code> template,
by passing the <code>Writer</code> Property along to it. (<code>Writer</code> is a property available on
all controllers.)</p>
<h2>Using Gosu code in templates</h2>
<p>Let's make this template slightly more interesting. Modify <code>ViewPost.gst</code> as follows:</p>
<pre class="prettyprint linenums">
<%@ extends ronin.RoninTemplate %>
<% uses java.util.Date %>
<html>
<body>
The current time is ${new Date()}.
</body>
</html></pre>
<p>There are two new types of template tags here. The first is surrounded by <code><%</code> and <code>%></code>; note that there is no @ like
in the directive tags. This type of tag contains Gosu code which is executed as soon as the template encounters
it; here, we're using it to add a uses statement. The tag will not be present in the output text.</p>
<p>The second is surrounded by <code>${</code> and <code>}</code>. This type of tag contains Gosu code which evaluates to a value; that value
is then inserted into the output text.</p>
<p>Reload the page. This time, you should see the current date and time. That's
because the expression between <code>${</code> and <code>}</code> was evaluated as Gosu code, converted to a String, and inserted into the
template.</p>
</section>
<section id="parameters">
<div class="page-header">
<h1>Controller And View Parameters</h1>
</div>
<p>Our application is still rather uninteresting in that it doesn't really accept any input from the user. Let's
address that by parameterizing our controller and view.</p>
<p>Modify the <code>viewPost()</code> method in <code>PostCx.gs</code> as follows:</p>
<pre class="prettyprint linenums">
function viewPost(post : String) {
view.ViewPost.render(Writer, post)
}</pre>
<p>and <code>ViewPost.gst</code> as follows:</p>
<pre class="prettyprint linenums">
<%@ extends ronin.RoninTemplate %>
<%@ params (post : String) %>
<html>
<body>
${post}
</body>
</html></pre>
<p>We've added a parameter to the viewPost method; its name is post, and its type is String. (Note that unlike in
Java, the name comes first and the type second.) We've also added a similar parameter to the ViewPost template,
using the params directive. When Gosu sees this, it modifies the template type's render method to take an
additional parameter - if we had added the directive but not modified <code>viewPost()</code> to pass in an additional
argument, that method call would have produced a compilation error.</p>
<p>When Ronin handles a URL which calls a method with one or more parameters, the values given to those parameters
are derived from the arguments in the URL. For instance:</p>
<p><a href="http://localhost:8080/PostCx/viewPost?post=Hello+world">http://localhost:8080/PostCx/viewPost?post=Hello+world</a></a></p>
<p>will assign the string "Hello world" to the post parameter of <code>viewPost()</code>. If you access the URL without a post
parameter, as we did before, the value of the post parameter will be null.</p>
<p>In a web application, it's important to ensure that you never render user input directly to your HTML (as we have
done above), since a user could insert malicious code into your page. The RoninTemplate class provides a method
called <code>h()</code> to help with this. Change the line that says <code>${post}</code> to <code>${h(post)}</code>, and the user's input will be
properly escaped for inclusion in an HTML page.</p>
</section>
<section id="layouts">
<div class="page-header">
<h1>Layout and Blocks</h1>
</div>
<p>Before we go crazy adding controllers and views to our application, here's an observation: the
<html> and <body> tags aren't likely to change too much between views. It would be nice if there was an easy
way to
extract the content that's common to all views to a single place, so that it's easier to change and maintain.
</p>
<p>
Fortunately, there is. Create a file in your view directory called <code>Layout.gst</code> with the following contents:
</p>
<pre class="prettyprint linenums">
<%@ extends ronin.RoninTemplate %>
<%@ params(content()) %>
<html>
<head>
<title>Blog</title>
</head>
<body>
<div id="content"><% content() %></div>
</body>
</html></pre>
<p>The parameter we've defined in this template looks a bit different from our previous template. That's because
it's a block (also known as a "closure", "first-class function", or "lambda"). The content parameter is itself a
function, taking no parameters and returning nothing. As you can see in the body of the template, a block can be
invoked like any other function. Here, we're invoking it in order to render the content of the page within the
structure we've defined. We can now remove the <html> and <body> tags from ViewPost.gst.</p>
<p>If you're a Java programmer, it may help to think of a block as being like an instance of <code>Runnable</code>, and invoking
the block being like calling the <code>run()</code> method. Unlike a <code>Runnable</code>, however, a block can take parameters and return
a value, and it can access variables from the scope where it's declared.</p>
<p>Let's modify the <code>viewPost()</code> function to make use of our layout template. Change the contents of the function
to:</p>
<pre class="prettyprint linenums">
Layout.render(Writer, \ -> {
ViewPost.render(Writer, post)
})</pre>
<p>and add the following uses statements:</p>
<pre class="prettyprint linenums">uses view.Layout
uses view.ViewPost</pre>
<p>The second argument to Layout.render() is a block, which is initiated with the backslash character. As the block
takes no arguments, it's followed immediately by an arrow (<code>-></code>), then the contents of the block surrounded by curly
brackets. When this block is invoked, the ViewPost template will be rendered.</p>
<p>Now remove the layout elements from <code>ViewPost.gst</code> so that it simply reads:</p>
<pre class="prettyprint linenums"><%@ extends ronin.RoninTemplate %>
<%@ params (post : String) %>
{post}</pre>
<p>Reload the page and you should see that the output has not changed, but you now have a reusable layout template.</p>
</section>
<section id="database">
<div class="page-header">
<h1>Database Interaction</h1>
</div>
<p>A blogging application needs a way to store and retrieve data - specifically, posts and comments. Ronin allows
you to use any mechanism you like for this purpose, but it is particularly well-suited for use with Tosa, a data
persistence layer that takes advantage of some powerful features of Gosu to make simple database operations very
convenient.</p>
<p>Tosa plugs in to Gosu's type system to generate types based on a pre-defined database schema; unlike with many
other similar frameworks, you don't explicitly define classes for the entities stored in your database. Before you
can start using Tosa in your Gosu code, then, you need to define your database schema.</p>
<p>Roninit created a file called <code>model.ddl</code> in <code>src/db</code>, which is meant to contain the data definition for your
database. Paste the following SQL in to that file, replacing the sample schema that's currently there:</p>
<pre class="prettyprint linenums">
CREATE TABLE "Post"(
"id" BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
"Title" TEXT,
"Body" TEXT,
"Posted" TIMESTAMP
);
CREATE TABLE "Comment"(
"id" BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
"Post_id" BIGINT,
"Name" VARCHAR(255),
"Text" TEXT,
"Posted" TIMESTAMP
);</pre>
<p>then stop your server and run:</p>
<p><code> $ vark reset-db </code></p>
<p>Each entity (Post and Comment) is represented by a table in the database. Each table has one primary key column
named "id", and some number of other columns containing information about the entity. A Comment has a foreign key
to the Post on which it was made, as denoted by the column named "Post_id".</p>
<p>The connection string for your database is managed in the <code>RoninConfig</code> class in <code>src/config</code>. Here is the default
configuration code:</p>
<pre class="prettyprint linenums">
if(m == DEVELOPMENT) {
db.model.Database.JdbcUrl = "jdbc:h2:file:runtime/h2/devdb"
} else if(m == TESTING) {
db.model.Database.JdbcUrl = "jdbc:h2:file:runtime/h2/testdb"
} else if(m == STAGING) {
db.model.Database.JdbcUrl = "jdbc:h2:file:runtime/h2/stagingdb"
} else if(m == PRODUCTION) {
db.model.Database.JdbcUrl = "jdbc:h2:file:runtime/h2/proddb"
}</pre>
<p>As you can see, depending on the mode that Ronin is in, it will use various different database connection
strings. You can always
tell which database you are connecting to because, like all Gosu code, this is debuggable.</p>
</section>
<section id="create_entity">
<div class="page-header">
<h1>Creating Entities</h1>
</div>
<p>Our database isn't very interesting without any data in it, so let's create a page where the user can enter a new
post. Add these uses statements to <code>PostCx</code>:</p>
<pre class="prettyprint linenums">uses db.model.Post
uses view.EditPost</pre>
<p>and these methods:</p>
<pre class="prettyprint linenums">
function create() {
Layout.render(Writer, \ -> {
EditPost.render(Writer, new Post())
})
}
function save(post : Post) {
// we will fill this in later.
}</pre>
<p>The <code>create()</code> method will render a template named <code>EditPost</code> (as we will be using the same template for creating a
new post and editing an existing post), passing in a new instance of Post. Note that we never defined a Gosu class
called <code>db.model.Post</code>; that type is generated for us by Tosa because it is a table in the database. When we create
a new instance here, we are not yet performing any database operations - we're simply creating an object in memory
which can be read from or persisted to the Post table.</p>
<p>(If the Gosu editor shows an error on <code>db.model.Post</code>, you may have to restart it in order for it to find the database types.)</p>
<p>Create the <code>EditPost.gst</code> template in the view directory with the following contents:</p>
<pre class="prettyprint linenums">
<%@ extends ronin.RoninTemplate %>
<%@ params(aPost : db.model.Post) %>
<% uses controller.PostCx %>
<% uses db.model.Post %>
<% using(target(PostCx#save(Post))) { %>
<form method="post" action="${TargetURL}">
<% if(not aPost.New) { %>
<input type="hidden" name="${n(Post)}" value="${aPost.id}">
<% } %>
<input type="text" name="${n(Post#Title)}" value="${h(aPost.Title)}"><br>
<textarea name="${n(Post#Body)}" rows=20 columns=80>${h(aPost.Body)}</textarea><br>
<input type="submit">
</form>
<% } %></pre>
<p>Let's walk through this template. The first two lines should appear familiar to you. Note that the type of the
parameter is our entity type, since that's what we're passing in from the controller.</p>
<p>The first code snippet after the uses statements opens a using statement. This is a Gosu construct which allows
you to safely initialize and dispose of resources used within the contained code. Here, we are calling a method on
<code>RoninTemplate</code> called <code>target()</code>, and using the context object that it returns.</p>
<p>The parameter we're passing in to <code>target()</code> is a <b>method literal</b>, indicated by the use of the # operator (instead
of a dot operator). Whereas a dot operator would cause the method to be called, the # operator simply represents a
theoretical future call to the method, and it returns a <code>MethodReference</code> object. <code>target()</code> takes the method literal
to mean that you are defining some part of your template in the context of a call to the <code>save()</code> method.</p>
<p>(Method literals come in two flavors - those with <b>bound</b> arguments and those with <b>unbound</b> arguments. If specific
values are provided for the method's arguments, they are said to be <b>bound</b>; if only the types of the arguments are
specified, as is the case here, they are <b>unbound</b>. <code>target()</code> does not require bound arguments.)</p>
<p>The action attribute of the form tag is being set to the string returned by the <code>TargetURL</code> property. Since we're
inside the using statement defined on the previous line, the <code>TargetURL</code> will be the URL to which our form should be
posted in order to invoke the <code>save()</code> method: <code>http://localhost:8080/PostCx/save</code>.</p>
<p>So why not just put that URL in the action attribute by hand? Say you change the name of the save method for some
reason. If the URL were hard-coded in your HTML, you wouldn't know anything was wrong until you ran your
application, tried to submit the form, and got an error. Using the <code>target()</code> method, on the other hand, means that
<code>PostCx#save(Post)</code> will not compile if the method has been renamed (or its signature has been changed, etc.), so
you'll see instantly in the editor that something is wrong.</p>
<p>After the form tag, we have an <code>if</code> statement. If the post passed in to this template is not a new post, we want to
store its ID in a hidden input, so that we know which post to edit when the form is posted to the server. We
determine whether the post is new by accessing the <code>New</code> property, which is created automatically on all Tosa
entity types, and returns true as long as the entity has not yet been saved to the database.</p>
<p>Since we know our post will be a new post for now, let's skip ahead. After we've closed the <code>if</code> statement, we have
a text field for the post's title. The value of the text field is set to the title of the existing post object,
<code>aPost.Title</code> (after being escaped by the <code>h()</code> function). The name of the field is set by a call to a helper method
named <code>n()</code> (for "name"). The argument to <code>n()</code> is a property literal, which is like a method literal, but for a
property instead of a method - here, for the <code>Title</code> property on <code>Post</code>. <code>n()</code> finds the parameter to <code>save()</code> which takes
a <code>Post</code>, and generates the appropriate input name to set the <code>Title</code> property (here, <code>post.Title</code>). The text area on
the next line performs the same task for the <code>Body</code> of the post.</p>
<p>Let's go over what will happen when we submit this form, say with the title "Hi" and the body text "Hello world".
The browser will post to the URL <code>http://localhost:8080/PostCx/save</code>, with the form data
<code>post.Title=Hi&post.Body=Hello+world</code>. Ronin will find the <code>save</code> function and see that it requires a <code>db.model.Post</code>
parameter; since we haven't told it a specific one to use, it will create a new one. It will then examine the form
data, and set the <code>Title</code> property of the post to "Hi" and the <code>Body</code> property to "Hello world". This will all happen
automatically, so most of the time you won't have to worry about it.</p>
<p>Now we'll go back and implement the <code>save()</code> method on <code>PostCx</code>:</p>
<pre class="prettyprint linenums">uses java.lang.System
uses java.sql.Timestamp</pre>
<pre class="prettyprint linenums">function save(post : Post) {
if(post.New) {
post.Posted = new Timestamp(System.currentTimeMillis())
}
post.update()
redirect(#viewPost(post))
}</pre>
<p>If the post is a new post (as it is in our case), we will set its <code>Posted</code> property to the current time. (Note that
the type of the <code>Posted</code> property is <code>java.sql.Timestamp</code>, since the Posted column in the database is a TIMESTAMP
column.) After we've done this, we save the post to the database by calling its <code>update()</code> method (which is generated
for us by Tosa). Finally, we redirect the user to a page where they can view the newly saved post.</p>
<p>This redirection bears further examination.</p>
<pre class="prettyprint linenums">redirect(#viewPost(post))</pre>
<p><code>redirect()</code> is a method we've inherited from RoninController. It takes a single argument, which is a method
literal with bound arguments. Note that since the method in question is on the same class, we don't need anything
before the # operator. <code>redirect()</code> examines the method reference and determines what URL would invoke it, then
sends the browser a response telling it to redirect to that URL.</p>
<p>(By sending a redirect to the browser, we're ensuring that if the user hits the back button, they are returned to
the edit screen, instead of to the URL which saved the post - that would result in a duplicate post being saved to
the database. It's generally a good idea to redirect after any action that changes data or is otherwise not
<a href="http://en.wikipedia.org/wiki/Idempotence">idempotent</a>.)</p>
<p>Let's quickly modify <code>viewPost()</code> so that it actually views a post:</p>
<pre class="prettyprint linenums">
function viewPost(post : Post) {
Layout.render(Writer,
\ -> ViewPost.render(Writer, post))
}</pre>
<p>And the ViewPost.gst template:</p>
<pre class="prettyprint linenums">
<%@ extends ronin.RoninTemplate %>
<%@ params(post : db.model.Post) %>
<div class="header">${h(post.Title)}</div>
<div class="body">${h(post.Body)}</div>
<div class="posted">Posted on ${post.Posted}</div></pre>
<p>All the pieces are in place. Restart the server, go to <a href="http://localhost:8080/PostCx/create">http://localhost:8080/PostCx/create</a>, and create a new post.
After clicking the submit button, you'll be redirect to the ViewPost page for that post. Keep this open in a browser tab for later.</p>
</section>
<section id="edit_entity">
<div class="page-header">
<h1>Editing Entities</h1>
</div>
<p>Now let's say we want to edit the post we've created. As we noted above, we can reuse the same template. Let's go
back and look at the <code>if</code> clause that we skipped before:</p>
<pre class="prettyprint linenums"><% if(not post.New) { %>
<input type="hidden" name="${n(Post)}" value="${post.id}">
<% } %> </pre>
<p>If the template receives an existing post, it will create a hidden input on the form whose value is the post's
unique ID (primary key). The name of the input is generated by calling <code>n()</code> with the Post type, which finds the
parameter of that type expected by <code>save()</code>, so when Ronin calls <code>save()</code>, it will use the ID to look up the existing
post in the database. The <code>Title</code> and <code>Body</code> properties of that post will then be set using the values of those
inputs, just as they were before, and the <code>save()</code> method will update the entity in the database.</p>
<p>So how do we get an existing post into the <code>EditPost</code> template? Create the following function in <code>PostCx</code>:</p>
<pre class="prettyprint linenums">
function edit(post : Post) {
Layout.render(Writer, \->
EditPost.render(Writer, post))
}</pre>
<p>That's all there is to it. When <code>http://localhost:8080/PostCx/edit</code> is accessed, Ronin will use the URL parameters
to look up the post and pass it in to the <code>edit()</code> method, which passes it in to the template.</p>
</section>
<section id="links">
<div class="page-header">
<h1>Links</h1>
</div>
<p>Let's put it all together by creating a link on the "view post" page to edit the post you're viewing. Add this
snippet to the <code>ViewPost.gst</code> template, wherever you'd like:</p>
<pre class="prettyprint linenums">
<a href="${urlFor(PostCx#edit(post))}">Edit post</a></pre>
<p>and this uses statement at the top (after the <code>extends</code> and <code>params</code> directives):</p>
<pre class="prettyprint linenums">
<% uses controller.PostCx %></pre>
<p>The target of the link is generated by the <code>urlFor()</code> method, which (like <code>redirect()</code>) takes a bound method literal
for the controller method you want the link to call. Let's see how this works. Reload the page you arrived
at after creating your new post earlier. You should see the new link appear. If you look at where the link goes, it should
be something like:</p>
<p><a href="http://localhost:8080/PostCx/edit?post=1">http://localhost:8080/PostCx/edit?post=1</a></p>
<p><code>urlFor()</code> generated a URL which, when requested, will invoke the
<code>controller.PostCx.edit()</code> function, passing in the <coe>Post</code> with the primary key 1.</p>
</section>
<section id="relationships">
<div class="page-header">
<h1>Relationships Between Entities</h1>
</div>
<p>Now let's take a look at the other table in our database - comments. Ideally we want to show all of the comments
for a post on the page where we view the post. Add the following code to the bottom of <code>ViewPost.gst</code>:</p>
<pre class="prettyprint linenums">
<% for (comment in post.Comments) { %>
<div class="comment">
<div class="commentAuthor">${comment.Name} - ${comment.Posted}</div>
<div class="commentBody">${comment.Text}</div>
</div>
<% } %></pre>
<p>This is a Gosu for loop, which is similar to a Java for loop, but with the <code>in</code> keyword in place of the <code>:</code>
character. The collection we're looping over - <code>post.Comments</code> - is of particular interest here. You'll notice that
it doesn't correspond to a column on the Post table in our schema above. Instead, it represents the Post_id
foreign key on the Comment table. <code>post.Comments</code> will return all of the Comments whose Post_id matches the ID of
the post.</p>
<p>Also note that, unlike in Java, we haven't explicitly stated the type of the comment variable. Gosu infers the
correct type for this variable because it knows that <code>post.Comments</code>
contains objects of that type.</p>
</section>
<section id="querying">
<div class="page-header">
<h1>Querying The Database</h1>
</div>
<p>Let's add one final piece to our application - a page which lists all of the posts we've created, in reverse chronological order.</p>
<p>Add the following to <code>PostCx</code>:</p>
<pre class="prettyprint linenums">uses view.AllPosts</pre>
<pre class="prettyprint linenums">
function all() {
var posts = Post.selectAll().orderBy(Post#Posted, DESC).toList()
Layout.render(Writer, \ -> AllPosts.render(Writer, posts))
}
</pre>
<p>The first line of the function declares a local variable. As in our for loop, it is not necessary to specify the type of the variable - Gosu will infer the correct type from the value you assign to it. Instead, you use the <code>var</code> keyword to declare the variable.</p>
<p>The <code>selectAll()</code> method is one of several static methods present on all types generated by Tosa.
It returns a <code>QueryResult</code> object that will return all Posts in the database (we could have used <code>select()</code>, <code>selectWhere()</code>,
or <code>selectLike()</code> if we wanted to add a WHERE clause to the query). We then apply an ordering to the query before
it's issued, using the <code>orderBy()</code> method, which takes two parameters: the property we want to sort on
and the sort direction. We specify the <code>Post#Posted</code> property, so the posts will be sorted chronologically,
and <code>DESC</code> as the sort direction so that more recent posts will appear first. (The second parameter is optional and defaults to <code>ASC</code>.) If we needed
a more complicated sort, we could have used <code>orderBySql()</code> instead, which takes a direct SQL statement to use as the
order by clause. Lastly, we materialize the results as a <code>List</code> to pass off to our template by using <code>toList()</code>. (We could
have also simply passed the <code>QueryResult</code> itself off to a template that was expecting to get an <code>Iterable</code> instead of a <code>List</code>.)</p>
<p>We then pass the list of posts to a new template, <code>AllPosts.gst</code>. Create this file and give it the following contents:</p>
<pre class="prettyprint linenums">
<%@ extends ronin.RoninTemplate %>
<%@ params(posts : List<db.model.Post>) %>
<% uses controller.PostCx %>
<div class="header">All Posts</div>
<% for(post in posts) { %>
<div class="postListEntry">
<a href="${urlFor(PostCx#viewPost(post))}">${post.Title}</a>
</div>
<% } %></pre>
<p>By this point, everything here should be familiar. Go to <a href="http://localhost:8080/PostCx/all">http://localhost:8080/PostCx/all</a> to see all the posts in the database.</p>
</section>
<section id="next_steps">
<div class="page-header">
<h1>Next Steps</h1>
</div>
<p>In this tutorial, I've shown you the basics of working with Gosu and Ronin. To learn more about Gosu, visit the
<a href="http://gosu-lang.org">Gosu website</a>. For more details
on Ronin and Tosa, dig into the <a href="docs.html">documentation</a>.</p>
<p>Here are some further exercises for extending our blog application, from easiest to most challenging:</p>
<ul>
<li>Change the layout template so that different pages have different <code><title></code> tags.</li>
<li>Implement the ability to add comments to a post.</li>
<li>Using the <code>delete()</code> method on Tosa entities, implement the ability to delete a post or comment.</li>
<li>Implement a page which displays a snippet of each post, with a link to the full post and some text indicating
how many comments have been left on the post.
</li>
<li>Using the <code>page()</code> and <code>loadPage()</code> methods on the <code>QueryResult</code>, display posts 20 at a time on the <code>AllPosts</code> page.</li>
<li>Using the <code>selectWhere()</code> method, show "previous" and "next" links on the <code>ViewPost</code> page.</li>
<li>Refactor the comments display to a separate controller method and view, and use blocks to include it in the
<code>ViewPost</code> page. Use AJAX to refresh just the comments display when the user leaves a comment.
</li>
<li>Implement user authentication by using the built-in support described <a href="docs.html#user_auth">here</a>.</li>
</ul>
<p>If you need some help with these exercises, or just want to see more examples of how to use Ronin, download
and examine the <a href="https://github.com/kprevas/ronin/tree/master/roblog">full sample RoBlog application</a>.</p>
</section>
</div>
<script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script>
<script src="http://twitter.github.com/bootstrap/assets/js/jquery.js"></script>
<script type="text/javascript">
$(window).load(function() {
var section = location.href.split('#')[1];
if(section != null) {
scrollToSection(section);
}
});
</script>
<script src="http://twitter.github.com/bootstrap/assets/js/google-code-prettify/prettify.js"></script>
<script src="http://twitter.github.com/bootstrap/assets/js/bootstrap-transition.js"></script>
<script src="http://twitter.github.com/bootstrap/assets/js/bootstrap-alert.js"></script>
<script src="http://twitter.github.com/bootstrap/assets/js/bootstrap-modal.js"></script>
<script src="http://twitter.github.com/bootstrap/assets/js/bootstrap-dropdown.js"></script>
<script src="http://twitter.github.com/bootstrap/assets/js/bootstrap-tab.js"></script>
<script src="http://twitter.github.com/bootstrap/assets/js/bootstrap-tooltip.js"></script>
<script src="http://twitter.github.com/bootstrap/assets/js/bootstrap-popover.js"></script>
<script src="http://twitter.github.com/bootstrap/assets/js/bootstrap-button.js"></script>
<script src="http://twitter.github.com/bootstrap/assets/js/bootstrap-carousel.js"></script>
<script src="http://twitter.github.com/bootstrap/assets/js/application.js"></script>
<script src="./js/site.js"></script>
</body>
</html>