hanami-view 1.3.1 → 2.0.0.alpha3

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/LICENSE +20 -0
  4. data/README.md +17 -835
  5. data/hanami-view.gemspec +26 -16
  6. data/lib/hanami/view/application_configuration.rb +77 -0
  7. data/lib/hanami/view/application_context.rb +35 -0
  8. data/lib/hanami/view/application_view.rb +89 -0
  9. data/lib/hanami/view/context.rb +97 -0
  10. data/lib/hanami/view/context_helpers/content_helpers.rb +26 -0
  11. data/lib/hanami/view/decorated_attributes.rb +82 -0
  12. data/lib/hanami/view/errors.rb +31 -53
  13. data/lib/hanami/view/exposure.rb +126 -0
  14. data/lib/hanami/view/exposures.rb +74 -0
  15. data/lib/hanami/view/part.rb +217 -0
  16. data/lib/hanami/view/part_builder.rb +140 -0
  17. data/lib/hanami/view/path.rb +68 -0
  18. data/lib/hanami/view/render_environment.rb +62 -0
  19. data/lib/hanami/view/render_environment_missing.rb +44 -0
  20. data/lib/hanami/view/rendered.rb +55 -0
  21. data/lib/hanami/view/renderer.rb +79 -0
  22. data/lib/hanami/view/scope.rb +189 -0
  23. data/lib/hanami/view/scope_builder.rb +98 -0
  24. data/lib/hanami/view/standalone_view.rb +400 -0
  25. data/lib/hanami/view/tilt/erb.rb +26 -0
  26. data/lib/hanami/view/tilt/erbse.rb +21 -0
  27. data/lib/hanami/view/tilt/haml.rb +26 -0
  28. data/lib/hanami/view/tilt.rb +78 -0
  29. data/lib/hanami/view/version.rb +5 -5
  30. data/lib/hanami/view.rb +208 -223
  31. data/lib/hanami-view.rb +3 -1
  32. metadata +120 -70
  33. data/LICENSE.md +0 -22
  34. data/lib/hanami/layout.rb +0 -190
  35. data/lib/hanami/presenter.rb +0 -98
  36. data/lib/hanami/view/configuration.rb +0 -504
  37. data/lib/hanami/view/dsl.rb +0 -347
  38. data/lib/hanami/view/escape.rb +0 -225
  39. data/lib/hanami/view/inheritable.rb +0 -54
  40. data/lib/hanami/view/rendering/layout_finder.rb +0 -128
  41. data/lib/hanami/view/rendering/layout_registry.rb +0 -69
  42. data/lib/hanami/view/rendering/layout_scope.rb +0 -274
  43. data/lib/hanami/view/rendering/null_layout.rb +0 -52
  44. data/lib/hanami/view/rendering/null_local.rb +0 -82
  45. data/lib/hanami/view/rendering/null_template.rb +0 -83
  46. data/lib/hanami/view/rendering/null_view.rb +0 -26
  47. data/lib/hanami/view/rendering/options.rb +0 -24
  48. data/lib/hanami/view/rendering/partial.rb +0 -31
  49. data/lib/hanami/view/rendering/partial_file.rb +0 -29
  50. data/lib/hanami/view/rendering/partial_finder.rb +0 -75
  51. data/lib/hanami/view/rendering/partial_templates_finder.rb +0 -73
  52. data/lib/hanami/view/rendering/registry.rb +0 -134
  53. data/lib/hanami/view/rendering/scope.rb +0 -108
  54. data/lib/hanami/view/rendering/subscope.rb +0 -56
  55. data/lib/hanami/view/rendering/template.rb +0 -69
  56. data/lib/hanami/view/rendering/template_finder.rb +0 -55
  57. data/lib/hanami/view/rendering/template_name.rb +0 -50
  58. data/lib/hanami/view/rendering/templates_finder.rb +0 -144
  59. data/lib/hanami/view/rendering/view_finder.rb +0 -37
  60. data/lib/hanami/view/rendering.rb +0 -294
  61. data/lib/hanami/view/template.rb +0 -57
data/README.md CHANGED
@@ -1,871 +1,53 @@
1
1
  # Hanami::View
2
2
 
3
- A View layer for [Hanami](http://hanamirb.org).
3
+ A view layer for [Hanami](http://hanamirb.org)
4
4
 
5
- It's based on a **separation between views and templates**.
5
+ ## Version
6
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.
7
+ **This branch contains the code for `hanami-view` 2.x.**
16
8
 
17
9
  ## Status
18
10
 
19
11
  [![Gem Version](https://badge.fury.io/rb/hanami-view.svg)](https://badge.fury.io/rb/hanami-view)
20
- [![TravisCI](https://travis-ci.org/hanami/view.svg?branch=master)](https://travis-ci.org/hanami/view)
21
- [![CircleCI](https://circleci.com/gh/hanami/view/tree/master.svg?style=svg)](https://circleci.com/gh/hanami/view/tree/master)
22
- [![Test Coverage](https://codecov.io/gh/hanami/view/branch/master/graph/badge.svg)](https://codecov.io/gh/hanami/view)
23
- [![Depfu](https://badges.depfu.com/badges/4f5c8868d047d206f33893bc9194812d/overview.svg)](https://depfu.com/github/hanami/view?project=Bundler)
12
+ [![CI](https://github.com/hanami/view/workflows/ci/badge.svg?branch=main)](https://github.com/hanami/view/actions?query=workflow%3Aci+branch%3Amain)
13
+ [![Test Coverage](https://codecov.io/gh/hanami/view/branch/main/graph/badge.svg)](https://codecov.io/gh/hanami/view)
14
+ [![Depfu](https://badges.depfu.com/badges/7cd17419fba78b726be1353118fb01de/overview.svg)](https://depfu.com/github/hanami/view?project=Bundler)
24
15
  [![Inline Docs](http://inch-ci.org/github/hanami/view.svg)](http://inch-ci.org/github/hanami/view)
25
16
 
26
17
  ## Contact
27
18
 
28
19
  * Home page: http://hanamirb.org
20
+ * Community: http://hanamirb.org/community
21
+ * Guides: https://guides.hanamirb.org
29
22
  * Mailing List: http://hanamirb.org/mailing-list
30
23
  * API Doc: http://rdoc.info/gems/hanami-view
31
24
  * Bugs/Issues: https://github.com/hanami/view/issues
32
- * Support: http://stackoverflow.com/questions/tagged/hanami
33
25
  * Chat: http://chat.hanamirb.org
34
26
 
35
27
  ## Rubies
36
28
 
37
- __Hanami::View__ supports Ruby (MRI) 2.3+ and JRuby 9.1.5.0+
29
+ __Hanami::view__ supports Ruby (MRI) 2.6+
38
30
 
39
31
  ## Installation
40
32
 
41
33
  Add this line to your application's Gemfile:
42
34
 
43
35
  ```ruby
44
- gem 'hanami-view'
36
+ gem "hanami/view"
45
37
  ```
46
38
 
47
39
  And then execute:
48
40
 
49
- $ bundle
50
-
51
- Or install it yourself as:
52
-
53
- $ gem install hanami-view
54
-
55
- ## Usage
56
-
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.new.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.new.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>
41
+ ```shell
42
+ $ bundle
161
43
  ```
162
44
 
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
- #### Optional View Methods
476
-
477
- If we want to render optional contents such as sidebar links or page specific javascripts, we can use `#local`
478
- It accepts a key that represents a method that should be available within the rendering context.
479
- That context is made of the locals, and the methods that view and layout respond to.
480
- If the context can't dispatch that method, it returns a null object (`Hanami::View::Rendering::NullLocal`).
481
-
482
- Given the following layout template.
483
-
484
- ```erb
485
- <!doctype HTML>
486
- <html>
487
- <!-- ... -->
488
- <body>
489
- <!-- ... -->
490
- <%= local :footer %>
491
- </body>
492
- </html>
493
- ```
494
-
495
- We have two views, one responds to `#footer` (`Products::Show`) and the other doesn't (`Products::Index`).
496
- When the first is rendered, `local` gives back the returning value of `#footer`.
497
- In the other case, `local` returns a null object (`Hanami::View::Rendering::NullLocal`).
498
-
499
- ```ruby
500
- module Products
501
- class Index
502
- include Hanami::View
503
- end
504
-
505
- class Show
506
- include Hanami::View
507
-
508
- def footer
509
- "contents for footer"
510
- end
511
- end
512
- end
513
- ```
514
-
515
- #### Optional Locals
516
-
517
- If we want to show announcements to our customers, but we want only load them from the database if there is something to show.
518
- This is an optional local.
519
-
520
- ```erb
521
- <% if local(:announcement).show? %>
522
- <h2><%= announcement.message %></h2>
523
- <% end %>
524
- ```
525
-
526
- The first line is safely evaluated in all the cases: if announcement is present or not.
527
- In case we enter the `if` statement, we're sure we can safely reference that object.
528
-
529
- ### Presenters
530
-
531
- The goal of a presenter is to wrap and reuse presentational logic for an object.
532
-
533
- ```ruby
534
- class Map
535
- attr_reader :locations
536
-
537
- def initialize(locations)
538
- @locations = locations
539
- end
540
-
541
- def location_names
542
- @locations.join(', ')
543
- end
544
- end
545
-
546
- class MapPresenter
547
- include Hanami::Presenter
548
-
549
- def count
550
- locations.count
551
- end
552
-
553
- def location_names
554
- super.upcase
555
- end
556
-
557
- def inspect_object
558
- @object.inspect
559
- end
560
- end
561
-
562
- map = Map.new(['Rome', 'Boston'])
563
- presenter = MapPresenter.new(map)
564
-
565
- # access a map method
566
- puts presenter.locations # => ['Rome', 'Boston']
567
-
568
- # access presenter concrete methods
569
- puts presenter.count # => 2
570
-
571
- # uses super to access original object implementation
572
- puts presenter.location_names # => 'ROME, BOSTON'
573
-
574
- # it has private access to the original object
575
- puts presenter.inspect_object # => #<Map:0x007fdeada0b2f0 @locations=["Rome", "Boston"]>
576
- ```
577
-
578
- ### Configuration
579
-
580
- __Hanami::View__ can be configured with a DSL that determines its behavior.
581
- It supports a few options:
582
-
583
- ```ruby
584
- require 'hanami/view'
585
-
586
- Hanami::View.configure do
587
- # Set the root path where to search for templates
588
- # Argument: String, Pathname, #to_pathname, defaults to the current directory
589
- #
590
- root '/path/to/root'
591
-
592
- # Default encoding for templates
593
- # Argument: String, defaults to utf-8
594
- #
595
- default_encoding 'koi-8'
596
-
597
- # Set the Ruby namespace where to lookup for views
598
- # Argument: Class, Module, String, defaults to Object
599
- #
600
- namespace 'MyApp::Views'
601
-
602
- # Set the global layout
603
- # Argument: Symbol, defaults to nil
604
- #
605
- layout :application
606
-
607
- # Set modules that you want to include in all views
608
- # Argument: Block
609
- #
610
- prepare do
611
- include MyCustomModule
612
- before { do_something }
613
- end
614
- end
615
- ```
616
-
617
- All those global configurations can be overwritten at a finer grained level:
618
- views. Each view and layout has its own copy of the global configuration, so
619
- that changes are inherited from the top to the bottom, but not bubbled up in the
620
- opposite direction.
621
-
622
- ```ruby
623
- require 'hanami/view'
624
-
625
- Hanami::View.configure do
626
- root '/path/to/root'
627
- end
628
-
629
- class Show
630
- include Hanami::View
631
- root '/another/root'
632
- end
633
-
634
- Hanami::View.configuration.root # => #<Pathname:/path/to/root>
635
- Show.root # => #<Pathname:/another/root>
636
- ```
637
-
638
- ### Reusability
639
-
640
- __Hanami::View__ can be used as a singleton framework as seen in this README.
641
- The application code includes `Hanami::View` or `Hanami::Layout` directly
642
- and the configuration is unique per Ruby process.
643
-
644
- While this is convenient for tiny applications, it doesn't fit well for more
645
- complex scenarios, where we want micro applications to coexist together.
646
-
647
- ```ruby
648
- require 'hanami/view'
649
-
650
- Hanami::View.configure do
651
- root '/path/to/root'
652
- end
653
-
654
- module WebApp
655
- View = Hanami::View.duplicate(self)
656
- end
657
-
658
- module ApiApp
659
- View = Hanami::View.duplicate(self) do
660
- root '/another/root'
661
- end
662
- end
663
-
664
- Hanami::View.configuration.root # => #<Pathname:/path/to/root>
665
- WebApp::View.configuration.root # => #<Pathname:/path/to/root>, inherited from Hanami::View
666
- ApiApp::View.configuration.root # => #<Pathname:/another/root>
667
- ```
668
-
669
- The code above defines `WebApp::View` and `WebApp::Layout`, to be used for
670
- the `WebApp` views, while `ApiApp::View` and `ApiApp::Layout` have a different
671
- configuration.
672
-
673
- ### Thread safety
674
-
675
- __Hanami::View__ is thread safe during the runtime, but it isn't during the loading process.
676
- Please load the framework as the last thing before your application starts.
677
- Also, be sure that your app provides a thread safe context while it's loaded.
678
-
679
-
680
- ```ruby
681
- Mutex.new.synchronize do
682
- Hanami::View.load!
683
- end
684
- ```
685
-
686
- After this operation, all the class variables are frozen, in order to prevent accidental modifications at the run time.
687
-
688
- **This is not necessary, when Hanami::View is used within a Hanami application.**
689
-
690
- ### Security
691
-
692
- The output of views and presenters is always **autoescaped**.
693
-
694
- **ATTENTION:** In order to prevent XSS attacks, please read the instructions below.
695
- Because Hanami::View supports a lot of template engines, the escape happens at the level of the view.
696
- Most of the time everything happens automatically, but there are still some corner cases that need your manual intervention.
697
-
698
- #### View autoescape
699
-
700
- ```ruby
701
- require 'hanami/view'
702
-
703
- User = Struct.new(:name)
704
-
705
- module Users
706
- class Show
707
- include Hanami::View
708
-
709
- def user_name
710
- user.name
711
- end
712
- end
713
- end
714
-
715
- # ERB template
716
- # <div id="user_name"><%= user_name %></div>
717
-
718
- user = User.new("<script>alert('xss')</script>")
719
-
720
- # THIS IS USEFUL FOR UNIT TESTING:
721
- template = Hanami::View::Template.new('users/show.html.erb')
722
- view = Users::Show.new(template, user: user)
723
- view.user_name # => "&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;"
724
-
725
- # THIS IS THE RENDERING OUTPUT:
726
- Users::Show.render(format: :html, user: user)
727
- # => <div id="user_name">&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;</div>
728
- ```
729
-
730
- #### Presenter autoescape
731
-
732
- ```ruby
733
- require 'hanami/view'
734
-
735
- User = Struct.new(:name)
736
-
737
- class UserPresenter
738
- include Hanami::Presenter
739
- end
740
-
741
- user = User.new("<script>alert('xss')</script>")
742
- presenter = UserPresenter.new(user)
743
-
744
- presenter.name # => "&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;"
745
- ```
746
-
747
- #### Escape entire objects
748
-
749
- We have seen that concrete methods in views are automatically escaped.
750
- This is great, but tedious if you need to print a lot of information from a given object.
751
-
752
- Imagine you have `user` as part of the view locals.
753
- If you want to use `<%= user.name %>` directly, **you're still vulnerable to XSS attacks**.
754
-
755
- You have two alternatives:
756
-
757
- * To use a concrete presenter (eg. `UserPresenter`)
758
- * Escape the entire object (see the example below)
759
-
760
- Both those solutions allow you to keep the template syntax unchanged, but to have a safer output.
761
-
762
- ```ruby
763
- require 'hanami/view'
764
-
765
- User = Struct.new(:first_name, :last_name)
766
-
767
- module Users
768
- class Show
769
- include Hanami::View
770
-
771
- def user
772
- _escape locals[:user]
773
- end
774
- end
775
- end
776
-
777
- # ERB template:
778
- #
779
- # <div id="first_name">
780
- # <%= user.first_name %>
781
- # </div>
782
- # <div id="last_name">
783
- # <%= user.last_name %>
784
- # </div>
785
-
786
- first_name = "<script>alert('first_name')</script>"
787
- last_name = "<script>alert('last_name')</script>"
788
-
789
- user = User.new(first_name, last_name)
790
- html = Users::Show.render(format: :html, user: user)
791
-
792
- html
793
- # =>
794
- # <div id="first_name">
795
- # &lt;script&gt;alert(&apos;first_name&apos;)&lt;&#x2F;script&gt;
796
- # </div>
797
- # <div id="last_name">
798
- # &lt;script&gt;alert(&apos;last_name&apos;)&lt;&#x2F;script&gt;
799
- # </div>
800
- ```
801
-
802
- #### Raw contents
803
-
804
- You can use `_raw` to mark an output as safe.
805
- Please note that **this may open your application to XSS attacks.**
806
-
807
- #### Raw contents in views
808
-
809
- ```ruby
810
- require 'hanami/view'
811
-
812
- User = Struct.new(:name)
813
-
814
- module Users
815
- class Show
816
- include Hanami::View
817
-
818
- def user_name
819
- _raw user.name
820
- end
821
- end
822
- end
823
-
824
- # ERB template
825
- # <div id="user_name"><%= user_name %></div>
826
-
827
- user = User.new("<script>alert('xss')</script>")
828
- html = Users::Show.render(format: :html, user: user)
829
-
830
- html
831
- # => <div id="user_name"><script>alert('xss')</script></div>
832
- ```
833
-
834
- #### Raw contents in presenters
835
-
836
- ```ruby
837
- require 'hanami/view'
838
-
839
- User = Struct.new(:name)
840
-
841
- class UserPresenter
842
- include Hanami::Presenter
843
-
844
- def first_name
845
- _raw @object.first_name
846
- end
847
- end
848
-
849
- user = User.new("<script>alert('xss')</script>")
850
- presenter = UserPresenter.new(user)
45
+ Or install it yourself as:
851
46
 
852
- presenter.name # => "<script>alert('xss')</script>"
47
+ ```shell
48
+ $ gem install hanami-view
853
49
  ```
854
50
 
855
- ## Versioning
856
-
857
- __Hanami::View__ uses [Semantic Versioning 2.0.0](http://semver.org)
858
-
859
- ## Contributing
860
-
861
- 1. Fork it
862
- 2. Create your feature branch (`git checkout -b my-new-feature`)
863
- 3. Commit your changes (`git commit -am 'Add some feature'`)
864
- 4. Push to the branch (`git push origin my-new-feature`)
865
- 5. Create new Pull Request
866
-
867
- ## Copyright
868
-
869
- Copyright 2014-2017 Luca Guidi – Released under MIT License
51
+ ## License
870
52
 
871
- This project was formerly known as Lotus (`lotus-view`).
53
+ See `LICENSE` file.