├── README.md
├── static
├── assets
│ ├── destroy.png
│ ├── todos.css
│ └── vendor
│ │ ├── ember-data.js
│ │ ├── ember.js
│ │ ├── handlebars-1.0.rc.2.js
│ │ └── jquery.js
└── todos.js
├── templates
└── todo.html
└── todo.py
/README.md:
--------------------------------------------------------------------------------
1 | # What is it #
2 |
3 | A demo web application in the spirit of [TodoMVC](http://todomvc.com/) showing how to use
4 | **RethinkDB as a backend for Bottle and Ember.js applications**.
5 |
6 | As any todo application, this one implements the following functionality:
7 |
8 | * Managing database connections
9 | * List existing todos
10 | * Create new todo
11 | * Retrieve a single todo
12 | * Edit a todo or mark a todo as done
13 | * Delete a todo
14 |
15 | _Open issues_: when editing a todo, a `PUT` request is sent after each typed character. This could be improved to send a request is only once when Enter is pressed (pull requests welcome!)
16 |
17 | # Complete stack #
18 |
19 | * [Bottle](http://bottlepy.org/)
20 | * [Ember (v1.0.0-pre.4-9-g6f709b0)](http://emberjs.com)
21 | * [RethinkDB](http://www.rethinkdb.com)
22 |
23 | # Installation #
24 |
25 | ```
26 | git clone git://github.com/rethinkdb/rethinkdb-example-bottle-ember-todo.git
27 | pip install bottle
28 | pip install rethinkdb
29 | ```
30 |
31 | _Note_: If you don't have RethinkDB installed, you can follow [these instructions to get it up and running](http://www.rethinkdb.com/docs/install/).
32 |
33 | # Running the application #
34 |
35 | Firstly we'll need to create the database `todoapp` (you can override the name of the database
36 | by setting the `TODO_DB` env variable) and the table used by this app: `todos`. You can
37 | do this by running:
38 |
39 | ```
40 | python todo.py --setup
41 | ```
42 |
43 | Running the Bottle application is as simple as:
44 |
45 | ```
46 | python todo.py
47 | ```
48 |
49 | Then open a browser: .
50 |
51 |
52 | # License #
53 |
54 | This demo application is licensed under the MIT license:
55 |
--------------------------------------------------------------------------------
/static/assets/destroy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rethinkdb/rethinkdb-example-bottle-ember-todo/9d3d4e36b00e1ddaccf7b120e4548f887c5bf79e/static/assets/destroy.png
--------------------------------------------------------------------------------
/static/assets/todos.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | button {
8 | margin: 0;
9 | padding: 0;
10 | border: 0;
11 | background: none;
12 | font-size: 100%;
13 | vertical-align: baseline;
14 | font-family: inherit;
15 | color: inherit;
16 | -webkit-appearance: none;
17 | /*-moz-appearance: none;*/
18 | -ms-appearance: none;
19 | -o-appearance: none;
20 | appearance: none;
21 | }
22 |
23 | body {
24 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
25 | line-height: 1.4em;
26 | background: #eaeaea;
27 | color: #4d4d4d;
28 | width: 550px;
29 | margin: 0 auto;
30 | -webkit-font-smoothing: antialiased;
31 | -moz-font-smoothing: antialiased;
32 | -ms-font-smoothing: antialiased;
33 | -o-font-smoothing: antialiased;
34 | font-smoothing: antialiased;
35 | }
36 |
37 | #todoapp {
38 | background: #fff;
39 | background: rgba(255, 255, 255, 0.9);
40 | margin: 130px 0 40px 0;
41 | border: 1px solid #ccc;
42 | position: relative;
43 | border-top-left-radius: 2px;
44 | border-top-right-radius: 2px;
45 | box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
46 | 0 25px 50px 0 rgba(0, 0, 0, 0.15);
47 | }
48 |
49 | #todoapp:before {
50 | content: '';
51 | border-left: 1px solid #f5d6d6;
52 | border-right: 1px solid #f5d6d6;
53 | width: 2px;
54 | position: absolute;
55 | top: 0;
56 | left: 40px;
57 | height: 100%;
58 | }
59 |
60 | #todoapp input::-webkit-input-placeholder {
61 | font-style: italic;
62 | }
63 |
64 | #todoapp input:-moz-placeholder {
65 | font-style: italic;
66 | color: #a9a9a9;
67 | }
68 |
69 | #todoapp h1 {
70 | position: absolute;
71 | top: -120px;
72 | width: 100%;
73 | font-size: 70px;
74 | font-weight: bold;
75 | text-align: center;
76 | color: #b3b3b3;
77 | color: rgba(255, 255, 255, 0.3);
78 | text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
79 | -webkit-text-rendering: optimizeLegibility;
80 | -moz-text-rendering: optimizeLegibility;
81 | -ms-text-rendering: optimizeLegibility;
82 | -o-text-rendering: optimizeLegibility;
83 | text-rendering: optimizeLegibility;
84 | }
85 |
86 | #header {
87 | padding-top: 15px;
88 | border-radius: inherit;
89 | }
90 |
91 | #header:before {
92 | content: '';
93 | position: absolute;
94 | top: 0;
95 | right: 0;
96 | left: 0;
97 | height: 15px;
98 | z-index: 2;
99 | border-bottom: 1px solid #6c615c;
100 | background: #8d7d77;
101 | background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
102 | background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
103 | background: -moz-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
104 | background: -o-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
105 | background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
106 | background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
107 | filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
108 | border-top-left-radius: 1px;
109 | border-top-right-radius: 1px;
110 | }
111 |
112 | #new-todo,
113 | .edit {
114 | position: relative;
115 | margin: 0;
116 | width: 100%;
117 | font-size: 24px;
118 | font-family: inherit;
119 | line-height: 1.4em;
120 | border: 0;
121 | outline: none;
122 | color: inherit;
123 | padding: 6px;
124 | border: 1px solid #999;
125 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
126 | -webkit-box-sizing: border-box;
127 | -moz-box-sizing: border-box;
128 | -ms-box-sizing: border-box;
129 | -o-box-sizing: border-box;
130 | box-sizing: border-box;
131 | -webkit-font-smoothing: antialiased;
132 | -moz-font-smoothing: antialiased;
133 | -ms-font-smoothing: antialiased;
134 | -o-font-smoothing: antialiased;
135 | font-smoothing: antialiased;
136 | }
137 |
138 | #new-todo {
139 | padding: 16px 16px 16px 60px;
140 | border: none;
141 | background: rgba(0, 0, 0, 0.02);
142 | z-index: 2;
143 | box-shadow: none;
144 | }
145 |
146 | #main {
147 | position: relative;
148 | z-index: 2;
149 | border-top: 1px dotted #adadad;
150 | }
151 |
152 | label[for='toggle-all'] {
153 | display: none;
154 | }
155 |
156 | #toggle-all {
157 | position: absolute;
158 | top: -42px;
159 | left: -4px;
160 | width: 40px;
161 | text-align: center;
162 | border: none; /* Mobile Safari */
163 | }
164 |
165 | #toggle-all:before {
166 | content: '»';
167 | font-size: 28px;
168 | color: #d9d9d9;
169 | padding: 0 25px 7px;
170 | }
171 |
172 | #toggle-all:checked:before {
173 | color: #737373;
174 | }
175 |
176 | #todo-list {
177 | margin: 0;
178 | padding: 0;
179 | list-style: none;
180 | }
181 |
182 | #todo-list li {
183 | position: relative;
184 | font-size: 24px;
185 | border-bottom: 1px dotted #ccc;
186 | }
187 |
188 | #todo-list li:last-child {
189 | border-bottom: none;
190 | }
191 |
192 | #todo-list li.editing {
193 | border-bottom: none;
194 | padding: 0;
195 | }
196 |
197 | #todo-list li.editing .edit {
198 | display: block;
199 | width: 506px;
200 | padding: 13px 17px 12px 17px;
201 | margin: 0 0 0 43px;
202 | }
203 |
204 | #todo-list li.editing .view {
205 | display: none;
206 | }
207 |
208 | #todo-list li .toggle {
209 | text-align: center;
210 | width: 40px;
211 | /* auto, since non-WebKit browsers doesn't support input styling */
212 | height: auto;
213 | position: absolute;
214 | top: 0;
215 | bottom: 0;
216 | margin: auto 0;
217 | border: none; /* Mobile Safari */
218 | -webkit-appearance: none;
219 | /*-moz-appearance: none;*/
220 | -ms-appearance: none;
221 | -o-appearance: none;
222 | appearance: none;
223 | }
224 |
225 | #todo-list li .toggle:after {
226 | content: '✔';
227 | line-height: 43px; /* 40 + a couple of pixels visual adjustment */
228 | font-size: 20px;
229 | color: #d9d9d9;
230 | text-shadow: 0 -1px 0 #bfbfbf;
231 | }
232 |
233 | #todo-list li .toggle:checked:after {
234 | color: #85ada7;
235 | text-shadow: 0 1px 0 #669991;
236 | bottom: 1px;
237 | position: relative;
238 | }
239 |
240 | #todo-list li label {
241 | word-break: break-word;
242 | padding: 15px;
243 | margin-left: 45px;
244 | display: block;
245 | line-height: 1.2;
246 | -webkit-transition: color 0.4s;
247 | -moz-transition: color 0.4s;
248 | -ms-transition: color 0.4s;
249 | -o-transition: color 0.4s;
250 | transition: color 0.4s;
251 | }
252 |
253 | #todo-list li.completed label {
254 | color: #a9a9a9;
255 | text-decoration: line-through;
256 | }
257 |
258 | #todo-list li .destroy {
259 | display: none;
260 | position: absolute;
261 | top: 0;
262 | right: 10px;
263 | bottom: 0;
264 | width: 40px;
265 | height: 40px;
266 | margin: auto 0;
267 | font-size: 22px;
268 | color: #a88a8a;
269 | -webkit-transition: all 0.2s;
270 | -moz-transition: all 0.2s;
271 | -ms-transition: all 0.2s;
272 | -o-transition: all 0.2s;
273 | transition: all 0.2s;
274 | }
275 |
276 | #todo-list li .destroy:hover {
277 | text-shadow: 0 0 1px #000,
278 | 0 0 10px rgba(199, 107, 107, 0.8);
279 | -webkit-transform: scale(1.3);
280 | -moz-transform: scale(1.3);
281 | -ms-transform: scale(1.3);
282 | -o-transform: scale(1.3);
283 | transform: scale(1.3);
284 | }
285 |
286 | #todo-list li .destroy:after {
287 | content: '✖';
288 | }
289 |
290 | #todo-list li:hover .destroy {
291 | display: block;
292 | }
293 |
294 | #todo-list li .edit {
295 | display: none;
296 | }
297 |
298 | #todo-list li.editing:last-child {
299 | margin-bottom: -1px;
300 | }
301 |
302 | #footer {
303 | color: #777;
304 | padding: 0 15px;
305 | position: absolute;
306 | right: 0;
307 | bottom: -31px;
308 | left: 0;
309 | height: 20px;
310 | z-index: 1;
311 | text-align: center;
312 | }
313 |
314 | #footer:before {
315 | content: '';
316 | position: absolute;
317 | right: 0;
318 | bottom: 31px;
319 | left: 0;
320 | height: 50px;
321 | z-index: -1;
322 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
323 | 0 6px 0 -3px rgba(255, 255, 255, 0.8),
324 | 0 7px 1px -3px rgba(0, 0, 0, 0.3),
325 | 0 43px 0 -6px rgba(255, 255, 255, 0.8),
326 | 0 44px 2px -6px rgba(0, 0, 0, 0.2);
327 | }
328 |
329 | #todo-count {
330 | float: left;
331 | text-align: left;
332 | }
333 |
334 | #filters {
335 | margin: 0;
336 | padding: 0;
337 | list-style: none;
338 | position: absolute;
339 | right: 0;
340 | left: 0;
341 | }
342 |
343 | #filters li {
344 | display: inline;
345 | }
346 |
347 | #filters li a {
348 | color: #83756f;
349 | margin: 2px;
350 | text-decoration: none;
351 | }
352 |
353 | #filters li a.selected {
354 | font-weight: bold;
355 | }
356 |
357 | #clear-completed {
358 | float: right;
359 | position: relative;
360 | line-height: 20px;
361 | text-decoration: none;
362 | background: rgba(0, 0, 0, 0.1);
363 | font-size: 11px;
364 | padding: 0 10px;
365 | border-radius: 3px;
366 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
367 | }
368 |
369 | #clear-completed:hover {
370 | background: rgba(0, 0, 0, 0.15);
371 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
372 | }
373 |
374 | #info {
375 | margin: 65px auto 0;
376 | color: #a6a6a6;
377 | font-size: 12px;
378 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
379 | text-align: center;
380 | }
381 |
382 | #info a {
383 | color: inherit;
384 | }
385 |
386 | /*
387 | Hack to remove background from Mobile Safari.
388 | Can't use it globally since it destroys checkboxes in Firefox and Opera
389 | */
390 | @media screen and (-webkit-min-device-pixel-ratio:0) {
391 | #toggle-all,
392 | #todo-list li .toggle {
393 | background: none;
394 | }
395 |
396 | #todo-list li .toggle {
397 | height: 40px;
398 | }
399 |
400 | #toggle-all {
401 | top: -56px;
402 | left: -15px;
403 | width: 65px;
404 | height: 41px;
405 | -webkit-transform: rotate(90deg);
406 | transform: rotate(90deg);
407 | -webkit-appearance: none;
408 | appearance: none;
409 | }
410 | }
411 |
412 | .hidden{
413 | display:none;
414 | }
--------------------------------------------------------------------------------
/static/assets/vendor/handlebars-1.0.rc.2.js:
--------------------------------------------------------------------------------
1 | // lib/handlebars/base.js
2 |
3 | /*jshint eqnull:true*/
4 | this.Handlebars = {};
5 |
6 | (function(Handlebars) {
7 |
8 | Handlebars.VERSION = "1.0.rc.2";
9 |
10 | Handlebars.helpers = {};
11 | Handlebars.partials = {};
12 |
13 | Handlebars.registerHelper = function(name, fn, inverse) {
14 | if(inverse) { fn.not = inverse; }
15 | this.helpers[name] = fn;
16 | };
17 |
18 | Handlebars.registerPartial = function(name, str) {
19 | this.partials[name] = str;
20 | };
21 |
22 | Handlebars.registerHelper('helperMissing', function(arg) {
23 | if(arguments.length === 2) {
24 | return undefined;
25 | } else {
26 | throw new Error("Could not find property '" + arg + "'");
27 | }
28 | });
29 |
30 | var toString = Object.prototype.toString, functionType = "[object Function]";
31 |
32 | Handlebars.registerHelper('blockHelperMissing', function(context, options) {
33 | var inverse = options.inverse || function() {}, fn = options.fn;
34 |
35 |
36 | var ret = "";
37 | var type = toString.call(context);
38 |
39 | if(type === functionType) { context = context.call(this); }
40 |
41 | if(context === true) {
42 | return fn(this);
43 | } else if(context === false || context == null) {
44 | return inverse(this);
45 | } else if(type === "[object Array]") {
46 | if(context.length > 0) {
47 | return Handlebars.helpers.each(context, options);
48 | } else {
49 | return inverse(this);
50 | }
51 | } else {
52 | return fn(context);
53 | }
54 | });
55 |
56 | Handlebars.K = function() {};
57 |
58 | Handlebars.createFrame = Object.create || function(object) {
59 | Handlebars.K.prototype = object;
60 | var obj = new Handlebars.K();
61 | Handlebars.K.prototype = null;
62 | return obj;
63 | };
64 |
65 | Handlebars.logger = {
66 | DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
67 |
68 | methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'},
69 |
70 | // can be overridden in the host environment
71 | log: function(level, obj) {
72 | if (Handlebars.logger.level <= level) {
73 | var method = Handlebars.logger.methodMap[level];
74 | if (typeof console !== 'undefined' && console[method]) {
75 | console[method].call(console, obj);
76 | }
77 | }
78 | }
79 | };
80 |
81 | Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); };
82 |
83 | Handlebars.registerHelper('each', function(context, options) {
84 | var fn = options.fn, inverse = options.inverse;
85 | var i = 0, ret = "", data;
86 |
87 | if (options.data) {
88 | data = Handlebars.createFrame(options.data);
89 | }
90 |
91 | if(context && typeof context === 'object') {
92 | if(context instanceof Array){
93 | for(var j = context.length; i 2) {
301 | expected.push("'" + this.terminals_[p] + "'");
302 | }
303 | if (this.lexer.showPosition) {
304 | errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'";
305 | } else {
306 | errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'");
307 | }
308 | this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
309 | }
310 | }
311 | if (action[0] instanceof Array && action.length > 1) {
312 | throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol);
313 | }
314 | switch (action[0]) {
315 | case 1:
316 | stack.push(symbol);
317 | vstack.push(this.lexer.yytext);
318 | lstack.push(this.lexer.yylloc);
319 | stack.push(action[1]);
320 | symbol = null;
321 | if (!preErrorSymbol) {
322 | yyleng = this.lexer.yyleng;
323 | yytext = this.lexer.yytext;
324 | yylineno = this.lexer.yylineno;
325 | yyloc = this.lexer.yylloc;
326 | if (recovering > 0)
327 | recovering--;
328 | } else {
329 | symbol = preErrorSymbol;
330 | preErrorSymbol = null;
331 | }
332 | break;
333 | case 2:
334 | len = this.productions_[action[1]][1];
335 | yyval.$ = vstack[vstack.length - len];
336 | yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column};
337 | if (ranges) {
338 | yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]];
339 | }
340 | r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
341 | if (typeof r !== "undefined") {
342 | return r;
343 | }
344 | if (len) {
345 | stack = stack.slice(0, -1 * len * 2);
346 | vstack = vstack.slice(0, -1 * len);
347 | lstack = lstack.slice(0, -1 * len);
348 | }
349 | stack.push(this.productions_[action[1]][0]);
350 | vstack.push(yyval.$);
351 | lstack.push(yyval._$);
352 | newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
353 | stack.push(newState);
354 | break;
355 | case 3:
356 | return true;
357 | }
358 | }
359 | return true;
360 | }
361 | };
362 | /* Jison generated lexer */
363 | var lexer = (function(){
364 | var lexer = ({EOF:1,
365 | parseError:function parseError(str, hash) {
366 | if (this.yy.parser) {
367 | this.yy.parser.parseError(str, hash);
368 | } else {
369 | throw new Error(str);
370 | }
371 | },
372 | setInput:function (input) {
373 | this._input = input;
374 | this._more = this._less = this.done = false;
375 | this.yylineno = this.yyleng = 0;
376 | this.yytext = this.matched = this.match = '';
377 | this.conditionStack = ['INITIAL'];
378 | this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
379 | if (this.options.ranges) this.yylloc.range = [0,0];
380 | this.offset = 0;
381 | return this;
382 | },
383 | input:function () {
384 | var ch = this._input[0];
385 | this.yytext += ch;
386 | this.yyleng++;
387 | this.offset++;
388 | this.match += ch;
389 | this.matched += ch;
390 | var lines = ch.match(/(?:\r\n?|\n).*/g);
391 | if (lines) {
392 | this.yylineno++;
393 | this.yylloc.last_line++;
394 | } else {
395 | this.yylloc.last_column++;
396 | }
397 | if (this.options.ranges) this.yylloc.range[1]++;
398 |
399 | this._input = this._input.slice(1);
400 | return ch;
401 | },
402 | unput:function (ch) {
403 | var len = ch.length;
404 | var lines = ch.split(/(?:\r\n?|\n)/g);
405 |
406 | this._input = ch + this._input;
407 | this.yytext = this.yytext.substr(0, this.yytext.length-len-1);
408 | //this.yyleng -= len;
409 | this.offset -= len;
410 | var oldLines = this.match.split(/(?:\r\n?|\n)/g);
411 | this.match = this.match.substr(0, this.match.length-1);
412 | this.matched = this.matched.substr(0, this.matched.length-1);
413 |
414 | if (lines.length-1) this.yylineno -= lines.length-1;
415 | var r = this.yylloc.range;
416 |
417 | this.yylloc = {first_line: this.yylloc.first_line,
418 | last_line: this.yylineno+1,
419 | first_column: this.yylloc.first_column,
420 | last_column: lines ?
421 | (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length:
422 | this.yylloc.first_column - len
423 | };
424 |
425 | if (this.options.ranges) {
426 | this.yylloc.range = [r[0], r[0] + this.yyleng - len];
427 | }
428 | return this;
429 | },
430 | more:function () {
431 | this._more = true;
432 | return this;
433 | },
434 | less:function (n) {
435 | this.unput(this.match.slice(n));
436 | },
437 | pastInput:function () {
438 | var past = this.matched.substr(0, this.matched.length - this.match.length);
439 | return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
440 | },
441 | upcomingInput:function () {
442 | var next = this.match;
443 | if (next.length < 20) {
444 | next += this._input.substr(0, 20-next.length);
445 | }
446 | return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
447 | },
448 | showPosition:function () {
449 | var pre = this.pastInput();
450 | var c = new Array(pre.length + 1).join("-");
451 | return pre + this.upcomingInput() + "\n" + c+"^";
452 | },
453 | next:function () {
454 | if (this.done) {
455 | return this.EOF;
456 | }
457 | if (!this._input) this.done = true;
458 |
459 | var token,
460 | match,
461 | tempMatch,
462 | index,
463 | col,
464 | lines;
465 | if (!this._more) {
466 | this.yytext = '';
467 | this.match = '';
468 | }
469 | var rules = this._currentRules();
470 | for (var i=0;i < rules.length; i++) {
471 | tempMatch = this._input.match(this.rules[rules[i]]);
472 | if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
473 | match = tempMatch;
474 | index = i;
475 | if (!this.options.flex) break;
476 | }
477 | }
478 | if (match) {
479 | lines = match[0].match(/(?:\r\n?|\n).*/g);
480 | if (lines) this.yylineno += lines.length;
481 | this.yylloc = {first_line: this.yylloc.last_line,
482 | last_line: this.yylineno+1,
483 | first_column: this.yylloc.last_column,
484 | last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length};
485 | this.yytext += match[0];
486 | this.match += match[0];
487 | this.matches = match;
488 | this.yyleng = this.yytext.length;
489 | if (this.options.ranges) {
490 | this.yylloc.range = [this.offset, this.offset += this.yyleng];
491 | }
492 | this._more = false;
493 | this._input = this._input.slice(match[0].length);
494 | this.matched += match[0];
495 | token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]);
496 | if (this.done && this._input) this.done = false;
497 | if (token) return token;
498 | else return;
499 | }
500 | if (this._input === "") {
501 | return this.EOF;
502 | } else {
503 | return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
504 | {text: "", token: null, line: this.yylineno});
505 | }
506 | },
507 | lex:function lex() {
508 | var r = this.next();
509 | if (typeof r !== 'undefined') {
510 | return r;
511 | } else {
512 | return this.lex();
513 | }
514 | },
515 | begin:function begin(condition) {
516 | this.conditionStack.push(condition);
517 | },
518 | popState:function popState() {
519 | return this.conditionStack.pop();
520 | },
521 | _currentRules:function _currentRules() {
522 | return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
523 | },
524 | topState:function () {
525 | return this.conditionStack[this.conditionStack.length-2];
526 | },
527 | pushState:function begin(condition) {
528 | this.begin(condition);
529 | }});
530 | lexer.options = {};
531 | lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
532 |
533 | var YYSTATE=YY_START
534 | switch($avoiding_name_collisions) {
535 | case 0:
536 | if(yy_.yytext.slice(-1) !== "\\") this.begin("mu");
537 | if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu");
538 | if(yy_.yytext) return 14;
539 |
540 | break;
541 | case 1: return 14;
542 | break;
543 | case 2:
544 | if(yy_.yytext.slice(-1) !== "\\") this.popState();
545 | if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1);
546 | return 14;
547 |
548 | break;
549 | case 3: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15;
550 | break;
551 | case 4: this.begin("par"); return 24;
552 | break;
553 | case 5: return 16;
554 | break;
555 | case 6: return 20;
556 | break;
557 | case 7: return 19;
558 | break;
559 | case 8: return 19;
560 | break;
561 | case 9: return 23;
562 | break;
563 | case 10: return 23;
564 | break;
565 | case 11: this.popState(); this.begin('com');
566 | break;
567 | case 12: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15;
568 | break;
569 | case 13: return 22;
570 | break;
571 | case 14: return 36;
572 | break;
573 | case 15: return 35;
574 | break;
575 | case 16: return 35;
576 | break;
577 | case 17: return 39;
578 | break;
579 | case 18: /*ignore whitespace*/
580 | break;
581 | case 19: this.popState(); return 18;
582 | break;
583 | case 20: this.popState(); return 18;
584 | break;
585 | case 21: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 30;
586 | break;
587 | case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 30;
588 | break;
589 | case 23: yy_.yytext = yy_.yytext.substr(1); return 28;
590 | break;
591 | case 24: return 32;
592 | break;
593 | case 25: return 32;
594 | break;
595 | case 26: return 31;
596 | break;
597 | case 27: return 35;
598 | break;
599 | case 28: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 35;
600 | break;
601 | case 29: return 'INVALID';
602 | break;
603 | case 30: /*ignore whitespace*/
604 | break;
605 | case 31: this.popState(); return 37;
606 | break;
607 | case 32: return 5;
608 | break;
609 | }
610 | };
611 | lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[} ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:\s+)/,/^(?:[a-zA-Z0-9_$-/]+)/,/^(?:$)/];
612 | lexer.conditions = {"mu":{"rules":[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,32],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"par":{"rules":[30,31],"inclusive":false},"INITIAL":{"rules":[0,1,32],"inclusive":true}};
613 | return lexer;})()
614 | parser.lexer = lexer;
615 | function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser;
616 | return new Parser;
617 | })();;
618 | // lib/handlebars/compiler/base.js
619 | Handlebars.Parser = handlebars;
620 |
621 | Handlebars.parse = function(string) {
622 | Handlebars.Parser.yy = Handlebars.AST;
623 | return Handlebars.Parser.parse(string);
624 | };
625 |
626 | Handlebars.print = function(ast) {
627 | return new Handlebars.PrintVisitor().accept(ast);
628 | };;
629 | // lib/handlebars/compiler/ast.js
630 | (function() {
631 |
632 | Handlebars.AST = {};
633 |
634 | Handlebars.AST.ProgramNode = function(statements, inverse) {
635 | this.type = "program";
636 | this.statements = statements;
637 | if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
638 | };
639 |
640 | Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
641 | this.type = "mustache";
642 | this.escaped = !unescaped;
643 | this.hash = hash;
644 |
645 | var id = this.id = rawParams[0];
646 | var params = this.params = rawParams.slice(1);
647 |
648 | // a mustache is an eligible helper if:
649 | // * its id is simple (a single part, not `this` or `..`)
650 | var eligibleHelper = this.eligibleHelper = id.isSimple;
651 |
652 | // a mustache is definitely a helper if:
653 | // * it is an eligible helper, and
654 | // * it has at least one parameter or hash segment
655 | this.isHelper = eligibleHelper && (params.length || hash);
656 |
657 | // if a mustache is an eligible helper but not a definite
658 | // helper, it is ambiguous, and will be resolved in a later
659 | // pass or at runtime.
660 | };
661 |
662 | Handlebars.AST.PartialNode = function(partialName, context) {
663 | this.type = "partial";
664 | this.partialName = partialName;
665 | this.context = context;
666 | };
667 |
668 | var verifyMatch = function(open, close) {
669 | if(open.original !== close.original) {
670 | throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
671 | }
672 | };
673 |
674 | Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
675 | verifyMatch(mustache.id, close);
676 | this.type = "block";
677 | this.mustache = mustache;
678 | this.program = program;
679 | this.inverse = inverse;
680 |
681 | if (this.inverse && !this.program) {
682 | this.isInverse = true;
683 | }
684 | };
685 |
686 | Handlebars.AST.ContentNode = function(string) {
687 | this.type = "content";
688 | this.string = string;
689 | };
690 |
691 | Handlebars.AST.HashNode = function(pairs) {
692 | this.type = "hash";
693 | this.pairs = pairs;
694 | };
695 |
696 | Handlebars.AST.IdNode = function(parts) {
697 | this.type = "ID";
698 | this.original = parts.join(".");
699 |
700 | var dig = [], depth = 0;
701 |
702 | for(var i=0,l=parts.length; i": ">",
782 | '"': """,
783 | "'": "'",
784 | "`": "`"
785 | };
786 |
787 | var badChars = /[&<>"'`]/g;
788 | var possible = /[&<>"'`]/;
789 |
790 | var escapeChar = function(chr) {
791 | return escape[chr] || "&";
792 | };
793 |
794 | Handlebars.Utils = {
795 | escapeExpression: function(string) {
796 | // don't escape SafeStrings, since they're already safe
797 | if (string instanceof Handlebars.SafeString) {
798 | return string.toString();
799 | } else if (string == null || string === false) {
800 | return "";
801 | }
802 |
803 | if(!possible.test(string)) { return string; }
804 | return string.replace(badChars, escapeChar);
805 | },
806 |
807 | isEmpty: function(value) {
808 | if (!value && value !== 0) {
809 | return true;
810 | } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
811 | return true;
812 | } else {
813 | return false;
814 | }
815 | }
816 | };
817 | })();;
818 | // lib/handlebars/compiler/compiler.js
819 |
820 | /*jshint eqnull:true*/
821 | Handlebars.Compiler = function() {};
822 | Handlebars.JavaScriptCompiler = function() {};
823 |
824 | (function(Compiler, JavaScriptCompiler) {
825 | // the foundHelper register will disambiguate helper lookup from finding a
826 | // function in a context. This is necessary for mustache compatibility, which
827 | // requires that context functions in blocks are evaluated by blockHelperMissing,
828 | // and then proceed as if the resulting value was provided to blockHelperMissing.
829 |
830 | Compiler.prototype = {
831 | compiler: Compiler,
832 |
833 | disassemble: function() {
834 | var opcodes = this.opcodes, opcode, out = [], params, param;
835 |
836 | for (var i=0, l=opcodes.length; i 0) {
1295 | this.source[1] = this.source[1] + ", " + locals.join(", ");
1296 | }
1297 |
1298 | // Generate minimizer alias mappings
1299 | if (!this.isChild) {
1300 | var aliases = [];
1301 | for (var alias in this.context.aliases) {
1302 | this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
1303 | }
1304 | }
1305 |
1306 | if (this.source[1]) {
1307 | this.source[1] = "var " + this.source[1].substring(2) + ";";
1308 | }
1309 |
1310 | // Merge children
1311 | if (!this.isChild) {
1312 | this.source[1] += '\n' + this.context.programs.join('\n') + '\n';
1313 | }
1314 |
1315 | if (!this.environment.isSimple) {
1316 | this.source.push("return buffer;");
1317 | }
1318 |
1319 | var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];
1320 |
1321 | for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
1759 | return "stack" + this.stackSlot;
1760 | },
1761 |
1762 | popStack: function() {
1763 | var item = this.compileStack.pop();
1764 |
1765 | if (item instanceof Literal) {
1766 | return item.value;
1767 | } else {
1768 | this.stackSlot--;
1769 | return item;
1770 | }
1771 | },
1772 |
1773 | topStack: function() {
1774 | var item = this.compileStack[this.compileStack.length - 1];
1775 |
1776 | if (item instanceof Literal) {
1777 | return item.value;
1778 | } else {
1779 | return item;
1780 | }
1781 | },
1782 |
1783 | quotedString: function(str) {
1784 | return '"' + str
1785 | .replace(/\\/g, '\\\\')
1786 | .replace(/"/g, '\\"')
1787 | .replace(/\n/g, '\\n')
1788 | .replace(/\r/g, '\\r') + '"';
1789 | },
1790 |
1791 | setupHelper: function(paramSize, name) {
1792 | var params = [];
1793 | this.setupParams(paramSize, params);
1794 | var foundHelper = this.nameLookup('helpers', name, 'helper');
1795 |
1796 | return {
1797 | params: params,
1798 | name: foundHelper,
1799 | callParams: ["depth0"].concat(params).join(", "),
1800 | helperMissingParams: ["depth0", this.quotedString(name)].concat(params).join(", ")
1801 | };
1802 | },
1803 |
1804 | // the params and contexts arguments are passed in arrays
1805 | // to fill in
1806 | setupParams: function(paramSize, params) {
1807 | var options = [], contexts = [], types = [], param, inverse, program;
1808 |
1809 | options.push("hash:" + this.popStack());
1810 |
1811 | inverse = this.popStack();
1812 | program = this.popStack();
1813 |
1814 | // Avoid setting fn and inverse if neither are set. This allows
1815 | // helpers to do a check for `if (options.fn)`
1816 | if (program || inverse) {
1817 | if (!program) {
1818 | this.context.aliases.self = "this";
1819 | program = "self.noop";
1820 | }
1821 |
1822 | if (!inverse) {
1823 | this.context.aliases.self = "this";
1824 | inverse = "self.noop";
1825 | }
1826 |
1827 | options.push("inverse:" + inverse);
1828 | options.push("fn:" + program);
1829 | }
1830 |
1831 | for(var i=0; i%@ %@ left'.fmt(remaining, plural);
105 | }.property('remaining'),
106 |
107 | completed: function() {
108 | return this.filterProperty('isCompleted', true).get('length');
109 | }.property('@each.isCompleted'),
110 |
111 | hasCompleted: function() {
112 | return this.get('completed') > 0;
113 | }.property('completed'),
114 |
115 | allAreDone: function( key, value ) {
116 | if ( value !== undefined ) {
117 | this.setEach( 'isCompleted', value );
118 | return value;
119 | } else {
120 | return !!this.get( 'length' ) &&
121 | this.everyProperty( 'isCompleted', true );
122 | }
123 | }.property( '@each.isCompleted' )
124 | });
125 |
126 | Todos.TodoController = Ember.ObjectController.extend({
127 | isEditing: false,
128 |
129 | editTodo: function() {
130 | this.set('isEditing', true);
131 | },
132 |
133 | removeTodo: function() {
134 | var todo = this.get('model');
135 |
136 | todo.deleteRecord();
137 | todo.get('store').commit();
138 | }
139 | });
140 |
141 | // ember.js views
142 |
143 | // todo view
144 | Todos.TodoView = Ember.View.extend({
145 | tagName: 'li',
146 | classNameBindings: ['todo.isCompleted:completed', 'isEditing:editing'],
147 |
148 | doubleClick: function(event) {
149 | this.set('isEditing', true);
150 | }
151 | });
152 |
153 | // edit todo view
154 | Todos.EditTodoView = Ember.TextField.extend({
155 | classNames: ['edit'],
156 |
157 | valueBinding: 'todo.title',
158 |
159 | change: function() {
160 | var value = this.get('value');
161 |
162 | if (Ember.isEmpty(value)) {
163 | this.get('controller').removeTodo();
164 | }
165 | },
166 |
167 | focusOut: function() {
168 | this.set('controller.isEditing', false);
169 | },
170 |
171 | insertNewline: function() {
172 | this.set('controller.isEditing', false);
173 | },
174 |
175 | didInsertElement: function() {
176 | this.$().focus();
177 | }
178 | });
179 |
--------------------------------------------------------------------------------
/templates/todo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ember.js • TodoMVC
7 |
8 |
9 |
10 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/todo.py:
--------------------------------------------------------------------------------
1 | # A demo web application in the spirit of
2 | # [TodoMVC](http://addyosmani.github.com/todomvc/) showing how to use
3 | # **RethinkDB as a backend for Bottle and Ember.js applications**.
4 | #
5 | # For details about the complete stack, installation, and running the
6 | # app see the
7 | # [README](https://github.com/rethinkdb/rethinkdb-example-bottle-ember-todo).
8 | import argparse
9 | import json
10 | import os
11 | import socket
12 |
13 | import bottle
14 | from bottle import static_file, request
15 |
16 | import rethinkdb as r
17 | from rethinkdb.errors import RqlRuntimeError, RqlDriverError
18 |
19 | #### Connection details
20 |
21 | # We will use these settings later in the code to connect to the
22 | # RethinkDB server.
23 | RDB_HOST = os.getenv('RDB_HOST', 'localhost')
24 | RDB_PORT = os.getenv('RDB_PORT', 28015)
25 | TODO_DB = os.getenv('TODO_DB', 'todoapp')
26 |
27 | #### Setting up the app database
28 |
29 | # The app will use a table `todos` in the database specified by the
30 | # `TODO_DB` variable (defaults to `todoapp`). We'll create the database and table here using
31 | # [`db_create`](http://www.rethinkdb.com/api/python/db_create/)
32 | # and
33 | # [`table_create`](http://www.rethinkdb.com/api/python/table_create/) commands.
34 | def dbSetup():
35 | connection = r.connect(host=RDB_HOST, port=RDB_PORT)
36 | try:
37 | r.db_create(TODO_DB).run(connection)
38 | r.db(TODO_DB).table_create('todos').run(connection)
39 | print ('Database setup completed. Now run the app without --setup: '
40 | '`python todo.py`')
41 | except RqlRuntimeError:
42 | print ('App database already exists. Run the app without --setup: '
43 | '`python todo.py`')
44 | finally:
45 | connection.close()
46 |
47 |
48 | #### Managing connections
49 |
50 | # The pattern we're using for managing database connections is to have **a connection per request**.
51 | # We're using Bottle's `@bottle.hook('before_request')` and `@bottle.hook('after_request')` for
52 | # [opening a database connection](http://www.rethinkdb.com/api/python/connect/) and
53 | # [closing it](http://www.rethinkdb.com/api/python/close/) respectively.
54 |
55 | @bottle.hook('before_request')
56 | def before_request():
57 | if request.path.startswith('/static/'):
58 | return
59 | try:
60 | bottle.local.rdb_connection = r.connect(RDB_HOST, RDB_PORT,
61 | TODO_DB)
62 | except RqlDriverError:
63 | bottle.abort(503, "No database connection could be established.")
64 |
65 | @bottle.hook('after_request')
66 | def after_request():
67 | if request.path.startswith('/static/'):
68 | return
69 | bottle.local.rdb_connection.close()
70 |
71 | #### Listing existing todos
72 |
73 | # To retrieve all existing tasks, we use the
74 | # [`r.table`](http://www.rethinkdb.com/api/python/table/)
75 | # command to query the database in response to a GET request from the
76 | # browser. When `table(table_name)` isn't followed by an additional
77 | # command, it returns all documents in the table.
78 | #
79 | # Running the query returns an iterator that automatically streams
80 | # data from the server in efficient batches.
81 | @bottle.get("/todos")
82 | def get_todos():
83 | selection = list(r.table('todos').run(bottle.local.rdb_connection))
84 | return json.dumps({'todos': selection})
85 |
86 | #### Creating a todo
87 |
88 | # We will create a new todo in response to a POST request to `/todos`
89 | # with a JSON payload using
90 | # [`table.insert`](http://www.rethinkdb.com/api/python/insert/).
91 | #
92 | # The `insert` operation returns a single object specifying the number
93 | # of successfully created objects and their corresponding IDs:
94 | #
95 | # ```
96 | # {
97 | # "inserted": 1,
98 | # "errors": 0,
99 | # "generated_keys": [
100 | # "773666ac-841a-44dc-97b7-b6f3931e9b9f"
101 | # ]
102 | # }
103 | # ```
104 | @bottle.post("/todos")
105 | def new_todo():
106 | todo = request.json['todo']
107 | inserted = (r.table('todos').insert(todo)
108 | .run(bottle.local.rdb_connection))
109 | todo['id'] = inserted['generated_keys'][0]
110 | return json.dumps({'todo': todo})
111 |
112 |
113 | #### Retrieving a single todo
114 |
115 | # Every new task gets assigned a unique ID. The browser can retrieve
116 | # a specific task by GETing `/todos/`. To query the database
117 | # for a single document by its ID, we use the
118 | # [`get`](http://www.rethinkdb.com/api/python/get/)
119 | # command.
120 | #
121 | # Using a task's ID will prove more useful when we decide to update
122 | # it, mark it completed, or delete it.
123 | @bottle.get("/todos/")
124 | def get_todo(todo_id):
125 | todo = r.table('todos').get(todo_id).run(bottle.local.rdb_connection)
126 | return json.dumps({'todo': todo})
127 |
128 | #### Editing/Updating a task
129 |
130 | # Updating a todo (editing it or marking it completed) is performed on
131 | # a `PUT` request. To save the updated todo we'll do a
132 | # [`replace`](http://www.rethinkdb.com/api/python/replace/).
133 | @bottle.put("/todos/")
134 | def update_todo(todo_id):
135 | todo = {'id': todo_id}
136 | todo.update(request.json['todo'])
137 | return json.dumps(r.table('todos')
138 | .get(todo_id)
139 | .replace(todo)
140 | .run(bottle.local.rdb_connection))
141 |
142 | # If you'd like the update operation to happen as the result of a
143 | # `PATCH` request (carrying only the updated fields), you can use the
144 | # [`update`](http://www.rethinkdb.com/api/python/update/)
145 | # command, which will merge the JSON object stored in the database
146 | # with the new one.
147 | @bottle.route("/todos/", method='PATCH')
148 | def patch_todo(todo_id):
149 | return json.dumps(r.table('todos')
150 | .get(todo_id)
151 | .update(request.json['todo'])
152 | .run(bottle.local.rdb_connection))
153 |
154 |
155 | #### Deleting a task
156 |
157 | # To delete a todo item we'll call a
158 | # [`delete`](http://www.rethinkdb.com/api/python/delete/)
159 | # command on a `DELETE /todos/` request.
160 | @bottle.delete("/todos/")
161 | def delete_todo(todo_id):
162 | return json.dumps(r.table('todos')
163 | .get(todo_id)
164 | .delete()
165 | .run(bottle.local.rdb_connection))
166 |
167 | @bottle.get("/")
168 | def show_todos():
169 | return static_file('todo.html', root='templates/',
170 | mimetype='text/html')
171 |
172 | @bottle.route('/static/', method='GET')
173 | @bottle.route('/static/', method='HEAD')
174 | def send_static(filename):
175 | return static_file(filename, root='static/')
176 |
177 | if __name__ == "__main__":
178 | parser = argparse.ArgumentParser(description='Run the Bottle todo app')
179 | parser.add_argument('--setup', dest='run_setup', action='store_true')
180 |
181 | args = parser.parse_args()
182 | if args.run_setup:
183 | dbSetup()
184 | else:
185 | bottle.run(host='localhost', port=5000, debug=True, reloader=True)
186 |
187 |
188 | # ### Best practices ###
189 | #
190 | # #### Managing connections: a connection per request ####
191 | #
192 | # The RethinkDB server doesn't use a thread-per-connnection approach
193 | # so opening connections per request will not slow down your database.
194 | #
195 | # #### Fetching multiple rows: batched iterators ####
196 | #
197 | # When fetching multiple rows from a table, RethinkDB returns a
198 | # batched iterator initially containing a subset of the complete
199 | # result. Once the end of the current batch is reached, a new batch is
200 | # automatically retrieved from the server. From a coding point of view
201 | # this is transparent:
202 | #
203 | # for result in r.table('todos').run(connection):
204 | # print result
205 | #
206 | #
207 | # #### `replace` vs `update` ####
208 | #
209 | # Both `replace` and `update` operations can be used to modify one or
210 | # multiple rows. Their behavior is different:
211 | #
212 | # * `replace` will completely replace the existing rows with new values
213 | # * `update` will merge existing rows with the new values
214 |
215 |
216 | #
217 | # Licensed under the MIT license:
218 | #
219 | # Copyright (c) 2012 RethinkDB
220 | #
221 |
--------------------------------------------------------------------------------