hanami-view 0.0.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b5fa7e5cacbc48414e630515f6c43d5c579514a2
4
- data.tar.gz: 69b8ac7e07ea57ff52c573dfc95dbb6fdd103cb2
3
+ metadata.gz: 0fa2f0c09d6500c146316de763a71a98afefdf03
4
+ data.tar.gz: 7d96ea6c912cdd30fc2945c786c6a5d057cffc98
5
5
  SHA512:
6
- metadata.gz: 854fa6b2b24c398a50901e4ea7f4994901670106c2aef4fcac4b315575652cf3d8527fc056ab39fa0e18fff587786c2460502f54b1fb51d46a6760c9974b0cd7
7
- data.tar.gz: 175ac6b7b96e1efefac54e555d8f39f82ab8061ccb2a9d27f0dac8be7366bc000a37ea8886e00691edc21130ce7800cd9041e33c75c6a51fb0fca0bd6ca5b8a5
6
+ metadata.gz: f818e7eb0e57bf05852b7f8a1b6fe1971ca87d9d20404815d179aa7202eb07c3627bc996e29136babff7d1b2072f531748b6401361e7d7e5a179ed2b7f25979a
7
+ data.tar.gz: 675e51826977c6e4bf1872aa4350e95d68deb225833a8a821b66ac686062f5b3418f9a19918572c850f5d9746bbc10a9e401382da4c27df9fab5d587bb1a9592
@@ -0,0 +1,96 @@
1
+ # Hanami::View
2
+ View layer for Hanami
3
+
4
+ ## v0.6.0 - 2016-01-22
5
+ ### Changed
6
+ - [Luca Guidi] Renamed the project
7
+
8
+ ## v0.5.0 - 2016-01-12
9
+ ### Added
10
+ - [Luca Guidi] Added `Lotus::View::Configuration#default_encoding` to set the encoding for templates
11
+
12
+ ### Fixed
13
+ - [Luca Guidi] Let exceptions to be raised as they occur in rendering context. This fixes misleading backtraces for exceptions.
14
+ - [Martin Rubi] Raise a `Lotus::View::MissingTemplateError` when rendering a missing partial from a template
15
+ - [Luca Guidi] Fix for `template.erb is not valid US-ASCII (Encoding::InvalidByteSequenceError)` when system encoding is not set
16
+
17
+ ### Changed
18
+ - [Liam Dawson] Introduced `Lotus::View::Error` and let all the framework exceptions to inherit from it.
19
+
20
+ ## v0.4.4 - 2015-09-30
21
+ ### Added
22
+ - [Luca Guidi] Autoescape for layout helpers.
23
+
24
+ ## v0.4.3 - 2015-07-10
25
+ ### Fixed
26
+ - [Farrel Lifson] Force partial finder to be explicit when to templates have the same name.
27
+
28
+ ## v0.4.2 - 2015-06-23
29
+ ### Fixed
30
+ - [Tom Kadwill] Ensure views to use methods defined by the associated layout.
31
+
32
+ ## v0.4.1 - 2015-05-22
33
+ ### Added
34
+ - [Luca Guidi] Introduced `#content` to render optional contents in a different context (eg. a view sets a page specific javascript in the application template footer).
35
+
36
+ ## v0.4.0 - 2015-03-23
37
+ ### Changed
38
+ - [Luca Guidi] Autoescape concrete and virtual methods from presenters
39
+ - [Luca Guidi] Autoescape concrete and virtual methods from views
40
+
41
+ ### Fixed
42
+ - [Tom Kadwill] Improve error message for undefined method in view
43
+ - [Luca Guidi] Ensure that layouts will include modules from `Configuration#prepare`
44
+
45
+ ## v0.3.0 - 2014-12-23
46
+ ### Added
47
+ - [Trung Lê] When duplicate the framework, also duplicate `Presenter`
48
+ - [Benny Klotz] Introduced `Scope#class`, `#inspect`, `LayoutScope#class` and `#inspect`
49
+ - [Alfonso Uceda Pompa & Trung Lê] Introduced `Configuration#prepare`
50
+ - [Luca Guidi] Implemented "respond to" logic for `Lotus::View::Scope` (`respond_to?` and `respond_to_missing?`)
51
+ - [Luca Guidi] Implemented "respond to" logic for `Lotus::Layout` (`respond_to?` and `respond_to_missing?`)
52
+ - [Jeremy Stephens] Allow view concrete methods that accept a block to be invoked from templates
53
+ - [Peter Suschlik] Implemented "respond to" logic for `Lotus::Presenter` (`respond_to?` and `respond_to_missing?`)
54
+ - [Luca Guidi] Official support for Ruby 2.2
55
+
56
+ ### Changed
57
+ - [Alfonso Uceda Pompa] Raise an exception when a layout doesn't have an associated template
58
+
59
+ ### Fixed
60
+ - [Luca Guidi] Ensure that concrete methods in layouts are available in templates
61
+ - [Luca Guidi] Ensure to associate the right layout to a view in case fo duplicated framework
62
+ - [Luca Guidi] Safe override of Ruby's top level methods in Scope. (Eg. use `select` from a view, not from `::Kernel`)
63
+
64
+ ## v0.2.0 - 2014-06-23
65
+ ### Added
66
+ - [Luca Guidi] Introduced `Configuration#duplicate`
67
+ - [Luca Guidi] Introduced `Configuration#layout` to define the layout that all the views will use
68
+ - [Luca Guidi] Introduced `Configuration#load_paths` to define several sources where to lookup for templates
69
+ - [Luca Guidi] Introduced `Configuration#root` to define the root path where to find templates
70
+ - [Luca Guidi] Introduced `Lotus::View::Configuration`
71
+ - [Grant Ammons] Allow view concrete methods with arity > 0 to be invoked from templates
72
+ - [Luca Guidi] Official support for Ruby 2.1
73
+
74
+ ### Changed
75
+ - [Luca Guidi] `Rendering::TemplatesFinder` now look recursively for templates, starting from the root.
76
+ - [Luca Guidi] Removed `View.layout=`
77
+ - [Luca Guidi] Removed `View.root=`
78
+
79
+ ### Fixed
80
+ - [Luca Guidi] Ensure outermost locals to not shadow innermost inside templates/partials
81
+
82
+ ## v0.1.0 - 2014-03-23
83
+ ### Added
84
+ - [Luca Guidi] Allow custom rendering policy via `Action#render` override. This bypasses the template lookup and rendering.
85
+ - [Luca Guidi] Introduced `Lotus::Presenter`
86
+ - [Luca Guidi] Introduced templates rendering from templates and layouts
87
+ - [Luca Guidi] Introduced partials rendering from templates and layouts
88
+ - [Luca Guidi] Introduced layouts support
89
+ - [Luca Guidi] Introduced `Lotus::View.load!` as entry point to load views and templates
90
+ - [Luca Guidi] Allow to setup template name via `View.template`
91
+ - [Luca Guidi] Rendering context also considers locals passed to the constructor
92
+ - [Luca Guidi] Introduced `View.format` as DSL to declare which format to handle
93
+ - [Luca Guidi] Introduced view subclasses as way to handle different formats (mime types)
94
+ - [Luca Guidi] Introduced multiple templates per each View
95
+ - [Luca Guidi] Implemented basic rendering with templates
96
+ - [Luca Guidi] Official support for Ruby 2.0
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014-2016 Luca Guidi
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,8 +1,40 @@
1
1
  # Hanami::View
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/hanami/view`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ A View layer for [Hanami](http://hanamirb.org).
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
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 a 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 Hanami components, it can be used as a standalone framework or within a full Hanami application.
16
+
17
+ ## Status
18
+
19
+ [![Gem Version](http://img.shields.io/gem/v/hanami-view.svg)](https://badge.fury.io/rb/hanami-view)
20
+ [![Build Status](http://img.shields.io/travis/hanami/view/master.svg)](https://travis-ci.org/hanami/view?branch=master)
21
+ [![Coverage](http://img.shields.io/coveralls/hanami/view/master.svg)](https://coveralls.io/r/hanami/view)
22
+ [![Code Climate](http://img.shields.io/codeclimate/github/hanami/view.svg)](https://codeclimate.com/github/hanami/view)
23
+ [![Dependencies](http://img.shields.io/gemnasium/hanami/view.svg)](https://gemnasium.com/hanami/view)
24
+ [![Inline docs](http://inch-ci.org/github/hanami/view.svg?branch=master)](http://inch-ci.org/github/hanami/view)
25
+
26
+ ## Contact
27
+
28
+ * Home page: http://hanamirb.org
29
+ * Mailing List: http://hanamirb.org/mailing-list
30
+ * API Doc: http://rdoc.info/gems/hanami-view
31
+ * Bugs/Issues: https://github.com/hanami/view/issues
32
+ * Support: http://stackoverflow.com/questions/tagged/hanami
33
+ * Chat: http://chat.hanamirb.org
34
+
35
+ ## Rubies
36
+
37
+ __Hanami::View__ supports Ruby (MRI) 2+
6
38
 
7
39
  ## Installation
8
40
 
@@ -22,15 +54,802 @@ Or install it yourself as:
22
54
 
23
55
  ## Usage
24
56
 
25
- TODO: Write usage instructions here
57
+ ### Conventions
58
+
59
+ * Templates are searched under `Hanami::View.configuration.root`, set this value according to your app structure (eg. `"app/templates"`).
60
+ * A view will look for a template with a file name that is composed by its full class name (eg. `"articles/index"`).
61
+ * A template must have two concatenated extensions: one for the format and one for the engine (eg. `".html.erb"`).
62
+ * The framework must be loaded before rendering the first time: `Hanami::View.load!`.
63
+
64
+ ### Views
65
+
66
+ A simple view looks like this:
67
+
68
+ ```ruby
69
+ require 'hanami/view'
70
+
71
+ module Articles
72
+ class Index
73
+ include Hanami::View
74
+ end
75
+ end
76
+ ```
77
+
78
+ Suppose that we want to render a list of `articles`:
79
+
80
+ ```ruby
81
+ require 'hanami/view'
82
+
83
+ module Articles
84
+ class Index
85
+ include Hanami::View
86
+ end
87
+ end
88
+
89
+ Hanami::View.configure do
90
+ root 'app/templates'
91
+ end
92
+
93
+ Hanami::View.load!
94
+
95
+ path = Hanami::View.configuration.root.join('articles/index.html.erb')
96
+ template = Hanami::View::Template.new(path)
97
+ articles = ArticleRepository.all
98
+
99
+ Articles::Index.new(template, articles: articles).render
100
+ ```
101
+
102
+ While this code is working fine, it's inefficient and verbose, because we are loading a template from the filesystem for each rendering attempt.
103
+ Also, this is strictly related to the HTML format, what if we want to manage other formats?
104
+
105
+ ```ruby
106
+ require 'hanami/view'
107
+
108
+ module Articles
109
+ class Index
110
+ include Hanami::View
111
+ end
112
+
113
+ class AtomIndex < Index
114
+ format :atom
115
+ end
116
+ end
117
+
118
+ Hanami::View.configure do
119
+ root 'app/templates'
120
+ end
121
+
122
+ Hanami::View.load!
123
+
124
+ articles = ArticleRepository.all
125
+
126
+ Articles::Index.render(format: :html, articles: articles)
127
+ # => This will use Articles::Index
128
+ # and "articles/index.html.erb"
129
+
130
+ Articles::Index.render(format: :atom, articles: articles)
131
+ # => This will use Articles::AtomIndex
132
+ # and "articles/index.atom.erb"
133
+
134
+ Articles::Index.render(format: :xml, articles: articles)
135
+ # => This will raise a Hanami::View::MissingTemplateError
136
+ ```
137
+
138
+ ### Locals
139
+
140
+ All the objects passed in the context are called _locals_, they are available both in the view and in the template:
141
+
142
+ ```ruby
143
+ require 'hanami/view'
144
+
145
+ module Articles
146
+ class Show
147
+ include Hanami::View
148
+
149
+ def authors
150
+ article.authors.map(&:full_name).join ', '
151
+ end
152
+ end
153
+ end
154
+ ```
155
+
156
+ ```erb
157
+ <h1><%= article.title %></h1>
158
+ <article>
159
+ <%= article.content %>
160
+ </article>
161
+ ```
162
+
163
+ All the methods defined in the view are accessible from the template:
164
+
165
+ ```erb
166
+ <h2><%= authors %></h2>
167
+ ```
168
+
169
+ For convenience, they are also available to the view as a Hash, accessed through the `locals` method.
170
+
171
+ ```ruby
172
+ require 'hanami/view'
173
+
174
+ module Articles
175
+ class Show
176
+ include Hanami::View
177
+
178
+ # This view already responds to `#article` because there is an element in
179
+ # the locals with the same key.
180
+ #
181
+ # In order to allow developers to override those methods, and decorate a
182
+ # single locals object, a view has a Hash with the same values.
183
+ #
184
+ # If we had implemented this method like this:
185
+ #
186
+ # def article
187
+ # ArticlePresenter.new(article)
188
+ # end
189
+ #
190
+ # We would have generated a `SystemStackError` (stack level too deep).
191
+ def article
192
+ ArticlePresenter.new(locals[:article])
193
+ end
194
+ end
195
+ end
196
+ ```
197
+
198
+ ### Custom rendering
199
+
200
+ Since a view is an object, you can override `#render` and provide your own rendering policy:
201
+
202
+ ```ruby
203
+ require 'hanami/view'
204
+
205
+ module Articles
206
+ class Show
207
+ include Hanami::View
208
+ format :json
209
+
210
+ def render
211
+ ArticleSerializer.new(article).to_json
212
+ end
213
+ end
214
+ end
215
+
216
+ Articles::Show.render({format: :json, article: article})
217
+ # => This will render from ArticleSerializer,
218
+ # without the need of a template
219
+ ```
220
+
221
+ ### Format
222
+
223
+ The `.format` DSL is used to declare one or more mime types that a view is able to render.
224
+ These values are **arbitrary**, just **be sure to create a corresponding template**.
225
+
226
+ ```ruby
227
+ require 'hanami/view'
228
+
229
+ module Articles
230
+ class Show
231
+ include Hanami::View
232
+ format :custom
233
+ end
234
+ end
235
+
236
+ Articles::Show.render({format: :custom, article: article})
237
+ # => This will render "articles/show.custom.erb"
238
+ ```
239
+
240
+ ### Engines
241
+
242
+ The builtin rendering engine is [ERb](http://en.wikipedia.org/wiki/ERuby).
243
+ However, Hanami::View supports countless rendering engines out of the box.
244
+ Require your library of choice **before** requiring `'hanami/view'`, and it will just work.
245
+
246
+ ```ruby
247
+ require 'haml'
248
+ require 'hanami/view'
249
+
250
+ module Articles
251
+ class Show
252
+ include Hanami::View
253
+ end
254
+ end
255
+
256
+ Articles::Show.render({format: :html, article: article})
257
+ # => This will render "articles/show.html.haml"
258
+ ```
259
+
260
+ This is the list of the supported engines.
261
+ They are listed in order of **higher precedence**, for a given extension.
262
+ For instance, if [ERubis](http://www.kuwata-lab.com/erubis/) is loaded, it will be preferred over ERb to render `.erb` templates.
263
+
264
+ <table>
265
+ <tr>
266
+ <th>Engine</th>
267
+ <th>Extensions</th>
268
+ </tr>
269
+ <tr>
270
+ <td>Erubis</td>
271
+ <td>erb, rhtml, erubis</td>
272
+ </tr>
273
+ <tr>
274
+ <td>ERb</td>
275
+ <td>erb, rhtml</td>
276
+ </tr>
277
+ <tr>
278
+ <td>Redcarpet</td>
279
+ <td>markdown, mkd, md</td>
280
+ </tr>
281
+ <tr>
282
+ <td>RDiscount</td>
283
+ <td>markdown, mkd, md</td>
284
+ </tr>
285
+ <tr>
286
+ <td>Kramdown</td>
287
+ <td>markdown, mkd, md</td>
288
+ </tr>
289
+ <tr>
290
+ <td>Maruku</td>
291
+ <td>markdown, mkd, md</td>
292
+ </tr>
293
+ <tr>
294
+ <td>BlueCloth</td>
295
+ <td>markdown, mkd, md</td>
296
+ </tr>
297
+ <tr>
298
+ <td>Asciidoctor</td>
299
+ <td>ad, adoc, asciidoc</td>
300
+ </tr>
301
+ <tr>
302
+ <td>Builder</td>
303
+ <td>builder</td>
304
+ </tr>
305
+ <tr>
306
+ <td>CSV</td>
307
+ <td>rcsv</td>
308
+ </tr>
309
+ <tr>
310
+ <td>CoffeeScript</td>
311
+ <td>coffee</td>
312
+ </tr>
313
+ <tr>
314
+ <td>WikiCloth</td>
315
+ <td>wiki, mediawiki, mw</td>
316
+ </tr>
317
+ <tr>
318
+ <td>Creole</td>
319
+ <td>wiki, creole</td>
320
+ </tr>
321
+ <tr>
322
+ <td>Etanni</td>
323
+ <td>etn, etanni</td>
324
+ </tr>
325
+ <tr>
326
+ <td>Haml</td>
327
+ <td>haml</td>
328
+ </tr>
329
+ <tr>
330
+ <td>Less</td>
331
+ <td>less</td>
332
+ </tr>
333
+ <tr>
334
+ <td>Liquid</td>
335
+ <td>liquid</td>
336
+ </tr>
337
+ <tr>
338
+ <td>Markaby</td>
339
+ <td>mab</td>
340
+ </tr>
341
+ <tr>
342
+ <td>Nokogiri</td>
343
+ <td>nokogiri</td>
344
+ </tr>
345
+ <tr>
346
+ <td>Plain</td>
347
+ <td>html</td>
348
+ </tr>
349
+ <tr>
350
+ <td>RDoc</td>
351
+ <td>rdoc</td>
352
+ </tr>
353
+ <tr>
354
+ <td>Radius</td>
355
+ <td>radius</td>
356
+ </tr>
357
+ <tr>
358
+ <td>RedCloth</td>
359
+ <td>textile</td>
360
+ </tr>
361
+ <tr>
362
+ <td>Sass</td>
363
+ <td>sass</td>
364
+ </tr>
365
+ <tr>
366
+ <td>Scss</td>
367
+ <td>scss</td>
368
+ </tr>
369
+ <tr>
370
+ <td>Slim</td>
371
+ <td>slim</td>
372
+ </tr>
373
+ <tr>
374
+ <td>String</td>
375
+ <td>str</td>
376
+ </tr>
377
+ <tr>
378
+ <td>Yajl</td>
379
+ <td>yajl</td>
380
+ </tr>
381
+ </table>
382
+
383
+ ### Root
384
+
385
+ Template lookup is performed under the `Hanami::View.configuration.root` directory. You can specify a different path on a per view basis:
386
+
387
+ ```ruby
388
+ class ViewWithDifferentRoot
389
+ include Hanami::View
390
+
391
+ root 'path/to/root'
392
+ end
393
+ ```
394
+
395
+ ### Template
396
+
397
+ The template file must be located under the relevant `root` and must match the class name:
398
+
399
+ ```ruby
400
+ puts Hanami::View.configuration.root # => #<Pathname:app/templates>
401
+ Articles::Index.template # => "articles/index"
402
+ ```
403
+
404
+ Each view can specify a different template:
405
+
406
+ ```ruby
407
+ module Articles
408
+ class Create
409
+ include Hanami::View
410
+
411
+ template 'articles/new'
412
+ end
413
+ end
414
+
415
+ Articles::Create.template # => "articles/new"
416
+ ```
417
+
418
+ ### Partials
419
+
420
+ Partials can be rendered within a template:
421
+
422
+ ```erb
423
+ <%= render partial: 'articles/form', locals: { secret: 23 } %>
424
+ ```
425
+
426
+ It will look for a template `articles/_form.html.erb` and make available both the view's and partial's locals (eg. `article` and `secret`).
427
+
428
+ ### Templates
429
+
430
+ Templates can be rendered within another template:
431
+
432
+ ```erb
433
+ <%= render template: 'articles/new', locals: { errors: {} } %>
434
+ ```
435
+
436
+ It will render `articles/new.html.erb` and make available both the view's and templates's locals (eg. `article` and `errors`).
437
+
438
+ ### Layouts
439
+
440
+ Layouts are wrappers for views. Layouts may serve to reuse common markup.
441
+
442
+ ```ruby
443
+ class ApplicationLayout
444
+ include Hanami::Layout
445
+
446
+ def page_title
447
+ 'Title:'
448
+ end
449
+ end
450
+
451
+ module Articles
452
+ class Index
453
+ include Hanami::View
454
+ layout :application
455
+
456
+ def page_title
457
+ "#{ layout.page_title } articles"
458
+ end
459
+ end
460
+
461
+ class RssIndex < Index
462
+ format :rss
463
+ layout false
464
+ end
465
+ end
466
+
467
+ Articles::Index.render(format: :html) # => Will use ApplicationLayout
468
+ Articles::Index.render(format: :rss) # => Will use nothing
469
+ ```
470
+
471
+ As per convention, layout templates are located under `Hanami::View.root` or `ApplicationLayout.root` and use the underscored name (eg. `ApplicationLayout => application.html.erb`).
472
+
473
+ ### Optional Content
474
+
475
+ If we want to render optional contents such as sidebar links or page specific javascripts, we can use `#content`
476
+ It accepts a key that represents a method that should be available within the rendering context.
477
+ That context is made of the locals, and the methods that view and layout respond to.
478
+ If the context can't dispatch that method, it returns `nil`.
479
+
480
+ Given the following layout template.
26
481
 
27
- ## Development
482
+ ```erb
483
+ <!doctype HTML>
484
+ <html>
485
+ <!-- ... -->
486
+ <body>
487
+ <!-- ... -->
488
+ <%= content :footer %>
489
+ </body>
490
+ </html>
491
+ ```
492
+
493
+ We have two views, one responds to `#footer` (`Products::Show`) and the other doesn't (`Products::Index`).
494
+ When the first is rendered, `content` gives back the returning value of `#footer`.
495
+ In the other case, `content` returns `nil`.
496
+
497
+ ```ruby
498
+ module Products
499
+ class Index
500
+ include Hanami::View
501
+ end
502
+
503
+ class Show
504
+ include Hanami::View
505
+
506
+ def footer
507
+ "contents for footer"
508
+ end
509
+ end
510
+ end
511
+ ```
512
+
513
+ ### Presenters
514
+
515
+ The goal of a presenter is to wrap and reuse presentational logic for an object.
516
+
517
+ ```ruby
518
+ class Map
519
+ attr_reader :locations
520
+
521
+ def initialize(locations)
522
+ @locations = locations
523
+ end
524
+
525
+ def location_names
526
+ @locations.join(', ')
527
+ end
528
+ end
529
+
530
+ class MapPresenter
531
+ include Hanami::Presenter
532
+
533
+ def count
534
+ locations.count
535
+ end
536
+
537
+ def location_names
538
+ super.upcase
539
+ end
540
+
541
+ def inspect_object
542
+ @object.inspect
543
+ end
544
+ end
545
+
546
+ map = Map.new(['Rome', 'Boston'])
547
+ presenter = MapPresenter.new(map)
548
+
549
+ # access a map method
550
+ puts presenter.locations # => ['Rome', 'Boston']
551
+
552
+ # access presenter concrete methods
553
+ puts presenter.count # => 2
554
+
555
+ # uses super to access original object implementation
556
+ puts presenter.location_names # => 'ROME, BOSTON'
557
+
558
+ # it has private access to the original object
559
+ puts presenter.inspect_object # => #<Map:0x007fdeada0b2f0 @locations=["Rome", "Boston"]>
560
+ ```
561
+
562
+ ### Configuration
563
+
564
+ __Hanami::View__ can be configured with a DSL that determines its behavior.
565
+ It supports a few options:
566
+
567
+ ```ruby
568
+ require 'hanami/view'
569
+
570
+ Hanami::View.configure do
571
+ # Set the root path where to search for templates
572
+ # Argument: String, Pathname, #to_pathname, defaults to the current directory
573
+ #
574
+ root '/path/to/root'
575
+
576
+ # Default encoding for templates
577
+ # Argument: String, defaults to utf-8
578
+ #
579
+ default_encoding 'koi-8'
580
+
581
+ # Set the Ruby namespace where to lookup for views
582
+ # Argument: Class, Module, String, defaults to Object
583
+ #
584
+ namespace 'MyApp::Views'
585
+
586
+ # Set the global layout
587
+ # Argument: Symbol, defaults to nil
588
+ #
589
+ layout :application
590
+
591
+ # Set modules that you want to include in all views
592
+ # Argument: Block
593
+ #
594
+ prepare do
595
+ include MyCustomModule
596
+ before { do_something }
597
+ end
598
+ end
599
+ ```
600
+
601
+ All those global configurations can be overwritten at a finer grained level:
602
+ views. Each view and layout has its own copy of the global configuration, so
603
+ that changes are inherited from the top to the bottom, but not bubbled up in the
604
+ opposite direction.
605
+
606
+ ```ruby
607
+ require 'hanami/view'
608
+
609
+ Hanami::View.configure do
610
+ root '/path/to/root'
611
+ end
612
+
613
+ class Show
614
+ include Hanami::View
615
+ root '/another/root'
616
+ end
617
+
618
+ Hanami::View.configuration.root # => #<Pathname:/path/to/root>
619
+ Show.root # => #<Pathname:/another/root>
620
+ ```
621
+
622
+ ### Reusability
623
+
624
+ __Hanami::View__ can be used as a singleton framework as seen in this README.
625
+ The application code includes `Hanami::View` or `Hanami::Layout` directly
626
+ and the configuration is unique per Ruby process.
627
+
628
+ While this is convenient for tiny applications, it doesn't fit well for more
629
+ complex scenarios, where we want micro applications to coexist together.
630
+
631
+ ```ruby
632
+ require 'hanami/view'
633
+
634
+ Hanami::View.configure do
635
+ root '/path/to/root'
636
+ end
637
+
638
+ module WebApp
639
+ View = Hanami::View.duplicate(self)
640
+ end
641
+
642
+ module ApiApp
643
+ View = Hanami::View.duplicate(self) do
644
+ root '/another/root'
645
+ end
646
+ end
647
+
648
+ Hanami::View.configuration.root # => #<Pathname:/path/to/root>
649
+ WebApp::View.configuration.root # => #<Pathname:/path/to/root>, inherited from Hanami::View
650
+ ApiApp::View.configuration.root # => #<Pathname:/another/root>
651
+ ```
652
+
653
+ The code above defines `WebApp::View` and `WebApp::Layout`, to be used for
654
+ the `WebApp` views, while `ApiApp::View` and `ApiApp::Layout` have a different
655
+ configuration.
656
+
657
+ ### Thread safety
658
+
659
+ __Hanami::View__ is thread safe during the runtime, but it isn't during the loading process.
660
+ Please load the framework as the last thing before your application starts.
661
+ Also, be sure that your app provides a thread safe context while it's loaded.
662
+
663
+
664
+ ```ruby
665
+ Mutex.new.synchronize do
666
+ Hanami::View.load!
667
+ end
668
+ ```
669
+
670
+ After this operation, all the class variables are frozen, in order to prevent accidental modifications at the run time.
671
+
672
+ **This is not necessary, when Hanami::View is used within a Hanami application.**
673
+
674
+ ### Security
28
675
 
29
- After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
676
+ The output of views and presenters is always **autoescaped**.
30
677
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
678
+ **ATTENTION:** In order to prevent XSS attacks, please read the instructions below.
679
+ Because Hanami::View supports a lot of template engines, the escape happens at the level of the view.
680
+ Most of the time everything happens automatically, but there are still some corner cases that need your manual intervention.
681
+
682
+ #### View autoescape
683
+
684
+ ```ruby
685
+ require 'hanami/view'
686
+
687
+ User = Struct.new(:name)
688
+
689
+ module Users
690
+ class Show
691
+ include Hanami::View
692
+
693
+ def user_name
694
+ user.name
695
+ end
696
+ end
697
+ end
698
+
699
+ # ERB template
700
+ # <div id="user_name"><%= user_name %></div>
701
+
702
+ user = User.new("<script>alert('xss')</script>")
703
+
704
+ # THIS IS USEFUL FOR UNIT TESTING:
705
+ template = Hanami::View::Template.new('users/show.html.erb')
706
+ view = Users::Show.new(template, user: user)
707
+ view.user_name # => "&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;"
708
+
709
+ # THIS IS THE RENDERING OUTPUT:
710
+ Users::Show.render(format: :html, user: user)
711
+ # => <div id="user_name">&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;</div>
712
+ ```
713
+
714
+ #### Presenter autoescape
715
+
716
+ ```ruby
717
+ require 'hanami/view'
718
+
719
+ User = Struct.new(:name)
720
+
721
+ class UserPresenter
722
+ include Hanami::Presenter
723
+ end
724
+
725
+ user = User.new("<script>alert('xss')</script>")
726
+ presenter = UserPresenter.new(user)
727
+
728
+ presenter.name # => "&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;"
729
+ ```
730
+
731
+ #### Escape entire objects
732
+
733
+ We have seen that concrete methods in views are automatically escaped.
734
+ This is great, but tedious if you need to print a lot of information from a given object.
735
+
736
+ Imagine you have `user` as part of the view locals.
737
+ If you want to use `<%= user.name %>` directly, **you're still vulnerable to XSS attacks**.
738
+
739
+ You have two alternatives:
740
+
741
+ * To use a concrete presenter (eg. `UserPresenter`)
742
+ * Escape the entire object (see the example below)
743
+
744
+ Both those solutions allow you to keep the template syntax unchanged, but to have a safer output.
745
+
746
+ ```ruby
747
+ require 'hanami/view'
748
+
749
+ User = Struct.new(:first_name, :last_name)
750
+
751
+ module Users
752
+ class Show
753
+ include Hanami::View
754
+
755
+ def user
756
+ _escape locals[:user]
757
+ end
758
+ end
759
+ end
760
+
761
+ # ERB template:
762
+ #
763
+ # <div id="first_name">
764
+ # <%= user.first_name %>
765
+ # </div>
766
+ # <div id="last_name">
767
+ # <%= user.last_name %>
768
+ # </div>
769
+
770
+ first_name = "<script>alert('first_name')</script>"
771
+ last_name = "<script>alert('last_name')</script>"
772
+
773
+ user = User.new(first_name, last_name)
774
+ html = Users::Show.render(format: :html, user: user)
775
+
776
+ html
777
+ # =>
778
+ # <div id="first_name">
779
+ # &lt;script&gt;alert(&apos;first_name&apos;)&lt;&#x2F;script&gt;
780
+ # </div>
781
+ # <div id="last_name">
782
+ # &lt;script&gt;alert(&apos;last_name&apos;)&lt;&#x2F;script&gt;
783
+ # </div>
784
+ ```
785
+
786
+ #### Raw contents
787
+
788
+ You can use `_raw` to mark an output as safe.
789
+ Please note that **this may open your application to XSS attacks.**
790
+
791
+ #### Raw contents in views
792
+
793
+ ```ruby
794
+ require 'hanami/view'
795
+
796
+ User = Struct.new(:name)
797
+
798
+ module Users
799
+ class Show
800
+ include Hanami::View
801
+
802
+ def user_name
803
+ _raw user.name
804
+ end
805
+ end
806
+ end
807
+
808
+ # ERB template
809
+ # <div id="user_name"><%= user_name %></div>
810
+
811
+ user = User.new("<script>alert('xss')</script>")
812
+ html = Users::Show.render(format: :html, user: user)
813
+
814
+ html
815
+ # => <div id="user_name"><script>alert('xss')</script></div>
816
+ ```
817
+
818
+ #### Raw contents in presenters
819
+
820
+ ```ruby
821
+ require 'hanami/view'
822
+
823
+ User = Struct.new(:name)
824
+
825
+ class UserPresenter
826
+ include Hanami::Presenter
827
+
828
+ def first_name
829
+ _raw @object.first_name
830
+ end
831
+ end
832
+
833
+ user = User.new("<script>alert('xss')</script>")
834
+ presenter = UserPresenter.new(user)
835
+
836
+ presenter.name # => "<script>alert('xss')</script>"
837
+ ```
838
+
839
+ ## Versioning
840
+
841
+ __Hanami::View__ uses [Semantic Versioning 2.0.0](http://semver.org)
32
842
 
33
843
  ## Contributing
34
844
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/hanami-view.
845
+ 1. Fork it
846
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
847
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
848
+ 4. Push to the branch (`git push origin my-new-feature`)
849
+ 5. Create new Pull Request
850
+
851
+ ## Copyright
852
+
853
+ Copyright 2014-2016 Luca Guidi – Released under MIT License
36
854
 
855
+ This project was formerly known as Lotus (`lotus-view`).