hanami-view 1.3.3 → 2.0.0.alpha6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -9
  3. data/LICENSE +20 -0
  4. data/README.md +17 -835
  5. data/hanami-view.gemspec +27 -16
  6. data/lib/hanami/view/application_configuration.rb +77 -0
  7. data/lib/hanami/view/application_context.rb +57 -0
  8. data/lib/hanami/view/application_view.rb +89 -0
  9. data/lib/hanami/view/context.rb +98 -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 +121 -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 -281
  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
- [![Build Status](https://ci.hanamirb.org/api/badges/hanami/view/status.svg)](https://ci.hanamirb.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) 3.0+
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.