lotus-view 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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