volt 0.8.8 → 0.8.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/Readme.md +2 -1036
  4. data/VERSION +1 -1
  5. data/app/volt/models/user.rb +5 -0
  6. data/app/volt/tasks/query_tasks.rb +3 -3
  7. data/app/volt/tasks/store_tasks.rb +17 -15
  8. data/app/volt/tasks/user_tasks.rb +6 -0
  9. data/lib/volt/cli.rb +9 -0
  10. data/lib/volt/extra_core/string.rb +10 -2
  11. data/lib/volt/models/array_model.rb +0 -1
  12. data/lib/volt/models/model.rb +45 -28
  13. data/lib/volt/models/model_hash_behaviour.rb +16 -4
  14. data/lib/volt/models/model_helpers.rb +4 -2
  15. data/lib/volt/models/model_wrapper.rb +2 -1
  16. data/lib/volt/models/persistors/array_store.rb +6 -6
  17. data/lib/volt/models/persistors/local_store.rb +1 -1
  18. data/lib/volt/models/persistors/model_store.rb +3 -3
  19. data/lib/volt/models/persistors/query/query_listener.rb +6 -4
  20. data/lib/volt/models/persistors/query/query_listener_pool.rb +9 -0
  21. data/lib/volt/models/persistors/store.rb +1 -0
  22. data/lib/volt/models/url.rb +20 -10
  23. data/lib/volt/page/bindings/template_binding.rb +7 -1
  24. data/lib/volt/page/page.rb +3 -3
  25. data/lib/volt/page/tasks.rb +20 -19
  26. data/lib/volt/reactive/reactive_array.rb +44 -0
  27. data/lib/volt/router/routes.rb +5 -0
  28. data/lib/volt/server.rb +3 -1
  29. data/lib/volt/server/component_templates.rb +7 -1
  30. data/lib/volt/server/html_parser/attribute_scope.rb +1 -1
  31. data/lib/volt/server/rack/component_paths.rb +1 -10
  32. data/lib/volt/server/socket_connection_handler.rb +3 -0
  33. data/lib/volt/tasks/dispatcher.rb +3 -7
  34. data/lib/volt/tasks/task_handler.rb +41 -0
  35. data/spec/integration/bindings_spec.rb +1 -1
  36. data/spec/models/model_spec.rb +32 -32
  37. data/spec/models/persistors/params_spec.rb +1 -1
  38. data/spec/router/routes_spec.rb +4 -4
  39. data/templates/model/model.rb.tt +3 -0
  40. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 725663d259d0cbdeb328cb92cb6185af9df2b191
4
- data.tar.gz: 10b2471d0b582b232e176cba6a57d9b944b2571f
3
+ metadata.gz: 25b9037f048d7aceb00ab96a2e8e3fae6cb5a78f
4
+ data.tar.gz: 2e0d3c891a4399d096ff4304a1ad19039a02b523
5
5
  SHA512:
6
- metadata.gz: d8892f547d0ad523e00ef77e48129675e5027ee1235f52bef93375b4aa2f90b4c77499344e7fa120163144bb05c2aa91e1c9e6a69cd85878174e6250ffc17973
7
- data.tar.gz: 3f3893a24fd20f25923a6cca7b4f3d087690b52b80bc8bf00f1db4c7ec8de4d2a1835dd584e9364ebc6b5c0b283f0429ec21c70a7202df2dd30416a964b0d7a4
6
+ metadata.gz: c5f82f15a8499bdab393de67cc7cd16b4d2cf0308cb6491e01b438ce35e45634b7df273470d013477a4fa2d0476b20715696a6dd5b618e2c5679886e900827ae
7
+ data.tar.gz: b9eca133f9a058e1bd0a5f5d20e6355f613e23cf0ea7e7ec86f1c9dbf5f8fed231f508f2d3d782713f1c2c2e3ee24e53af35100ab1176d7268691f6891668860
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ #
2
+ - url.query, url.fragment, url.path all update reactively now.
3
+ - MAJOR CHANGE: Previously all tables and fields were created with _'s as their name prefixes. The underscores have been removed from everywhere. The only place you use underscores is when you want to access fields without creating setters and getters. Now when you do: ```model._name = 'Something'```, your setting the ```name``` attribute on the model. When you do: ```model._name```, your fetching the ```name``` attribute. If you insert a hash into a collection, you no longer use underscores:
4
+
5
+ ```ruby
6
+ store._items << {name: 'Item 1'}
7
+ ```
8
+
9
+ For the moment, continue using underscores in routes.
10
+
11
+
1
12
  # 0.8.6 - Oct 5, 2014
2
13
 
3
14
  - Major changes to the templating system (to address common concerns and make things simpler).
data/Readme.md CHANGED
@@ -25,1040 +25,6 @@ Check out demo apps:
25
25
  - https://github.com/voltrb/contactsdemo
26
26
 
27
27
 
28
- ## Goals
28
+ # Docs
29
29
 
30
- Volt has the following goals:
31
-
32
- 1. Developer happiness
33
- 2. Write once on the client and server
34
- 3. Automatic data syncing between client and server
35
- 4. Apps are built as nested components. Components can be shared (via gems)
36
- 5. Concurrent. Volt provides tools to simplify concurrency. Component rendering is done in parallel on the server
37
- 6. Intelligent asset management
38
- 7. Secure (shouldn't need to be said, but it does)
39
- 8. Be fast/light
40
- 9. Understandable code base
41
- 10. Control upgradeability
42
-
43
- # Road Map
44
-
45
- Many of the core Volt features are implemented. We still have a bit to go before 1.0, most of it involving models.
46
-
47
- 1. Model read/write permissions
48
- 2. User accounts, user collection, signup/login templates
49
-
50
- # VOLT guide
51
-
52
- This guide will take you through creating a basic web application in Volt. This tutorial assumes a basic knowledge of Ruby and web development.
53
-
54
- To get started, install Volt:
55
-
56
- gem install volt
57
-
58
- Then create a new project:
59
-
60
- volt new project_name
61
-
62
- This will setup a basic project. Now let's run the server:
63
-
64
- bundle exec volt server
65
-
66
- You can access the Volt console with:
67
-
68
- bundle exec volt console
69
-
70
- # Guide Sections
71
-
72
- 1. [Getting Help](#getting-help)
73
- 2. [Rendering](#rendering)
74
- 1. [States and Computations](#state-and-computations)
75
- 1. [Computations](#computations)
76
- 2. [Dependencies](#dependencies)
77
- 3. [Views](#views)
78
- 1. [Bindings](#bindings)
79
- 1. [Content Binding](#content-binding)
80
- 2. [If Binding](#if-binding)
81
- 3. [Each Binding](#each-binding)
82
- 4. [Attribute Bindings](#attribute-bindings)
83
- 2. [Escaping](#escaping)
84
- 4. [Models](#models)
85
- 1. [Nil Models](#nil-models)
86
- 2. [Provided Collections](#provided-collections)
87
- 3. [Store Collection](#store-collection)
88
- 4. [Sub Collections](#sub-collections)
89
- 5. [Model Classes](#model-classes)
90
- 6. [Buffers](#buffers)
91
- 7. [Validations](#validations)
92
- 8. [Model State](#model-state)
93
- 9. [ArrayModel Events](#arraymodel-events)
94
- 10. [Automatic Model Conversion](#automatic-model-conversion)
95
- 5. [Controllers](#controllers)
96
- 1. [Reactive Accessors](#reactive-accessors)
97
- 6. [Tasks](#tasks)
98
- 7. [Components](#components)
99
- 1. [Dependencies](#dependencies)
100
- 2. [Assets](#assets)
101
- 3. [Component Generator](#component-generator)
102
- 4. [Provided Components](#provided-components)
103
- 1. [Notices](#notices)
104
- 2. [Flash](#flash)
105
- 8. [Controls](#controls)
106
- 9. [Routes](#routes)
107
- 1. [Routes file](#routes-file)
108
- 10. [Testing](#testing)
109
- 11. [Debugging](#debugging)
110
- 12. [Volt Helpers](#volt-helpers)
111
- 1. [Logging](#logging)
112
- 2. [App Configuration](#app-configuration)
113
- 13. [Contributing](#contributing)
114
-
115
- # Getting Help
116
-
117
- Volt is still a work in progress, but early feedback is appreciated. Use the following to communicate with the developers, someone will get back to you very quickly:
118
-
119
- - **If you need help**: post on [stackoverflow.com](http://www.stackoverflow.com). Be sure to tag your question with `voltrb`.
120
- - **If you found a bug**: post on [github issues](https://github.com/voltrb/volt/issues)
121
- - **If you have an idea or need a feature**: post on [github issues](https://github.com/voltrb/volt/issues)
122
- - **If you want to discuss Volt**: [chat on gitter](https://gitter.im/voltrb/volt), someone from the volt team is usually online and happy to help with anything.
123
-
124
-
125
- # Rendering
126
-
127
- When a user interacts with a web page, typically we want to do two things:
128
-
129
- 1. Change application state
130
- 2. Update the DOM
131
-
132
- For example when a user clicks to add a new todo item to a todo list, we might create an object to represent the todo item, then add an item to the list's DOM. A lot of work needs to be done to make sure that the object and the DOM always stay in sync.
133
-
134
- The idea of "reactive programming" can be used to simplify maintaining the DOM. Instead of having event handlers that manage a model and manage the DOM, we have event handlers that manage reactive data models. We describe our DOM layer in a declarative way so that it automatically knows how to render our data models.
135
-
136
- ## State and Computations
137
-
138
- Web applications center around maintaining state. Many events can trigger changes to a state. Page interaction like entering text into form elements, clicking a button, links, scrolling, etc.. can all change the state of the app. In the past, each page interaction event would manually change any state stored on a page.
139
-
140
- To simplify managing application state, all application state is kept in models that can optionally be persisted in different locations. By centralizing the application state, we reduce the amount of complex code needed to update a page. We can then build our page's html declaratively. The relationship to the page's models' are bound using function and method calls.
141
-
142
- We want our DOM to automatically update when our model data changes. To make this happen, Volt lets you "watch" any method/proc call and have it get called again when data accessed by the method/proc call changes.
143
-
144
- ### Computations
145
-
146
- Lets take a look at this in practice. We'll use the ```page``` collection as an example. (You'll see more on collections later)
147
-
148
- First, we setup a computation watch. Computations are built by calling .watch! on a Proc. Here we'll use the ruby 1.9 proc shorthand syntax ```-> { ... }``` It will run once, then run again each time the data in page._name changes.
149
- ```ruby
150
- page._name = 'Ryan'
151
- -> { puts page._name }.watch!
152
- # => Ryan
153
- page._name = 'Jimmy'
154
- # => Jimmy
155
- ```
156
-
157
- Each time page._name is assigned to a new value, the computation is run again. A re-run of the computation will be triggered when any data accessed in the previous run is changed. This lets us access data through methods and still have watches re-triggered.
158
-
159
- ```ruby
160
- page._first = 'Ryan'
161
- page._last = 'Stout'
162
-
163
- def lookup_name
164
- return "#{page._first} #{page._last}"
165
- end
166
-
167
- -> do
168
- puts lookup_name
169
- end.watch!
170
- # => Ryan Stout
171
-
172
- page._first = 'Jimmy'
173
- # => Jimmy Stout
174
-
175
- page._last = 'Jones'
176
- # => Jimmy Jones
177
- ```
178
-
179
- When you call .watch! the return value is a Computation object. In the event you no longer want to receive updates, you can call .stop on the computation.
180
-
181
- ```ruby
182
- page._name = 'Ryan'
183
-
184
- comp = -> { puts page._name }.watch!
185
- # => Ryan
186
-
187
- page._name = 'Jimmy'
188
- # => Jimmy
189
-
190
- comp.stop
191
-
192
- page._name = 'Jo'
193
- # (nothing)
194
- ```
195
-
196
- ## Dependencies
197
-
198
- TODO: Explain Dependencies
199
-
200
- As a Volt user, you rarely need to use Comptuations and Dependencies directly. Instead you usually just interact with models and bindings. Computations are used under the hood, and having a full understanding of what's going on is useful, but not required.
201
-
202
- # Views
203
-
204
- Views in Volt use a templating language similar to handlebars. They can be broken up into sections. A section header looks like the following:
205
-
206
- ```html
207
- <:Body>
208
- ```
209
-
210
- Section headers should start with a capital letter so as not to be confused with [controls](#controls). Section headers do not use closing tags. If section headers are not provided, the Body section is assumed.
211
-
212
- Sections help you split up different parts of the same content (title and body usually), but within the same file.
213
-
214
- ## Bindings
215
-
216
- In Volt, you code your views in a handlebars like template language. Volt provides several bindings, which handle rendering of something for you. Content bindings are anything inbetween {{ and }}.
217
-
218
- ### Content binding
219
-
220
- The most basic binding is a content binding:
221
-
222
- ```html
223
- <p>{{ some_method }}<p>
224
- ```
225
-
226
- The content binding runs the Ruby code between {{ and }}, then renders the return value. Any time the data a content binding relies on changes, the binding will run again and update the text
227
-
228
- ### If binding
229
-
230
- An if binding lets you provide basic flow control.
231
-
232
- ```html
233
- {{ if _some_check? }}
234
- <p>render this</p>
235
- {{ end }}
236
- ```
237
-
238
- Blocks are closed with a {{ end }}
239
-
240
- When the if binding is rendered, it will run the ruby code after #if. If the code is true it will render the code below. Again, if the returned value is reactive, it will update as that value changes.
241
-
242
- If bindings can also have #elsif and #else blocks.
243
-
244
- ```html
245
- {{ if _condition_1? }}
246
- <p>condition 1 true</p>
247
- {{ elsif _condition_2? }}
248
- <p>condition 2 true</p>
249
- {{ else }}
250
- <p>neither true</p>
251
- {{ end }}
252
- ```
253
-
254
- ### Each binding
255
-
256
- For iteration over objects, you can use .each
257
-
258
- ```html
259
- {{ _items.each do |item| }}
260
- <p>{{ item }}</p>
261
- {{ end }}
262
- ```
263
-
264
- Above, if _items were an array, the block would be rendered for each item, setting 'item' to the value of the array element.
265
-
266
- You can also access the position of the item in the array with the #index method.
267
-
268
- ```html
269
- {{ each _items as item }}
270
- <p>{{ index }}. {{ item }}</p>
271
- {{ end }}
272
- ```
273
-
274
- For the array: ['one', 'two', 'three'] this would print:
275
-
276
- 0. one
277
- 1. two
278
- 2. three
279
-
280
- You can do {{ index + 1 }} to correct the zero offset.
281
-
282
- When items are removed or added to the array, the #each binding automatically and intelligently adds or removes the items from/to the DOM.
283
-
284
- ### Attribute Bindings
285
-
286
- Bindings can also be placed inside of attributes.
287
-
288
- ```html
289
- <p class="{{ if _is_cool? }}cool{{ end }}">Text</p>
290
- ```
291
-
292
- There are some special features provided to make elements work as "two way bindings":
293
-
294
- ```html
295
- <input type="text" value="{{ _name }}" />
296
- ```
297
-
298
- In the example above, if _name changes, the field will update, and if the field is updated, _name will be changed:
299
-
300
- ```html
301
- <input type="checkbox" checked="{{ _checked }}" />
302
- ```
303
-
304
- If the value of a checked attribute is true, the checkbox will be shown checked. If it's checked or unchecked, the value will be updated to true or false.
305
-
306
- Radio buttons bind to a checked state as well, except instead of setting the value to true or false, they set it to a supplied field value.
307
-
308
- ```html
309
- <input type="radio" checked="{{ _radio }}" value="one" />
310
- <input type="radio" checked="{{ _radio }}" value="two" />
311
- ```
312
-
313
- When a radio button is checked, whatever checked is bound to is set to the field's value. When the checked binding value is changed, any radio buttons where the binding's value matches the fields value are checked. NOTE: This seems to be the most useful behaviour for radio buttons.
314
-
315
- Select boxes can be bound to a value (while not technically a property, this is another convient behavior we add).
316
-
317
- ```html
318
- <select value="{{ _rating }}">
319
- <option value="1">*</option>
320
- <option value="2">**</option>
321
- <option value="3">***</option>
322
- <option value="4">****</option>
323
- <option value="5">*****</option>
324
- </select>
325
- ```
326
-
327
- When the selected option of the select above changes, ```_rating``` is changed to match. When ```_rating``` is changed, the selected value is changed to the first option with a matching value. If no matching values are found, the select box is unselected.
328
-
329
- If you have a controller at app/home/controller/index_controller.rb, and a view at app/home/views/index/index.html, all methods called are called on the controller.
330
-
331
- ### Template Bindings
332
-
333
- All views/*.html files are templates that can be rendered inside of other views using the template binding.
334
-
335
- ```html
336
- {{ template "header" }}
337
- ```
338
-
339
- ## Escaping
340
-
341
- When you need to use {{ and }} outside of bindings, anything in a triple mustache will be escaped and not processed as a binding:
342
-
343
- ```html
344
- {{{ bindings look like: {{this}} }}}
345
- ```
346
-
347
- # Models
348
-
349
- Volt's concept of a model is slightly different from many frameworks where a model is the name for the ORM to the database. In Volt a model is a class where you can store data easily. Models can be created with a "Persistor", which is responsible for storing the data in the model somewhere. Models created without a persistor, simply store the data in the classes instance. Lets first see how to use a model.
350
-
351
- Volt comes with many built-in models; one is called `page`. If you call `#page` on a controller, you will get access to the model.
352
-
353
- ```ruby
354
- page._name = 'Ryan'
355
- page._name
356
- # => 'Ryan'
357
- ```
358
-
359
- Models act like a hash that you can access with getters and setters that start with an underscore. If an attribute is accessed that hasn't yet been assigned, you will get back a "nil model". Prefixing with an underscore makes sure we don't accidentally try to call a method that doesn't exist and get back nil model instead of raising an exception. Fields behave similarly to a hash, but with a different access and assignment syntax.
360
-
361
- # TODO: Add docs on fields in classes
362
-
363
- Models also let you nest data without creating the intermediate models:
364
-
365
- ```ruby
366
- page._settings._color = 'blue'
367
- page._settings._color
368
- # => @'blue'
369
-
370
- page._settings
371
- # => @#<Model:_settings {:_color=>"blue"}>
372
- ```
373
-
374
- Nested data is automatically setup when assigned. In this case, page._settings is a model that is part of the page model. This allows nested models to be bound to a binding without the need to setup the model before use.
375
-
376
- In Volt models, plural properties return an ArrayModel instance. ArrayModels behave the same way as normal arrays. You can add/remove items to the array with normal array methods (#<<, push, append, delete, delete_at, etc...)
377
-
378
- ```ruby
379
- page._items
380
- # #<ArrayModel:70303686333720 []>
381
-
382
- page._items << {_name: 'Item 1'}
383
-
384
- page._items
385
- # #<ArrayModel:70303686333720 [<Model:70303682055800 {:_name=>"Item 1"}>]>
386
-
387
- page._items.size
388
- # => 1
389
-
390
- page._items[0]
391
- # => <Model:70303682055800 {:_name=>"Item 1"}>
392
- ```
393
-
394
-
395
- ## Nil Models
396
-
397
- As a convience, calling something like ```page._info``` returns what's called a NilModel (assuming it isn't already initialized). NilModels are place holders for future possible Models. NilModels allow us to bind deeply nested values without initializing any intermediate values.
398
-
399
- ```ruby
400
- page._info
401
- # => <Model:70260787225140 nil>
402
-
403
- page._info._name
404
- # => <Model:70260795424200 nil>
405
-
406
- page._info._name = 'Ryan'
407
- # => <Model:70161625994820 {:_info=><Model:70161633901800 {:_name=>"Ryan"}>}>
408
- ```
409
-
410
- One gotchya with NilModels is that they are a truthy value (since only nil and false are falsy in ruby). To make things easier, calling ```.nil?``` on a NilModel will return true.
411
-
412
- One common place we use a truthy check is in setting up default values with || (logical or) Volt provides a convenient method that does the same thing `#or`, but works with NilModels.
413
-
414
- Instead of
415
-
416
- ```ruby
417
- a || b
418
- ```
419
-
420
- Simply use:
421
-
422
- ```ruby
423
- a.or(b)
424
- ```
425
-
426
- `#and` works the same way as &&. #and and #or let you easily deal with default values involving NilModels.
427
-
428
- -- TODO: Document .true? / .false?
429
-
430
-
431
- ## Provided Collections
432
-
433
- Above, I mentioned that Volt comes with many default collection models accessible from a controller. Each stores in a different location.
434
-
435
- | Name | Storage Location |
436
- |-------------|---------------------------------------------------------------------------|
437
- | page | page provides a temporary store that only lasts for the life of the page. |
438
- | store | store syncs the data to the backend database and provides query methods. |
439
- | local_store | values will be stored in the local_store |
440
- | params | values will be stored in the params and URL. Routes can be setup to change how params are shown in the URL. (See routes for more info) |
441
- | flash | any strings assigned will be shown at the top of the page and cleared as the user navigates between pages. |
442
- | controller | a model for the current controller |
443
-
444
- **more storage locations are planned**
445
-
446
- ## Store Collection
447
-
448
- The store collection backs data in the data store. Currently the only supported data store is Mongo. (More coming soon, RethinkDb will probably be next) You can use store very similar to the other collections.
449
-
450
- In Volt you can access ```store``` on the front-end and the back-end. Data will automatically be synced between the front-end and the backend. Any changes to the data in store will be reflected on any clients using the data (unless a [buffer](#buffers) is in use - see below).
451
-
452
- ```ruby
453
- store._items << {_name: 'Item 1'}
454
-
455
- store._items[0]
456
- # => <Model:70303681865560 {:_name=>"Item 1", :_id=>"e6029396916ed3a4fde84605"}>
457
- ```
458
-
459
- Inserting into ```store._items``` will create a ```_items``` table and insert the model into it. An pseudo-unique _id will be automatically generated.
460
-
461
- Currently one difference between ```store``` and other collections is ```store``` does not store properties directly. Only ArrayModels are allowed directly on ```store```
462
-
463
- ```ruby
464
- store._something = 'yes'
465
- # => won't be saved at the moment
466
- ```
467
-
468
- Note: We're planning to add support for direct ```store``` properties.
469
-
470
- ## Sub Collections
471
-
472
- Models can be nested on ```store```
473
-
474
- ```ruby
475
- store._states << {_name: 'Montana'}
476
- montana = store._states[0]
477
-
478
- montana._cities << {_name: 'Bozeman'}
479
- montana._cities << {_name: 'Helena'}
480
-
481
- store._states << {_name: 'Idaho'}
482
- idaho = store._states[1]
483
-
484
- idaho._cities << {_name: 'Boise'}
485
- idaho._cities << {_name: 'Twin Falls'}
486
-
487
- store._states
488
- # #<ArrayModel:70129010999880 [<Model:70129010999460 {:_name=>"Montana", :_id=>"e3aa44651ff2e705b8f8319e"}>, <Model:70128997554160 {:_name=>"Montana", :_id=>"9aaf6d2519d654878c6e60c9"}>, <Model:70128997073860 {:_name=>"Idaho", :_id=>"5238883482985760e4cb2341"}>, <Model:70128997554160 {:_name=>"Montana", :_id=>"9aaf6d2519d654878c6e60c9"}>, <Model:70128997073860 {:_name=>"Idaho", :_id=>"5238883482985760e4cb2341"}>]>
489
- ```
490
-
491
- You can also create a Model first and then insert it.
492
-
493
- ```ruby
494
- montana = Model.new({_name: 'Montana'})
495
-
496
- montana._cities << {_name: 'Bozeman'}
497
- montana._cities << {_name: 'Helena'}
498
-
499
- store._states << montana
500
- ```
501
-
502
- ## Model Classes
503
-
504
- By default all collections use the Model class by default.
505
-
506
- ```ruby
507
- page._info.class
508
- # => Model
509
- ```
510
-
511
- You can provide classes that will be loaded in place of the standard model class. You can place these in any app/{component}/models folder. For example, you could add ```app/main/info.rb``` Model classes should inherit from ```Model```
512
-
513
- ```ruby
514
- class Info < Model
515
- end
516
- ```
517
-
518
- Now when you access any sub-collection called ```_info```, it will load as an instance of ```Info```
519
-
520
- ```ruby
521
- page._info.class
522
- # => Info
523
- ```
524
-
525
- This lets you set custom methods and validations within collections.
526
-
527
- ## Buffers
528
-
529
- Because the store collection is automatically synced to the backend, any change to a model's property will result in all other clients seeing the change immediately. Often this is not the desired behavior. To facilitate building [CRUD](http://en.wikipedia.org/wiki/Create,_read,_update_and_delete) apps, Volt provides the concept of a "buffer". A buffer can be created from one model and will not save data back to its backing model until .save! is called on it. This lets you create a form thats not saved until a submit button is pressed.
530
-
531
- ```ruby
532
- store._items << {_name: 'Item 1'}
533
-
534
- item1 = store._items[0]
535
-
536
- item1_buffer = item1.buffer
537
-
538
- item1_buffer._name = 'Updated Item 1'
539
- item1_buffer._name
540
- # => 'Updated Item 1'
541
-
542
- item1._name
543
- # => 'Item 1'
544
-
545
- item1_buffer.save!
546
-
547
- item1_buffer._name
548
- # => 'Updated Item 1'
549
-
550
- item1._name
551
- # => 'Updated Item 1'
552
- ```
553
-
554
- ```#save!``` on buffer also returns a [promise](http://opalrb.org/blog/2014/05/07/promises-in-opal/) that will resolve when the data has been saved back to the server.
555
-
556
- ```ruby
557
- item1_buffer.save!.then do
558
- puts "Item 1 saved"
559
- end.fail do |err|
560
- puts "Unable to save because #{err}"
561
- end
562
- ```
563
-
564
- Calling .buffer on an existing model will return a buffer for that model instance. If you call .buffer on an ArrayModel (plural sub-collection), you will get a buffer for a new item in that collection. Calling .save! will then add the item to that sub-collection as if you had done << to push the item into the collection.
565
-
566
- ## Validations
567
-
568
- Within a model class, you can setup validations. Validations let you restrict the types of data that can be stored in a model. Validations are mostly useful for the ```store``` collection, though they can be used elsewhere.
569
-
570
- At the moment we only have two validations implemented (length and presence). Though a lot more will be coming.
571
-
572
- ```ruby
573
- class Info < Model
574
- validate :_name, length: 5
575
- validate :_state, presence: true
576
- end
577
- ```
578
-
579
- When calling save on a model with validations, the following occurs:
580
-
581
- 1. Client side validations are run; if they fail, the promise from ```save!``` is rejected with the error object.
582
- 2. The data is sent to the server and client and server side validations are run on the server; any failures are returned and the promise is rejected on the front-end (with the error object)
583
- - re-running the validations on the server side makes sure that no data can be saved that doesn't pass the validations
584
- 3. If all validations pass, the data is saved to the database and the promise resolved on the client.
585
- 4. The data is synced to all other clients.
586
-
587
-
588
- ## Model State
589
-
590
- **Work in progress**
591
-
592
- | state | events bound | description |
593
- |-------------|--------------|--------------------------------------------------------------|
594
- | not_loaded | no | no events and no one has accessed the data in the model |
595
- | loading | maybe | someone either accessed the data or bound an event |
596
- | loaded | yes | data is loaded and there is an event bound |
597
- | dirty | no | data was either accessed without binding an event, or an event was bound, but later unbound. |
598
-
599
-
600
- ## ArrayModel Events
601
-
602
- Models trigger events when their data is updated. Currently, models emit two events: added and removed. For example:
603
-
604
- ```ruby
605
- model = Model.new
606
-
607
- model._items.on('added') { puts 'item added' }
608
- model._items << 1
609
- # => item added
610
-
611
- model._items.on('removed') { puts 'item removed' }
612
- model._items.delete_at(0)
613
- # => item removed
614
- ```
615
-
616
- ## Automatic Model Conversion
617
-
618
- ### Hash -> Model
619
-
620
- For convenience, when placing a hash inside of another model, it is automatically converted into a model. Models are similar to hashes, but provide support for things like persistence and triggering reactive events.
621
-
622
- ```ruby
623
- user = Model.new
624
- user._name = 'Ryan'
625
- user._profiles = {
626
- _twitter: 'http://www.twitter.com/ryanstout',
627
- _dribbble: 'http://dribbble.com/ryanstout'
628
- }
629
-
630
- user._name
631
- # => "Ryan"
632
- user._profiles._twitter
633
- # => "http://www.twitter.com/ryanstout"
634
- user._profiles.class
635
- # => Model
636
- ```
637
-
638
- Models are accessed differently from hashes. Instead of using `model[:symbol]` to access, you call a method `model.method_name`. This provides a dynamic unified store where setters and getters can be added without changing any access code.
639
-
640
- You can get a Ruby hash back out by calling `#to_h` on a Model.
641
-
642
- ### Array -> ArrayModel
643
-
644
- Arrays inside of models are automatically converted to an instance of ArrayModel. ArrayModels behave the same as a normal Array except that they can handle things like being bound to backend data and triggering reactive events.
645
-
646
- ```ruby
647
- model = Model.new
648
- model._items << {_name: 'item 1'}
649
- model._items.class
650
- # => ArrayModel
651
-
652
- model._items[0].class
653
- # => Model
654
- model._items[0]
655
- ```
656
-
657
-
658
- To convert a Model or an ArrayModel back to a normal hash, call .to_h or .to_a respectively. To convert them to a JavaScript Object (for passing to some JavaScript code), call `#to_n` (to native).
659
-
660
- ```ruby
661
- user = Model.new
662
- user._name = 'Ryan'
663
- user._profiles = {
664
- _twitter: 'http://www.twitter.com/ryanstout',
665
- _dribbble: 'http://dribbble.com/ryanstout'
666
- }
667
-
668
- user._profiles.to_h
669
- # => {_twitter: 'http://www.twitter.com/ryanstout', _dribbble: 'http://dribbble.com/ryanstout'}
670
-
671
- items = ArrayModel.new([1,2,3,4])
672
- # => #<ArrayModel:70226521081980 [1, 2, 3, 4]>
673
-
674
- items.to_a
675
- # => [1,2,3,4]
676
- ```
677
-
678
- You can get a normal array again by calling .to_a on an ArrayModel.
679
-
680
- # Controllers
681
-
682
- A controller can be any class in Volt, however it is common to have that class inherit from ModelController. A model controller lets you specify a model that the controller works off of. This is a common pattern in Volt. The model for a controller can be assigned by one of the following:
683
-
684
- 1. A symbol representing the name of a provided collection model:
685
-
686
- ```ruby
687
- class TodosController < ModelController
688
- model :page
689
-
690
- # ...
691
- end
692
- ```
693
-
694
- 2. Calling `self.model=` in a method:
695
-
696
- ```ruby
697
- class TodosController < ModelController
698
- def initialize
699
- self.model = :page
700
- end
701
- end
702
- ```
703
-
704
- When a model is set, any missing methods will be proxied to the model. This lets you bind within the views without prefixing the model object every time. It also lets you change out the current model and have the views update automatically.
705
-
706
- In methods, the `#model` method returns the current model.
707
-
708
- See the [provided collections](#provided-collections) section for a list of the available collection models.
709
-
710
- You can also provide your own object to model.
711
-
712
- In the example above, any methods not defined on the TodosController will fall through to the provided model. All views in views/{controller_name} will have this controller as the target for any Ruby run in their bindings. This means that calls on self (implicit or with self.) will have the model as their target (after calling through the controller). This lets you add methods to the controller to control how the model is handled, or provide extra methods to the views.
713
-
714
- Volt is more similar to an MVVM architecture than an MVC architecture. Instead of the controllers passing data off to the views, the controllers are the context for the views. When using a ModelController, the controller automatically forwards all methods it does not handle to the model. This is convenient since you can set a model in the controller and then access its properties directly with methods in bindings. This lets you do something like ```{{ _name }}``` instead of something like ```{{ @model._name }}```
715
-
716
- Controllers in the app/home component do not need to be namespaced, all other components should namespace controllers like so:
717
-
718
- ```ruby
719
- module Auth
720
- class LoginController < ModelController
721
- # ...
722
- end
723
- end
724
- ```
725
-
726
- Here "auth" would be the component name.
727
-
728
- ## Reactive Accessors
729
-
730
- The default ModelController proxies any missing methods to its model. Sometimes you need to store additional data reactively in the controller outside of the model. (Though often you may want to condier doing another control/controller). In this case, you can add a ```reactive_accessor```. These behave just like ```attr_accessor``` except the values assigned and returned are tracked for any Computations.
731
-
732
- ```ruby
733
- class Contacts < ModelController
734
- reactive_accessor :query
735
- end
736
- ```
737
-
738
- Now from the view we can bind to query while also changing in and out the model. You can also use ```reactive_reader``` and ```reactive_writer``` When query is accessed it tracks that it was accessed and will any Computations when it changes.
739
-
740
- # Tasks
741
-
742
- Sometimes you need to explicitly execute some code on the server. Volt solves this problem through *tasks*. You can define your own tasks by dropping a class into your component's ```tasks``` folder.
743
-
744
- ```ruby
745
- # app/main/tasks/logging_tasks.rb
746
-
747
- class LoggingTasks
748
- def initialize(channel=nil, dispatcher=nil)
749
- @channel = channel
750
- @dispatcher = dispatcher
751
- end
752
-
753
- def log(message)
754
- puts message
755
- end
756
- end
757
- ```
758
-
759
- To invoke a task from a controller use ```tasks.call```.
760
-
761
- ```ruby
762
- class Contacts < ModelController
763
- def hello
764
- tasks.call('LoggingTasks', 'log', 'Hello World!')
765
- end
766
- end
767
- ```
768
-
769
- You can also pass a block to ```tasks.call``` that will receive the return value of your task as soon as it's done.
770
-
771
- ```ruby
772
- tasks.call('MathTasks', 'add', 23, 5) do |result|
773
- # result should be 28
774
- alert result
775
- end
776
- ```
777
-
778
- # Components
779
-
780
- Apps are made up of Components. Each folder under app/ is a component. When you visit a route, it loads all of the files in the component on the front end, so new pages within the component can be rendered without a new http request. If a URL is visited that routes to a different component, the request will be loaded as a normal page load and all of that components files will be loaded. You can think of components as the "reload boundary" between sections of your app.
781
-
782
- ## Dependencies
783
-
784
- You can also use controls (see below) from one component in another. To do this, you must require the component from the component you wish to use them in. This can be done in the ```config/dependencies.rb``` file. Just put
785
-
786
- ```ruby
787
- component 'component_name'
788
- ```
789
-
790
- in the file.
791
-
792
- Dependencies act just like require in ruby, but for whole components.
793
-
794
- Sometimes you may need to include an externally hosted JS file from a component. To do this, simply do the following in the dependencies.rb file:
795
-
796
- ```ruby
797
- javascript_file 'http://code.jquery.com/jquery-2.0.3.min.js'
798
- css_file '//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css'
799
- ```
800
-
801
- Note above though that jquery and bootstrap are currently included by default. Using javascript_file and css_file will be mixed in with your component assets at the correct locations according to the order they occur in the dependencies.rb files.
802
-
803
- ## Assets
804
-
805
- **Note, asset management is still early, and likely will change quite a bit**
806
-
807
- In Volt, assets such as JavaScript and CSS (or sass) are automatically included on the page for you. Anything placed inside of a components asset/js or assets/css folder is served at /assets/{js,css} (via [Sprockets](https://github.com/sstephenson/sprockets)). Link and script tags are automatically added for each css and js file in assets/css and assets/js respectively. Files are included in their lexical order, so you can add numbers in front if you need to change the load order.
808
-
809
- Any JS/CSS from an included component or component gem will be included as well. By default [bootstrap](http://getbootstrap.com/) is provided by the volt-bootstrap gem.
810
-
811
- **Note: asset bundling is on the TODO list**
812
-
813
- ## Component Generator
814
-
815
- Components can easily be shared as a gem. Volt provides a scaffold for component gems. In a folder (not in a volt project), simply type: volt gem {component_name} This will create the files needed for the gem. Note that all volt component gems will be prefixed with volt- so they can easily be found by others on github and rubygems.
816
-
817
- While developing, you can use the component by placing the following in your Gemfile:
818
-
819
- ```ruby
820
- gem 'volt-{component_name}', path: '/path/to/folder/with/component'
821
- ```
822
-
823
- Once the gem is ready, you can release it to ruby gems with:
824
-
825
- rake release
826
-
827
- Remove the path: option in the gemfile if you wish to use the rubygems version.
828
-
829
- ## Provided Components
830
-
831
- Volt provides a few components to make web developers' lives easier.
832
-
833
- ### Notices
834
-
835
- Volt automatically places ```<:volt:notices />``` into views. This shows notices for the following:
836
-
837
- 1. flash messages
838
- 2. connection status (when a disconnect happens, lets the user know why and when a reconnect will be attempted)
839
- 3. page reloading notices (in development)
840
-
841
- ### Flash
842
-
843
- As part of the notices component explained above, you can append messages to any collection on the flash model.
844
-
845
- Each collection represents a different type of "flash". Common examples are ```_notices, _warnings, and _errors``` Using different collections allows you to change how you want the flash displayed. For example, you might want ```_notices``` and ```_errors``` to show with different colors.
846
-
847
- ```ruby
848
- flash._notices << "message to flash"
849
- ```
850
-
851
- These messages will show for 5 seconds, then disappear (both from the screen and the collection).
852
-
853
- # Controls
854
-
855
- Everyone wishes that we could predict the scope and required features for each part of our application, but in the real world, things we don't expect to grow large often do and things we think will be large don't end up that way. Controls let you quickly setup reusable code/views. The location of the controls code can be moved as it grows without changing the way controls are invoked.
856
-
857
- To render a control, simply use a tag like so:
858
-
859
- ```html
860
- <:control-name />
861
- ```
862
-
863
- or
864
-
865
- ```html
866
- <:control-name></:control-name>
867
- ```
868
-
869
- To find the control's views and optional controller, Volt will search the following (in order):
870
-
871
- | Section | View File | View Folder | Component |
872
- |-----------|--------------|----------------|-------------|
873
- | :{name} | | | |
874
- | :body | {name}.html | | |
875
- | :body | index.html | {name} | |
876
- | :body | index.html | index | {name} |
877
- | :body | index.html | index | gems/{name} |
878
-
879
- **Note that anything with a view folder will also load a controller if the name/folder matches.**
880
-
881
-
882
- Each part is explained below:
883
-
884
- 1. section
885
- Views are composed of sections. Sections start with a ```<:SectionName>``` and are not closed. Volt will look first for a section in the same view.
886
-
887
- 2. views
888
- Next Volt will look for a view file with the control name. If found, it will render the body section of that view.
889
-
890
- 3. view folder
891
- Failing above, Volt will look for a view folder with the control name, and an index.html file within that folder. It will render the :body section of that view. If a controller exists for the view folder, it will make a new instance of that controller and render in that instance.
892
-
893
- 4. component
894
- Next, all folders under app/ are checked. The view path looked for is {component}/index/index.html with a section of :body.
895
-
896
- 5. gems
897
- Lastly the app folder of all gems that start with volt are checked. They are checked for a similar path to component.
898
-
899
- When you create a control, you can also specify multiple parts of the search path in the name. The parts should be separated by a : Example:
900
-
901
- ```html
902
- <:blog:comments />
903
- ```
904
-
905
- The above would search the following:
906
-
907
- | Section | View File | View Folder | Component |
908
- |-----------|--------------|----------------|-------------|
909
- | :comments | blog.html | | |
910
- | :body | comments.html| blog | |
911
- | :body | index.html | comments | blog |
912
- | :body | index.html | comments | gems/blog |
913
-
914
- Once the view file for the control or template is found, it will look for a matching controller. If the control is specified as a local template, an empty ModelController will be used. If a controller is found and loaded, a corresponding "action" method will be called on it if its exists. Action methods default to "index" unless the component or template path has two parts, in which case the last part is the action.
915
-
916
- # Control Arguments/Attributes
917
-
918
- Like other html tags, controls can be passed attributes. These are then converted into an object that is passed as the first argument to the initialize method on the controller. The standard ModelController's initialize will then assign the object to the attrs property which can be accessed with ```#attrs``` This makes it easy to access attributes passed in.
919
-
920
- ```html
921
-
922
- <:Body>
923
-
924
- <ul>
925
- {{ _todos.each do |todo| }}
926
- <:todo name="{{ todo._name }}" />
927
- {{ end }}
928
- </ul>
929
-
930
- <:Todo>
931
- <li>{{ attrs.name }}</li>
932
- ```
933
-
934
- Instead of passing in individual attributes, you can also pass in a Model object with the "model" attribute and it will be set as the model for the controller.
935
-
936
- ```html
937
- <:Body>
938
- <ul>
939
- {{ _todos.each do |todo| }}
940
- <:todo model="{{ todo }}" />
941
- {{ end }}
942
- </ul>
943
-
944
- <:Todo>
945
- <li>
946
- {{ _name }} -
947
- {{ if _complete }}
948
- Complete
949
- {{ end }}
950
- </li>
951
- ```
952
-
953
- # Routes
954
-
955
- Routes in Volt are very different from traditional backend frameworks. Since data is synchronized using websockets, routes are mainly used to serialize the state of the application into the url in a pretty way. When a page is first loaded, the URL is parsed with the routes and the params model's values are set from the URL. Later if the params model is updated, the URL is updated based on the routes.
956
-
957
- This means that routes in Volt have to be able to go both from URL to params and params to URL. It should also be noted that if a link is clicked and the controller/view to render the new URL is within the current component (or an included component), the page will not be reloaded, the URL will be updated with the HTML5 history API, and the params hash will reflect the new URL. You can use the changes in params to render different views based on the URL.
958
-
959
- ## Routes file
960
-
961
- Routes are specified on a per-component basis in the config/routes.rb file. Routes simply map from URL to params.
962
-
963
- ```ruby
964
- get "/todos", {_view: 'todos'}
965
- ```
966
-
967
- Routes take two arguments; a path, and a params hash. When a new URL is loaded and the path is matched on a route, the params will be set to the params provided for that route. The specified params hash acts as a constraint. An empty hash will match any url. Any params that are not matched will be placed in the query parameters.
968
-
969
- When the params are changed, the URL will be set to the path for the route whose params hash matches.
970
-
971
- Route paths can also contain variables similar to bindings:
972
-
973
- ```ruby
974
- get "/todos/{{ _index }}", _view: 'todos'
975
- ```
976
-
977
- In the case above, if any URL matches /todos/*, (where * is anything but a slash), it will be the active route. ```params._view``` would be set to 'todos', and ```params._index``` would be set to the value in the path.
978
-
979
- If ```params._view``` were 'todos' and ```params._index``` were not nil, the route would be matched.
980
-
981
- Routes are matched top to bottom in a routes file.
982
-
983
- # Channel
984
-
985
- Controllers provide a `#channel` method, that you can use to get the status of the connection to the backend. Channel's access methods are reactive and when the status changes, the watching computations will be re-triggered. It provides the following:
986
-
987
- | method | description |
988
- |-------------|-----------------------------------------------------------|
989
- | connected? | true if it is connected to the backend |
990
- | status | possible values: :opening, :open, :closed, :reconnecting |
991
- | error | the error message for the last failed connection |
992
- | retry_count | the number of reconnection attempts that have been made without a successful connection |
993
- | reconnect_interval | the time until the next reconnection attempt (in seconds) |
994
-
995
-
996
- # Testing
997
-
998
- ** Testing is being reworked at the moment.
999
- Volt provides rspec and capybara out of the box. You can test directly against your models, controllers, etc... or you can do full integration tests via [Capybara](https://github.com/jnicklas/capybara).
1000
-
1001
- To run Capybara tests, you need to specify a driver. The following drivers are currently supported:
1002
-
1003
- 1. Phantom (via poltergeist)
1004
-
1005
- ```BROWSER=phantom bundle exec rspec```
1006
-
1007
- 2. Firefox
1008
-
1009
- ```BROWSER=firefox bundle exec rspec```
1010
-
1011
- 3. IE - coming soon
1012
-
1013
- Chrome is not supported due to [this issue](https://code.google.com/p/chromedriver/issues/detail?id=887#makechanges) with ChromeDriver. Feel free to go [here](https://code.google.com/p/chromedriver/issues/detail?id=887#makechanges) and pester the chromedriver team to fix it.
1014
-
1015
- # Debugging
1016
-
1017
- An in browser irb is in the works. We also have source maps support, but they are currently disabled by default. To enable them run:
1018
-
1019
- MAPS=true volt s
1020
-
1021
- This feature is disabled by default because (due to the volume of pages rendered) it slows down page rendering. We're working with the opal and sprockets teams to make it so everything is still served in one big source maps file (which would show the files as they originated on disk)
1022
-
1023
- # Volt Helpers
1024
-
1025
- ## Logging
1026
-
1027
- Volt provides a helper for logging. Calling ```Volt.logger``` returns an instance of the ruby logger. See [here](http://www.ruby-doc.org/stdlib-2.1.3/libdoc/logger/rdoc/Logger.html) for more.
1028
-
1029
- ```ruby
1030
- Volt.logger.info("Some info...")
1031
- ```
1032
-
1033
- You can change the logger with:
1034
-
1035
- ```ruby
1036
- Volt.logger = Logger.new
1037
- ```
1038
-
1039
- ## App Configuration
1040
-
1041
- Like many frameworks, Volt changes some default settings based on an environment flag. You can set the volt environment with the VOLT_ENV environment variable.
1042
-
1043
- All files in the app's ```config``` folder are loaded when Volt boots. This is similar to the ```initializers``` folder in Rails.
1044
-
1045
- Volt does its best to start with useful defaults. You can configure things like your database and app name in the config/app.rb file. The following are the current configuration options:
1046
-
1047
- | name | default | description |
1048
- |-----------|---------------------------|---------------------------------------------------------------|
1049
- | app_name | the current folder name | This is used internally for things like logging. |
1050
- | db_driver | 'mongo' | Currently mongo is the only supported driver, more coming soon|
1051
- | db_name | "#{app_name}_#{Volt.env} | The name of the mongo database. |
1052
- | db_host | 'localhost' | The hostname for the mongo database. |
1053
- | db_port | 27017 | The port for the mongo database. |
1054
- | compress_deflate | false | If true, will run deflate in the app server, its better to let something like nginx do this though |
1055
-
1056
- ## Accessing DOM section in a controller
1057
-
1058
- TODO
1059
-
1060
- # Contributing
1061
-
1062
- You want to contribute? Great! Thanks for being awesome! At the moment, we have a big internal todo list, hop on https://gitter.im/voltrb/volt so we don't duplicate work. Pull requests are always welcome, but asking about helping on gitter should save some duplication.
1063
-
1064
- [![Pledgie](https://pledgie.com/campaigns/26731.png?skin_name=chrome)](https://pledgie.com/campaigns/26731)
30
+ Read the [full docs on Volt here](http://voltframework.com/docs)