lotus-view 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -15
  3. data/.travis.yml +6 -0
  4. data/.yardopts +3 -0
  5. data/Gemfile +13 -2
  6. data/README.md +514 -3
  7. data/Rakefile +17 -1
  8. data/lib/lotus/layout.rb +132 -0
  9. data/lib/lotus/presenter.rb +70 -0
  10. data/lib/lotus/view/dsl.rb +247 -0
  11. data/lib/lotus/view/inheritable.rb +50 -0
  12. data/lib/lotus/view/rendering/layout_finder.rb +104 -0
  13. data/lib/lotus/view/rendering/layout_registry.rb +63 -0
  14. data/lib/lotus/view/rendering/layout_scope.rb +138 -0
  15. data/lib/lotus/view/rendering/null_layout.rb +52 -0
  16. data/lib/lotus/view/rendering/null_template.rb +79 -0
  17. data/lib/lotus/view/rendering/partial.rb +29 -0
  18. data/lib/lotus/view/rendering/partial_finder.rb +41 -0
  19. data/lib/lotus/view/rendering/registry.rb +129 -0
  20. data/lib/lotus/view/rendering/scope.rb +48 -0
  21. data/lib/lotus/view/rendering/template.rb +56 -0
  22. data/lib/lotus/view/rendering/template_finder.rb +53 -0
  23. data/lib/lotus/view/rendering/templates_finder.rb +85 -0
  24. data/lib/lotus/view/rendering/view_finder.rb +37 -0
  25. data/lib/lotus/view/rendering.rb +265 -0
  26. data/lib/lotus/view/template.rb +45 -0
  27. data/lib/lotus/view/version.rb +4 -1
  28. data/lib/lotus/view.rb +180 -2
  29. data/lib/lotus-view.rb +1 -0
  30. data/lotus-view.gemspec +15 -11
  31. data/test/fixtures/templates/app/app_view.html.erb +0 -0
  32. data/test/fixtures/templates/app/view.html.erb +0 -0
  33. data/test/fixtures/templates/application.html.erb +10 -0
  34. data/test/fixtures/templates/articles/_form.html.erb +4 -0
  35. data/test/fixtures/templates/articles/alternative_new.html.erb +1 -0
  36. data/test/fixtures/templates/articles/index.atom.erb +5 -0
  37. data/test/fixtures/templates/articles/index.html.erb +3 -0
  38. data/test/fixtures/templates/articles/index.json.erb +9 -0
  39. data/test/fixtures/templates/articles/index.rss.erb +0 -0
  40. data/test/fixtures/templates/articles/new.html.erb +7 -0
  41. data/test/fixtures/templates/articles/show.html.erb +1 -0
  42. data/test/fixtures/templates/articles/show.json.erb +5 -0
  43. data/test/fixtures/templates/contacts/show.html.haml +1 -0
  44. data/test/fixtures/templates/dashboard/index.html.erb +2 -0
  45. data/test/fixtures/templates/hello_world_view.html.erb +1 -0
  46. data/test/fixtures/templates/index_view.html.erb +1 -0
  47. data/test/fixtures/templates/json_render_view.json.erb +3 -0
  48. data/test/fixtures/templates/render_view.html.erb +1 -0
  49. data/test/fixtures/templates/shared/_sidebar.html.erb +1 -0
  50. data/test/fixtures.rb +187 -0
  51. data/test/layout_test.rb +10 -0
  52. data/test/load_test.rb +79 -0
  53. data/test/presenter_test.rb +31 -0
  54. data/test/rendering_test.rb +125 -0
  55. data/test/root_test.rb +38 -0
  56. data/test/test_helper.rb +24 -0
  57. data/test/version_test.rb +7 -0
  58. data/test/view_test.rb +27 -0
  59. metadata +137 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: df7ea1dce4a993b4f0da127f42db9e934b0ba162
4
- data.tar.gz: df6efe88e8b6bcf4f0056fa97e57e6cb11f4209d
3
+ metadata.gz: 73dd8583d5b48a96229bf72a847120710d701d0e
4
+ data.tar.gz: 99c9a9ce462407779c7a14113dddd4c8129c4dfb
5
5
  SHA512:
6
- metadata.gz: 3d34a930d3493d0f6a67b9ed6a1cecf9f90c07a258505af72f633fe51b48d47445f5ced8940844e180149ac8b8280cf0e2201605aaa15072cf50c0448f5815b9
7
- data.tar.gz: e1797c621ea418200d4313e5802255f6f54a0a3b23fc7e045253882d69c576a52ab6a3712f4c9dc1c1d38d03224ea7b5d698b59011d64391fd92ea3fe3192a3b
6
+ metadata.gz: 098ee9925947868c1f65424e0d443ba6722866f904ba4279fc9d9a79a9f7cb41229723b8f79ddd1aedb80dc521fb880056ce04344a8ad210175f776051a717b8
7
+ data.tar.gz: 3fd4ac2c4c5738c15aed901ea82df7e2fb26234df758e1eea35794721ae4f6adc50cd9d7744e8fccd38a2077e0d9eab6b2e1d64265a9f586e91fd4ba10924490
data/.gitignore CHANGED
@@ -1,17 +1,8 @@
1
- *.gem
2
- *.rbc
3
- .bundle
4
- .config
5
- .yardoc
1
+ .devnotes
2
+ .greenbar
6
3
  Gemfile.lock
7
- InstalledFiles
8
- _yardoc
9
- coverage
4
+ coverage/
5
+ .yardoc/
10
6
  doc/
11
- lib/bundler/man
12
- pkg
13
- rdoc
14
- spec/reports
15
- test/tmp
16
- test/version_tmp
17
- tmp
7
+ *.gem
8
+ .bundle
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ script: 'bundle exec rake test:coverage'
3
+ rvm:
4
+ - 2.0.0
5
+ - 2.1.0
6
+ - 2.1.1
data/.yardopts ADDED
@@ -0,0 +1,3 @@
1
+ -
2
+ LICENSE.txt
3
+ lib/**/*.rb
data/Gemfile CHANGED
@@ -1,4 +1,15 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in lotus-view.gemspec
4
- gemspec
3
+ if !ENV['TRAVIS']
4
+ gem 'byebug', require: false, platforms: :ruby if RUBY_VERSION == '2.1.1'
5
+ gem 'yard', require: false
6
+ gem 'lotus-utils', require: false, path: '../lotus-utils'
7
+ else
8
+ gem 'lotus-utils'
9
+ end
10
+
11
+ gem 'rake'
12
+ gem 'tilt', '~> 2.0.1', '>= 2.0.1'
13
+ gem 'haml', require: false
14
+ gem 'simplecov', require: false
15
+ gem 'coveralls', require: false
data/README.md CHANGED
@@ -1,6 +1,39 @@
1
1
  # Lotus::View
2
2
 
3
- TODO: Write a gem description
3
+ A View layer for [Lotus](http://lotusrb.org).
4
+
5
+ It's based on a **separation between views and templates**.
6
+
7
+ A _view_ is an object that encapsulates the presentation logic of a page.
8
+ A _template_ is a file that defines the semantic and visual elements of a page.
9
+ In order to show a result to an user, a template must be _rendered_ by a view.
10
+
11
+ Keeping things separated, helps to declutter templates and models from presentation logic.
12
+ Also, since views are objects they are easily testable.
13
+ If you ever used [Mustache](http://mustache.github.io/), you are already aware of the advantages.
14
+
15
+ Like all the other Lotus compontents it can be used as a standalone framework, or within a full Lotus application.
16
+
17
+ ## Status
18
+
19
+ [![Gem Version](https://badge.fury.io/rb/lotus-view.png)](http://badge.fury.io/rb/lotus-view)
20
+ [![Build Status](https://secure.travis-ci.org/lotus/view.png?branch=master)](http://travis-ci.org/lotus/view?branch=master)
21
+ [![Coverage](https://coveralls.io/repos/lotus/view/badge.png?branch=master)](https://coveralls.io/r/lotus/view)
22
+ [![Code Climate](https://codeclimate.com/github/lotus/view.png)](https://codeclimate.com/github/lotus/view)
23
+ [![Dependencies](https://gemnasium.com/lotus/view.png)](https://gemnasium.com/lotus/view)
24
+ [![Inline docs](http://inch-pages.github.io/github/lotus/view.png)](http://inch-pages.github.io/github/lotus/view)
25
+
26
+ ## Contact
27
+
28
+ * Home page: http://lotusrb.org
29
+ * Mailing List: http://lotusrb.org/mailing-list
30
+ * API Doc: http://rdoc.info/gems/lotus-view
31
+ * Bugs/Issues: https://github.com/lotus/view/issues
32
+ * Support: http://stackoverflow.com/questions/tagged/lotus-ruby
33
+
34
+ ## Rubies
35
+
36
+ __Lotus::View__ supports Ruby (MRI) 2+
4
37
 
5
38
  ## Installation
6
39
 
@@ -18,12 +51,490 @@ Or install it yourself as:
18
51
 
19
52
  ## Usage
20
53
 
21
- TODO: Write usage instructions here
54
+ ### Conventions
55
+
56
+ * Templates are searched under `Lotus::View.root`, set this value according to your app structure (eg. `"app/templates"`).
57
+ * A view will look for a template with a file name that is composed by its full class name (eg. `"articles/index"`).
58
+ * A template must have two concatenated extensions: one for the format one for the engine (eg. `".html.erb"`).
59
+ * The framework must be loaded before to render for the first time: `Lotus::View.load!`.
60
+
61
+ ### Views
62
+
63
+ A simple view looks like this:
64
+
65
+ ```ruby
66
+ require 'lotus/view'
67
+
68
+ module Articles
69
+ class Index
70
+ include Lotus::View
71
+ end
72
+ end
73
+ ```
74
+
75
+ Suppose that we want to render a list of `articles`:
76
+
77
+ ```ruby
78
+ require 'lotus/view'
79
+
80
+ module Articles
81
+ class Index
82
+ include Lotus::View
83
+ end
84
+ end
85
+
86
+ Lotus::View.root = 'app/templates'
87
+ Lotus::View.load!
88
+
89
+ path = Lotus::View.root.join('articles/index.html.erb')
90
+ template = Lotus::View::Template.new(path)
91
+ articles = ArticleRepository.all
92
+
93
+ Articles::Index.new(template, articles: articles).render
94
+ ```
95
+
96
+ While this code is working fine, it's inefficient and verbose, because we are loading a template from the filesystem for each rendering attempt.
97
+ Also, this is strictly related to the HTML format, what if we want to manage other formats?
98
+
99
+ ```ruby
100
+ require 'lotus/view'
101
+
102
+ module Articles
103
+ class Index
104
+ include Lotus::View
105
+ end
106
+
107
+ class AtomIndex < Index
108
+ format :atom
109
+ end
110
+ end
111
+
112
+ Lotus::View.root = 'app/templates'
113
+ Lotus::View.load!
114
+
115
+ articles = ArticleRepository.all
116
+
117
+ Articles::Index.render(format: :html, articles: articles)
118
+ # => This will use Articles::Index
119
+ # and "articles/index.html.erb"
120
+
121
+ Articles::Index.render(format: :atom, articles: articles)
122
+ # => This will use Articles::AtomIndex
123
+ # and "articles/index.atom.erb"
124
+
125
+ Articles::Index.render(format: :xml, articles: articles)
126
+ # => This will raise a Lotus::View::MissingTemplateError
127
+ ```
128
+
129
+ ### Locals
130
+
131
+ All the objects passed in the context are called _locals_, they are available both in the view and in the template:
132
+
133
+ ```ruby
134
+ require 'lotus/view'
135
+
136
+ module Articles
137
+ class Show
138
+ include Lotus::View
139
+
140
+ def authors
141
+ article.map(&:author).join ', '
142
+ end
143
+ end
144
+ end
145
+ ```
146
+
147
+ ```erb
148
+ <h1><%= article.title %></h1>
149
+ <article>
150
+ <%= article.content %>
151
+ </article>
152
+ ```
153
+
154
+ All the methods defined in the view are accessible in the template:
155
+
156
+ ```erb
157
+ <h2><%= authors %></h2>
158
+ ```
159
+
160
+ For convenience, they are also available as an Hash `locals` in the view.
161
+
162
+ ```ruby
163
+ require 'lotus/view'
164
+
165
+ module Articles
166
+ class Show
167
+ include Lotus::View
168
+
169
+ # This view, already responds to `#article` because there is an element in
170
+ # the locals with the same key.
171
+ #
172
+ # In order to allow developers to override those methods, and decorate a
173
+ # single locals object, a view has an Hash with the same values.
174
+ #
175
+ # If we had implemented this method like this:
176
+ #
177
+ # def article
178
+ # ArticlePresenter.new(article)
179
+ # end
180
+ #
181
+ # We would have generated a `SystemStackError` (stack level too deep).
182
+ def article
183
+ ArticlePresenter.new(locals[:article])
184
+ end
185
+ end
186
+ end
187
+ ```
188
+
189
+ ### Custom rendering
190
+
191
+ Since a view is an object, you can override `#render` and provide your own rendering policy:
192
+
193
+ ```ruby
194
+ require 'lotus/view'
195
+
196
+ module Articles
197
+ class Show
198
+ include Lotus::View
199
+ format :json
200
+
201
+ def render
202
+ ArticleSerializer.new(article).to_json
203
+ end
204
+ end
205
+ end
206
+
207
+ Articles::Show.render({format: :json, article: article})
208
+ # => This will render from ArticleSerializer,
209
+ # without the need of a template
210
+ ```
211
+
212
+ ### Format
213
+
214
+ The `.format` DSL is used to declare one or more mime types that a view is able to render.
215
+ These values are **arbitrary**, just **be sure to create a corresponding template**.
216
+
217
+ ```ruby
218
+ require 'lotus/view'
219
+
220
+ module Articles
221
+ class Show
222
+ include Lotus::View
223
+ format :custom
224
+ end
225
+ end
226
+
227
+ Articles::Show.render({format: :custom, article: article})
228
+ # => This will render "articles/show.custom.erb"
229
+ ```
230
+
231
+ ### Engines
232
+
233
+ The builtin rendering engine is [ERb](http://en.wikipedia.org/wiki/ERuby).
234
+ However, Lotus::View supports countless rendering engines out of the box.
235
+ Require your library of choice **before** of requiring `'lotus/view'`, and it will just work.
236
+
237
+ ```ruby
238
+ require 'haml'
239
+ require 'lotus/view'
240
+
241
+ module Articles
242
+ class Show
243
+ include Lotus::View
244
+ end
245
+ end
246
+
247
+ Articles::Show.render({format: :html, article: article})
248
+ # => This will render "articles/show.html.haml"
249
+ ```
250
+
251
+ This is the list of the supported engines.
252
+ They are listed in order of **higher precedence**, for a given extension.
253
+ For instance, if [ERubis](http://www.kuwata-lab.com/erubis/) is loaded, it will be preferred over ERb to render `.erb` templates.
254
+
255
+ <table>
256
+ <tr>
257
+ <th>Engine</th>
258
+ <th>Extensions</th>
259
+ </tr>
260
+ <tr>
261
+ <td>Erubis</td>
262
+ <td>erb, rhtml, erubis</td>
263
+ </tr>
264
+ <tr>
265
+ <td>ERb</td>
266
+ <td>erb, rhtml</td>
267
+ </tr>
268
+ <tr>
269
+ <td>Redcarpet</td>
270
+ <td>markdown, mkd, md</td>
271
+ </tr>
272
+ <tr>
273
+ <td>RDiscount</td>
274
+ <td>markdown, mkd, md</td>
275
+ </tr>
276
+ <tr>
277
+ <td>Kramdown</td>
278
+ <td>markdown, mkd, md</td>
279
+ </tr>
280
+ <tr>
281
+ <td>Maruku</td>
282
+ <td>markdown, mkd, md</td>
283
+ </tr>
284
+ <tr>
285
+ <td>BlueCloth</td>
286
+ <td>markdown, mkd, md</td>
287
+ </tr>
288
+ <tr>
289
+ <td>Asciidoctor</td>
290
+ <td>ad, adoc, asciidoc</td>
291
+ </tr>
292
+ <tr>
293
+ <td>Builder</td>
294
+ <td>builder</td>
295
+ </tr>
296
+ <tr>
297
+ <td>CSV</td>
298
+ <td>rcsv</td>
299
+ </tr>
300
+ <tr>
301
+ <td>CoffeeScript</td>
302
+ <td>coffee</td>
303
+ </tr>
304
+ <tr>
305
+ <td>WikiCloth</td>
306
+ <td>wiki, mediawiki, mw</td>
307
+ </tr>
308
+ <tr>
309
+ <td>Creole</td>
310
+ <td>wiki, creole</td>
311
+ </tr>
312
+ <tr>
313
+ <td>Etanni</td>
314
+ <td>etn, etanni</td>
315
+ </tr>
316
+ <tr>
317
+ <td>Haml</td>
318
+ <td>haml</td>
319
+ </tr>
320
+ <tr>
321
+ <td>Less</td>
322
+ <td>less</td>
323
+ </tr>
324
+ <tr>
325
+ <td>Liquid</td>
326
+ <td>liquid</td>
327
+ </tr>
328
+ <tr>
329
+ <td>Markaby</td>
330
+ <td>mab</td>
331
+ </tr>
332
+ <tr>
333
+ <td>Nokogiri</td>
334
+ <td>nokogiri</td>
335
+ </tr>
336
+ <tr>
337
+ <td>Plain</td>
338
+ <td>html</td>
339
+ </tr>
340
+ <tr>
341
+ <td>RDoc</td>
342
+ <td>rdoc</td>
343
+ </tr>
344
+ <tr>
345
+ <td>Radius</td>
346
+ <td>radius</td>
347
+ </tr>
348
+ <tr>
349
+ <td>RedCloth</td>
350
+ <td>textile</td>
351
+ </tr>
352
+ <tr>
353
+ <td>Sass</td>
354
+ <td>sass</td>
355
+ </tr>
356
+ <tr>
357
+ <td>Scss</td>
358
+ <td>scss</td>
359
+ </tr>
360
+ <tr>
361
+ <td>String</td>
362
+ <td>str</td>
363
+ </tr>
364
+ <tr>
365
+ <td>Yajl</td>
366
+ <td>yajl</td>
367
+ </tr>
368
+ </table>
369
+
370
+ ### Root
371
+
372
+ Templates lookup is performed under the `Lotus::View.root` directory. Single views can specify a different path:
373
+
374
+ ```ruby
375
+ class ViewWithDifferentRoot
376
+ include Lotus::View
377
+
378
+ root 'path/to/root'
379
+ end
380
+ ```
381
+
382
+ ### Template
383
+
384
+ The template file must be located under the relevant `root` and must match the class name:
385
+
386
+ ```ruby
387
+ puts Lotus::View.root # => #<Pathname:app/templates>
388
+ Articles::Index.template # => "articles/index"
389
+ ```
390
+
391
+ Each view can specify a different template:
392
+
393
+ ```ruby
394
+ module Articles
395
+ class Create
396
+ include Lotus::View
397
+
398
+ template 'articles/new'
399
+ end
400
+ end
401
+
402
+ Articles::Index.template # => "articles/new"
403
+ ```
404
+
405
+ ### Partials
406
+
407
+ Partials can be rendered within a template:
408
+
409
+ ```erb
410
+ <%= render partial: 'articles/form', locals: { secret: 23 } %>
411
+ ```
412
+
413
+ It will look for a template `articles/_form.html.erb` and it will make available both the view's and partial's locals (eg. `article` and `secret`).
414
+
415
+ ### Templates
416
+
417
+ Templates can be rendered within another template:
418
+
419
+ ```erb
420
+ <%= render template: 'articles/new', locals: { errors: {} } %>
421
+ ```
422
+
423
+ It will render `articles/new.html.erb` and it will make available both the view's and templates's locals (eg. `article` and `errors`).
424
+
425
+ ### Layouts
426
+
427
+ Layouts are wrappers for views, they can be a way to reuse common markup.
428
+
429
+ ```ruby
430
+ class ApplicationLayout
431
+ include Lotus::Layout
432
+
433
+ def page_title
434
+ 'Title:'
435
+ end
436
+ end
437
+
438
+ module Articles
439
+ class Index
440
+ include Lotus::View
441
+ layout :application
442
+
443
+ def page_title
444
+ "#{ layout.page_title } articles"
445
+ end
446
+ end
447
+
448
+ class RssIndex < Index
449
+ format :rss
450
+ layout nil
451
+ end
452
+ end
453
+
454
+ Articles::Index.render(format: :html) # => Will use ApplicationLayout
455
+ Articles::Index.render(format: :rss) # => Will use nothing
456
+ ```
457
+
458
+ As per convention, layouts' templates are located under `Lotus::View.root` or `ApplicationLayout.root` and they uses the underscored name (eg. `ApplicationLayout => application.html.erb`).
459
+
460
+ ### Presenters
461
+
462
+ They are a way to wrap and reuse presentational logic for an object.
463
+
464
+ ```ruby
465
+ class Map
466
+ attr_reader :locations
467
+
468
+ def initialize(locations)
469
+ @locations = locations
470
+ end
471
+
472
+ def location_names
473
+ @locations.join(', ')
474
+ end
475
+ end
476
+
477
+ class MapPresenter
478
+ include Lotus::Presenter
479
+
480
+ def count
481
+ locations.count
482
+ end
483
+
484
+ def location_names
485
+ super.upcase
486
+ end
487
+
488
+ def inspect_object
489
+ @object.inspect
490
+ end
491
+ end
492
+
493
+ map = Map.new(['Rome', 'Boston'])
494
+ presenter = MapPresenter.new(map)
495
+
496
+ # access a map method
497
+ puts presenter.locations # => ['Rome', 'Boston']
498
+
499
+ # access presenter concrete methods
500
+ puts presenter.count # => 1
501
+
502
+ # uses super to access original object implementation
503
+ puts presenter.location_names # => 'ROME, BOSTON'
504
+
505
+ # it has private access to the original object
506
+ puts presenter.inspect_object # => #<Map:0x007fdeada0b2f0 @locations=["Rome", "Boston"]>
507
+ ```
508
+
509
+ ### Thread safety
510
+
511
+ **Lotus::View**'s is thread safe during the runtime, but it isn't during the loading process.
512
+ Please load the framework as the last thing before your application starts.
513
+ Also, be sure that your app provides a thread safe context while it's loaded.
514
+
515
+
516
+ ```ruby
517
+ Mutex.new.synchronize do
518
+ Lotus::View.load!
519
+ end
520
+ ```
521
+
522
+ After this operation, all the class variables are frozen, in order to prevent accidental modifications at the run time.
523
+
524
+ **This is not necessary, when Lotus::View is used within a Lotus application.**
525
+
526
+ ## Versioning
527
+
528
+ __Lotus::View__ uses [Semantic Versioning 2.0.0](http://semver.org)
22
529
 
23
530
  ## Contributing
24
531
 
25
- 1. Fork it ( http://github.com/<my-github-username>/lotus-view/fork )
532
+ 1. Fork it
26
533
  2. Create your feature branch (`git checkout -b my-new-feature`)
27
534
  3. Commit your changes (`git commit -am 'Add some feature'`)
28
535
  4. Push to the branch (`git push origin my-new-feature`)
29
536
  5. Create new Pull Request
537
+
538
+ ## Copyright
539
+
540
+ Copyright 2014 Luca Guidi – Released under MIT License
data/Rakefile CHANGED
@@ -1 +1,17 @@
1
- require "bundler/gem_tasks"
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'bundler/gem_tasks'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.pattern = 'test/**/*_test.rb'
7
+ t.libs.push 'test'
8
+ end
9
+
10
+ namespace :test do
11
+ task :coverage do
12
+ ENV['COVERAGE'] = 'true'
13
+ Rake::Task['test'].invoke
14
+ end
15
+ end
16
+
17
+ task default: :test