dry-view 0.5.1 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +143 -18
  3. data/LICENSE +20 -0
  4. data/README.md +22 -14
  5. data/dry-view.gemspec +29 -21
  6. data/lib/dry-view.rb +3 -1
  7. data/lib/dry/view.rb +514 -2
  8. data/lib/dry/view/context.rb +80 -0
  9. data/lib/dry/view/decorated_attributes.rb +82 -0
  10. data/lib/dry/view/errors.rb +29 -0
  11. data/lib/dry/view/exposure.rb +35 -14
  12. data/lib/dry/view/exposures.rb +18 -6
  13. data/lib/dry/view/part.rb +166 -53
  14. data/lib/dry/view/part_builder.rb +140 -0
  15. data/lib/dry/view/path.rb +35 -7
  16. data/lib/dry/view/render_environment.rb +62 -0
  17. data/lib/dry/view/render_environment_missing.rb +44 -0
  18. data/lib/dry/view/rendered.rb +55 -0
  19. data/lib/dry/view/renderer.rb +36 -29
  20. data/lib/dry/view/scope.rb +160 -14
  21. data/lib/dry/view/scope_builder.rb +98 -0
  22. data/lib/dry/view/tilt.rb +78 -0
  23. data/lib/dry/view/tilt/erb.rb +26 -0
  24. data/lib/dry/view/tilt/erbse.rb +21 -0
  25. data/lib/dry/view/tilt/haml.rb +26 -0
  26. data/lib/dry/view/version.rb +5 -2
  27. metadata +78 -115
  28. data/.gitignore +0 -26
  29. data/.rspec +0 -2
  30. data/.travis.yml +0 -23
  31. data/CONTRIBUTING.md +0 -29
  32. data/Gemfile +0 -22
  33. data/LICENSE.md +0 -10
  34. data/Rakefile +0 -6
  35. data/benchmarks/templates/button.html.erb +0 -1
  36. data/benchmarks/view.rb +0 -24
  37. data/bin/console +0 -7
  38. data/lib/dry/view/controller.rb +0 -155
  39. data/lib/dry/view/decorator.rb +0 -45
  40. data/lib/dry/view/missing_renderer.rb +0 -15
  41. data/spec/fixtures/templates/_hello.html.slim +0 -1
  42. data/spec/fixtures/templates/decorated_parts.html.slim +0 -4
  43. data/spec/fixtures/templates/edit.html.slim +0 -11
  44. data/spec/fixtures/templates/empty.html.slim +0 -1
  45. data/spec/fixtures/templates/greeting.html.slim +0 -2
  46. data/spec/fixtures/templates/hello.html.slim +0 -1
  47. data/spec/fixtures/templates/layouts/app.html.slim +0 -6
  48. data/spec/fixtures/templates/layouts/app.txt.erb +0 -3
  49. data/spec/fixtures/templates/parts_with_args.html.slim +0 -3
  50. data/spec/fixtures/templates/parts_with_args/_box.html.slim +0 -3
  51. data/spec/fixtures/templates/shared/_index_table.html.slim +0 -2
  52. data/spec/fixtures/templates/shared/_shared_hello.html.slim +0 -1
  53. data/spec/fixtures/templates/tasks.html.slim +0 -3
  54. data/spec/fixtures/templates/user.html.slim +0 -2
  55. data/spec/fixtures/templates/users.html.slim +0 -5
  56. data/spec/fixtures/templates/users.txt.erb +0 -3
  57. data/spec/fixtures/templates/users/_row.html.slim +0 -2
  58. data/spec/fixtures/templates/users/_tbody.html.slim +0 -5
  59. data/spec/fixtures/templates/users_with_count.html.slim +0 -5
  60. data/spec/fixtures/templates/users_with_count_inherit.html.slim +0 -6
  61. data/spec/fixtures/templates_override/_hello.html.slim +0 -1
  62. data/spec/fixtures/templates_override/users.html.slim +0 -5
  63. data/spec/integration/decorator_spec.rb +0 -80
  64. data/spec/integration/exposures_spec.rb +0 -392
  65. data/spec/integration/part/decorated_attributes_spec.rb +0 -157
  66. data/spec/integration/view_spec.rb +0 -133
  67. data/spec/spec_helper.rb +0 -46
  68. data/spec/unit/controller_spec.rb +0 -37
  69. data/spec/unit/decorator_spec.rb +0 -61
  70. data/spec/unit/exposure_spec.rb +0 -227
  71. data/spec/unit/exposures_spec.rb +0 -103
  72. data/spec/unit/part_spec.rb +0 -90
  73. data/spec/unit/renderer_spec.rb +0 -57
  74. data/spec/unit/scope_spec.rb +0 -53
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b7142689536853b1e216512d00558c4edbaafe44ee3abc2d1e51eb1ad498bc1
4
- data.tar.gz: 6373160c6fa073a52c9fa106b6f1c8ac0b58c088bbce26e29b4b65ff587dad27
3
+ metadata.gz: 5e819c2e7fc0b6fe2aed059b2d63993b2e107e7d9b682652dc9a0dedefdf7458
4
+ data.tar.gz: e45c5c695005d30eb755ccffd22efc5bbdf7c91b018f4c2fe859cb6b015f70bc
5
5
  SHA512:
6
- metadata.gz: 0eaccf27fc9d7f29a8b3be6e9b695341738e67d98b132630d8ec6bc462683bdcfa4613ed59c00a8d3791f93789f4a5c437ee6dd2b66c5c893d52e537a3a93f51
7
- data.tar.gz: a6b0bb154f5056de7b1bf6ca21d78e7ad9e1453dcc64d438d7f435e6285f236e0627d55838ba020bdf68749a4a1da577b6dc1e2cacc9e8b8993ba553d99a797d
6
+ metadata.gz: a4797bca5d6dba8ba676794d9ff21c531c1ee303a0af87f924ee09e89c9faadc898f5b1dc9d24731c32e62d5b5c031efa4b6f8a7062ab30bd3f7ea4397eac7b2
7
+ data.tar.gz: 60fe4d2131720fd19cebe465283f06f05abb0247b094fc946ff96ac9b0b77c5962561ee68da1dd067876ace1d5def0caccdf18e7598a25800cf199aad5f36772
data/CHANGELOG.md CHANGED
@@ -1,21 +1,129 @@
1
- # 0.5.1 / 2018-02-20
1
+ <!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
2
+
3
+ ## 0.7.1 2021-02-08
4
+
5
+
6
+ ### Fixed
7
+
8
+ - Template not found errors properly show configured paths (@adam12 in #144)
9
+
10
+ ### Changed
11
+
12
+ - Compatible with Ruby 2.7/3.0 keyword argument handling (@flash-gordon in 4e7cefb)
13
+ - dry-equalizer dependency dropped in favor of dry-core (@solnic)
14
+
15
+ [Compare v0.7.0...v0.7.1](https://github.com/dry-rb/dry-view/compare/v0.7.0...v0.7.1)
16
+
17
+ ## 0.7.0 2019-03-06
18
+
19
+
20
+ ### Added
21
+
22
+ - Raise a `Dry::View::UndefinedConfigError` when a view is called but no paths have been configured (timriley in [#130][pr130])
23
+
24
+ ### Fixed
25
+
26
+ - Avoid a `SystemStackError` when a view is configured with a template that cannot be found on the filesystem (timriley in [#129][pr129])
27
+
28
+ ### Changed
29
+
30
+ - [BREAKING] Move `Dry::View::Renderer::TemplateNotFoundError` to `Dry::View::TemplateNotFoundError` (timriley in [#130][pr130])
31
+ - [BREAKING] `Dry::View::UndefinedConfigError` is raised instead of `Dry::View::UndefinedTemplateError` when a view is called but no template has been configured (timriley in [#130][pr130])
32
+ - Stop searching upwards through parent directories when rendering a view's template (as opposed to partials) (timriley in [#130][pr130])
33
+ - Stop searching in `shared/` subdirectories when rendering a view's template (as opposed to partials) (timriley in [#130][pr130])
34
+ - Adjust template lookup cache keys to ensure no false hits (timriley in [#130][pr130])
35
+
36
+ [Compare v0.6.0...v0.7.0](https://github.com/dry-rb/dry-view/compare/v0.6.0...v0.7.0)
37
+
38
+ ## 0.6.0 2019-01-30
39
+
40
+
41
+ ### Added
42
+
43
+ - [BREAKING] `Dry::View#call` now returns a `Dry::View::Rendered` instance, which carries both the rendered output (accessible via `#to_s` or `#to_str`) as well as all of the view's locals, wrapped in their view parts (accessible via `#locals` or individually via `#[]`) (timriley in [#72][pr72])
44
+ - [BREAKING] Added `Dry::View::PartBuilder` (renamed from `Dry::View::Decorator`), which resolves part classes from a namespace configured via View's `part_namespace` setting. A custom part builder can be specified via a View's `part_builder` setting. (timriley in [#80][pr80])
45
+ - [BREAKING] Context classes can now declare decorated attributes just like part classes, via `.decorate` class-level API. Context classes are now required to inherit from `Dry::View::Context`. `Dry::View::Context` provides a `#with` method for creating copies of itself while preserving the rendering details needed for decorated attributes to work (timriley in [#89][pr89] and [#91][pr91])
46
+ - Customizable _scope_ objects, which work like view parts, but instead of encapsulating a single value, they encapsulate a whole template or partial and all of its locals. Scopes can be created via `#scope` method in templates, parts, as well as scope classes themselves. Scope classes are resolved via a View's `scope_builder` setting, which defaults to an instance of `Dry::View::ScopeBuilder`.
47
+ - Added `inflector` setting to View, which is used by the part and scope builders to resolve classes for a given part or scope name. Defaults to `Dry::Inflector.new` (timriley in [#80][pr80] and [#90][pr90])
48
+ - Exposures can be sent to the layout template when defined with `layout: true` option (GustavoCaso in [#87][pr87])
49
+ - Exposures can be left undecorated by a part when defined with `decorate: false` option (timriley in [#88][pr88])
50
+ - Part classes have access to the current template format via a private `#_format` method (timriley in [#118][pr118])
51
+ - Added "Tilt adapter" layer, to ensure a rendering engine compatible with dry-view's features is being used. Added adapters for "haml" and "erb" templates to ensure that "hamlit-block" and "erbse" are required and used as engines (unlike their more common counterparts, both of these engines support the implicit block capturing that is a central part of dry-view rendering behaviour) (timriley in [#106][pr106])
52
+ - Added `renderer_engine_mapping` setting to View, which allows an explicit engine class to be provided for the rendering of a given type of template (e.g. `config.renderer_engine_mapping = {erb: Tilt::ErubiTemplate}`) (timriley in [#106][pr106])
53
+
54
+ ### Fixed
55
+
56
+ - Preserve renderer options when chdir-ing (timriley in [889ac7b](https://github.com/dry-rb/dry-view/commit/889ac7b))
57
+
58
+ ### Changed
59
+
60
+ - [BREAKING] `Dry::View::Controller` renamed to `Dry::View` (timriley in [#115][pr115])
61
+ - [BREAKING] `Dry::View` `context` setting renamed to `default_context` (GustavoCaso in [#86][pr86])
62
+ - Exposure values are wrapped in their view parts before being made available as exposure dependencies (timriley in [#80][pr80])
63
+ - Exposures can access current context object through `context:` block or method parameter (timriley in [#119][pr119])
64
+ - Improved performance due to caching various lookups (timriley and GustavoCaso in [#97][pr97])
65
+ - `Part#inspect` output simplified to include only name and value (timriley in [#98][pr98])
66
+ - Attribute decoration in `Part` now achieved via a prepended module, which means it is possible to decorate an attribute provided by an instance method directly on the part class, which wasn't possible with the previous `method_missing`-based approach (timriley in [#110][pr110])
67
+ - `Part` classes can be initialized with missing `name:` and `rendering:` values, which can be useful for unit testing Part methods that don't use any rendering facilities (timriley in [#116][pr116])
68
+
69
+ [Compare v0.5.4...v0.6.0](https://github.com/dry-rb/dry-view/compare/v0.5.4...v0.6.0)
70
+
71
+ ## 0.5.4 2019-01-06
72
+
73
+ This version was yanked due to the release accidentally containing a batch of breaking changes from master.
74
+
75
+ ### Fixed
76
+
77
+ - Preserve renderer options when chdir-ing (timriley in [889ac7b](https://github.com/dry-rb/dry-view/commit/889ac7b))
78
+
79
+
80
+ [Compare v0.5.3...v0.5.4](https://github.com/dry-rb/dry-view/compare/v0.5.3...v0.5.4)
81
+
82
+ ## 0.5.3 2018-10-22
83
+
84
+
85
+ ### Added
86
+
87
+ - `renderer_options` setting for configuring tilt-based renderer (liseki in [#62][pr62])
88
+
89
+ ### Changed
90
+
91
+ - Part objects wrap values more transparently, via added `#respond_to_missing?` (liseki in [#63][pr63])
92
+
93
+ [Compare v0.5.2...v0.5.3](https://github.com/dry-rb/dry-view/compare/v0.5.2...v0.5.3)
94
+
95
+ ## 0.5.2 2018-06-13
96
+
97
+
98
+ ### Changed
99
+
100
+ - Only truthy view part attributes are decorated (timriley)
101
+
102
+ [Compare v0.5.1...v0.5.2](https://github.com/dry-rb/dry-view/compare/v0.5.1...v0.5.2)
103
+
104
+ ## 0.5.1 2018-02-20
105
+
2
106
 
3
107
  ### Added
4
108
 
5
109
  - Exposures are inherited from parent view controller classes (GustavoCaso)
6
110
 
111
+
7
112
  [Compare v0.5.0...v0.5.1](https://github.com/dry-rb/dry-view/compare/v0.5.0...v0.5.1)
8
113
 
9
- # 0.5.0 / 2018-01-23
114
+ ## 0.5.0 2018-01-23
115
+
10
116
 
11
117
  ### Added
12
118
 
13
119
  - Support for parts with decorated attributes (timriley + GustavoCaso)
14
120
  - Ability to easily create another part instance via `Part#new` (GustavoCaso)
15
121
 
122
+
16
123
  [Compare v0.4.0...v0.5.0](https://github.com/dry-rb/dry-view/compare/v0.4.0...v0.5.0)
17
124
 
18
- # 0.4.0 / 2017-11-01
125
+ ## 0.4.0 2017-11-01
126
+
19
127
 
20
128
  ### Added
21
129
 
@@ -29,35 +137,52 @@
29
137
  - Allow `Dry::View::Part` instances to be created without explicitly passing a `renderer`. This is helpful for unit testing view parts that don't need to render anything (dNitza)
30
138
  - Partials can be nested within additional sub-directories by rendering them their relative path as their name, e.g. `render(:"foo/bar")` will look for a `foo/_bar.html.slim` template within the normal template lookup paths (timriley)
31
139
 
32
- # 0.3.0 / 2017-05-14
140
+ [Compare v0.3.0...v0.4.0](https://github.com/dry-rb/dry-view/compare/v0.3.0...v0.4.0)
141
+
142
+ ## 0.3.0 2017-05-14
33
143
 
34
144
  This release reintroduces view parts in a more helpful form. You can provide your own custom view part classes to encapsulate your view logic, as well as a decorator for custom, shared behavior arouund view part wrapping.
35
145
 
146
+ ### Added
147
+
148
+ - Wrap all values passed to the template in `Dry::View::Part` objects
149
+ - Added a `decorator` config to `Dry::View::Controller`, with a default `Dry::View::Decorator` that wraps the exposure values in `Dry::View::Part` objects (as above). Provide your own part classes by passing an `:as` option to your exposures, e.g. `expose :user, as: MyApp::UserPart`
150
+
36
151
  ### Changed
37
152
 
38
153
  - [BREAKING] Partial rendering in templates requires an explicit `render` method call instead of method_missing behaviour usinig the partial's name (e.g. `<%= render :my_partial %>` instead of `<%= my_partial %>`)
39
154
 
40
- ### Added
155
+ [Compare v0.2.2...v0.3.0](https://github.com/dry-rb/dry-view/compare/v0.2.2...v0.3.0)
41
156
 
42
- - Wrap all values passed to the template in `Dry::View::Part` objects
43
- - Added a `decorator` config to `Dry::View::Controller`, with a default `Dry::View::Decorator` that wraps the exposure values in `Dry::View::Part` objects (as above). Provide your own part classes by passing an `:as` option to your exposures, e.g. `expose :user, as: MyApp::UserPart`
157
+ ## 0.2.2 2017-01-31
44
158
 
45
- # 0.2.2 / 2017-01-31
46
159
 
47
160
  ### Changed
48
161
 
49
162
  - Make input passthrough exposures (when there is no block or matching instance metod) return nil instead of raise in the case of a missing input key (timriley)
50
163
 
51
- # 0.2.1 / 2017-01-30
164
+ [Compare v0.2.1...v0.2.2](https://github.com/dry-rb/dry-view/compare/v0.2.1...v0.2.2)
165
+
166
+ ## 0.2.1 2017-01-30
167
+
52
168
 
53
169
  ### Fixed
54
170
 
55
171
  - Exposure blocks now have access to the view controller instance when they're called (timriley)
56
172
 
57
- # 0.2.0 / 2017-01-30
173
+
174
+ [Compare v0.2.0...v0.2.1](https://github.com/dry-rb/dry-view/compare/v0.2.0...v0.2.1)
175
+
176
+ ## 0.2.0 2017-01-30
58
177
 
59
178
  This release is a major reorientation for dry-view, and it should allow for more natural, straightforward template authoring.
60
179
 
180
+ ### Added
181
+
182
+ - Will render templates using any Tilt-supported engine, based on the template's final file extension (e.g. `hello.html.slim` will use Slim). For thread-safety, be sure to explicitly require any engine gems you intend to use. (timriley)
183
+ - `expose` (and `expose_private`) `Dry::View::Controller` class methods allow you to more easily declare and prepare the data for your template (timriley)
184
+ - Added `Dry::View::Scope`, which is the scope used for rendering templates. This includes the data from the exposures along with the context object (timriley)
185
+
61
186
  ### Changed
62
187
 
63
188
  - [BREAKING] `Dry::View::Layout` renamed to `Dry::View::Controller`. The "view controller" name better represents this object's job: to (timriley)
@@ -69,13 +194,10 @@ This release is a major reorientation for dry-view, and it should allow for more
69
194
  - [BREAKING] With view parts removed, partials can only be rendered by top-level method calls within templates (timriley)
70
195
  - Ruby version 2.1.0 is now the earliest supported version (timriley)
71
196
 
72
- ### Added
197
+ [Compare v0.1.1...v0.2.0](https://github.com/dry-rb/dry-view/compare/v0.1.1...v0.2.0)
73
198
 
74
- - Will render templates using any Tilt-supported engine, based on the template's final file extension (e.g. `hello.html.slim` will use Slim). For thread-safety, be sure to explicitly require any engine gems you intend to use. (timriley)
75
- - `expose` (and `expose_private`) `Dry::View::Controller` class methods allow you to more easily declare and prepare the data for your template (timriley)
76
- - Added `Dry::View::Scope`, which is the scope used for rendering templates. This includes the data from the exposures along with the context object (timriley)
199
+ ## 0.1.1 2016-07-07
77
200
 
78
- # 0.1.1 / 2016-07-07
79
201
 
80
202
  ### Changed
81
203
 
@@ -83,12 +205,15 @@ This release is a major reorientation for dry-view, and it should allow for more
83
205
  - Render template content first, before passing that content to the layout. This makes "content_for"-style behaviours possible, where the template stores some data that the layout can then use later (timriley)
84
206
  - Configure default template encoding to be UTF-8, fixing some issues with template rendering on deployed sites (gotar)
85
207
 
86
- # 0.1.0 / 2016-03-28
208
+ [Compare v0.1.0...v0.1.1](https://github.com/dry-rb/dry-view/compare/v0.1.0...v0.1.1)
209
+
210
+ ## 0.1.0 2016-03-28
211
+
87
212
 
88
213
  ### Added
89
214
 
90
- – `Dry::View::Layout` supports multiple view template formats. Configure format/engine pairs (e.g. `{html: :slim, text: :erb}`) on the `formats` setting. The first format becomes the default. Request specific formats when calling the view, e.g. `my_view.call(format: :text)`.
215
+ - – `Dry::View::Layout` supports multiple view template formats. Configure format/engine pairs (e.g. `{html: :slim, text: :erb}`) on the `formats` setting. The first format becomes the default. Request specific formats when calling the view, e.g. `my_view.call(format: :text)`.
91
216
 
92
217
  ### Changed
93
218
 
94
- – Extracted from rodakase and renamed to dry-view. `Rodakase::View` is now `Dry::View`.
219
+ - – Extracted from rodakase and renamed to dry-view. `Rodakase::View` is now `Dry::View`.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-2021 dry-rb team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,21 +1,29 @@
1
- [gitter]: https://gitter.im/dry-rb/chat
2
1
  [gem]: https://rubygems.org/gems/dry-view
3
- [travis]: https://travis-ci.org/dry-rb/dry-view
4
- [inch]: http://inch-ci.org/github/dry-rb/dry-view
2
+ [actions]: https://github.com/dry-rb/dry-view/actions
3
+ [codacy]: https://www.codacy.com/gh/dry-rb/dry-view
4
+ [chat]: https://dry-rb.zulipchat.com
5
+ [inchpages]: http://inch-ci.org/github/dry-rb/dry-view
5
6
 
6
- # dry-view [![Join the Gitter chat](https://badges.gitter.im/Join%20Chat.svg)][gitter]
7
+ # dry-view [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
7
8
 
8
- [![Gem Version](https://img.shields.io/gem/v/dry-view.svg)][gem]
9
- [![Build Status](https://img.shields.io/travis/dry-rb/dry-view.svg)][travis]
10
- [![Maintainability](https://api.codeclimate.com/v1/badges/de81a8026a2e7f64e4df/maintainability)](https://codeclimate.com/github/dry-rb/dry-view/maintainability)
11
- [![Test Coverage](https://api.codeclimate.com/v1/badges/de81a8026a2e7f64e4df/test_coverage)](https://codeclimate.com/github/dry-rb/dry-view/test_coverage)
12
- [![API Documentation Coverage](http://inch-ci.org/github/dry-rb/dry-view.svg)][inch]
9
+ [![Gem Version](https://badge.fury.io/rb/dry-view.svg)][gem]
10
+ [![CI Status](https://github.com/dry-rb/dry-view/workflows/ci/badge.svg)][actions]
11
+ [![Codacy Badge](https://api.codacy.com/project/badge/Grade/fe8a45d76d8b45f6a680a29c48b43a99)][codacy]
12
+ [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/fe8a45d76d8b45f6a680a29c48b43a99)][codacy]
13
+ [![Inline docs](http://inch-ci.org/github/dry-rb/dry-view.svg?branch=master)][inchpages]
13
14
 
15
+ ## Links
14
16
 
15
- A simple, standalone view rendering system built around functional view
16
- controllers and templates. dry-view allows you to model your views as stateless
17
- transformations, accepting user input and returning your rendered view.
17
+ * [User documentation](http://dry-rb.org/gems/dry-view)
18
+ * [API documentation](http://rubydoc.info/gems/dry-view)
18
19
 
19
- ## Links
20
+ ## Supported Ruby versions
21
+
22
+ This library officially supports the following Ruby versions:
23
+
24
+ * MRI >= `2.5`
25
+ * jruby >= `9.2`
26
+
27
+ ## License
20
28
 
21
- * [Documentation](http://dry-rb.org/gems/dry-view)
29
+ See `LICENSE` file.
data/dry-view.gemspec CHANGED
@@ -1,32 +1,40 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+ # this file is managed by dry-rb/devtools project
3
+
4
+ lib = File.expand_path('lib', __dir__)
3
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
6
  require 'dry/view/version'
5
7
 
6
8
  Gem::Specification.new do |spec|
7
- spec.name = "dry-view"
8
- spec.version = Dry::View::VERSION
9
- spec.authors = ["Piotr Solnica", "Tim Riley"]
10
- spec.email = ["piotr.solnica@gmail.com", "tim@icelab.com.au"]
11
- spec.summary = "Functional view rendering system"
9
+ spec.name = 'dry-view'
10
+ spec.authors = ["Tim Riley", "Piotr Solnica"]
11
+ spec.email = ["tim@icelab.com.au", "piotr.solnica@gmail.com"]
12
+ spec.license = 'MIT'
13
+ spec.version = Dry::View::VERSION.dup
14
+
15
+ spec.summary = "A complete, standalone view rendering system that gives you everything you need to write well-factored view code"
12
16
  spec.description = spec.summary
13
- spec.homepage = "https://github.com/dry-rb/dry-view"
14
- spec.license = "MIT"
17
+ spec.homepage = 'https://dry-rb.org/gems/dry-view'
18
+ spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-view.gemspec", "lib/**/*"]
19
+ spec.bindir = 'bin'
20
+ spec.executables = []
21
+ spec.require_paths = ['lib']
15
22
 
16
- spec.files = `git ls-files -z`.split("\x0")
17
- spec.bindir = "exe"
18
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
- spec.require_paths = ["lib"]
23
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
24
+ spec.metadata['changelog_uri'] = 'https://github.com/dry-rb/dry-view/blob/master/CHANGELOG.md'
25
+ spec.metadata['source_code_uri'] = 'https://github.com/dry-rb/dry-view'
26
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/dry-rb/dry-view/issues'
21
27
 
22
- spec.required_ruby_version = '>= 2.2.0'
28
+ spec.required_ruby_version = ">= 2.5.0"
23
29
 
24
- spec.add_runtime_dependency "tilt", "~> 2.0"
25
- spec.add_runtime_dependency "dry-core", "~> 0.2"
30
+ # to update dependencies edit project.yml
31
+ spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
26
32
  spec.add_runtime_dependency "dry-configurable", "~> 0.1"
27
- spec.add_runtime_dependency "dry-equalizer", "~> 0.2"
33
+ spec.add_runtime_dependency "dry-core", "~> 0.5", ">= 0.5"
34
+ spec.add_runtime_dependency "dry-inflector", "~> 0.1"
35
+ spec.add_runtime_dependency "tilt", "~> 2.0", ">= 2.0.6"
28
36
 
29
- spec.add_development_dependency "bundler", "~> 1.7"
30
- spec.add_development_dependency "rake", "~> 10.0"
31
- spec.add_development_dependency "rspec", "~> 3.1"
37
+ spec.add_development_dependency "bundler"
38
+ spec.add_development_dependency "rake"
39
+ spec.add_development_dependency "rspec"
32
40
  end
data/lib/dry-view.rb CHANGED
@@ -1 +1,3 @@
1
- require 'dry/view'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/view"
data/lib/dry/view.rb CHANGED
@@ -1,2 +1,514 @@
1
- require 'dry/view/renderer'
2
- require 'dry/view/controller'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+ require "dry/core/cache"
5
+ require "dry/core/equalizer"
6
+ require "dry/inflector"
7
+
8
+ require_relative "view/context"
9
+ require_relative "view/exposures"
10
+ require_relative "view/errors"
11
+ require_relative "view/part_builder"
12
+ require_relative "view/path"
13
+ require_relative "view/render_environment"
14
+ require_relative "view/rendered"
15
+ require_relative "view/renderer"
16
+ require_relative "view/scope_builder"
17
+
18
+ # A collection of next-generation Ruby libraries, helping you to write clear,
19
+ # flexible, and more maintainable Ruby code. Each dry-rb gem fulfils a common
20
+ # task, and together they make a powerful platform for any kind of Ruby
21
+ # application.
22
+ module Dry
23
+ # A standalone, template-based view rendering system that offers everything
24
+ # you need to write well-factored view code.
25
+ #
26
+ # This represents a single view, holding the configuration and exposures
27
+ # necessary for rendering its template.
28
+ #
29
+ # @abstract Subclass this and provide your own configuration and exposures to
30
+ # define your own view (along with a custom `#initialize` if you wish to
31
+ # inject dependencies into your subclass)
32
+ #
33
+ # @see https://dry-rb.org/gems/dry-view/
34
+ #
35
+ # @api public
36
+ class View
37
+ # @api private
38
+ DEFAULT_RENDERER_OPTIONS = {default_encoding: "utf-8"}.freeze
39
+
40
+ include Dry::Equalizer(:config, :exposures)
41
+
42
+ extend Dry::Core::Cache
43
+
44
+ extend Dry::Configurable
45
+
46
+ # @!group Configuration
47
+
48
+ # @overload config.paths=(paths)
49
+ # Set an array of directories that will be searched for all templates
50
+ # (templates, partials, and layouts).
51
+ #
52
+ # These will be converted into Path objects and used for template lookup
53
+ # when rendering.
54
+ #
55
+ # This is a **required setting**.
56
+ #
57
+ # @param paths [String, Path, Array<String, Path>] the paths
58
+ #
59
+ # @api public
60
+ # @!scope class
61
+ setting :paths do |paths|
62
+ Array(paths).map { |path| Path[path] }
63
+ end
64
+
65
+ # @overload config.template=(name)
66
+ # Set the name of the template for rendering this view. Template name
67
+ # should be relative to the configured `paths`.
68
+ #
69
+ # This is a **required setting**.
70
+ #
71
+ # @param name [String] template name
72
+ # @api public
73
+ # @!scope class
74
+ setting :template
75
+
76
+ # @overload config.layout=(name)
77
+ # Set the name of the layout to render templates within. Layouts will be
78
+ # looked up within the configured `layouts_dir`, within the configured
79
+ # `paths`.
80
+ #
81
+ # A false or nil value will use no layout. Defaults to `nil`.
82
+ #
83
+ # @param name [String, FalseClass, nil] layout name, or false to indicate no layout
84
+ # @api public
85
+ # @!scope class
86
+ setting :layout, false
87
+
88
+ # @overload config.layouts_dir=(dir)
89
+ # Set the name of the directory (within the configured `paths`) holding
90
+ # the layouts. Defaults to `"layouts"`
91
+ #
92
+ # @param dir [String] directory name
93
+ # @api public
94
+ # @!scope class
95
+ setting :layouts_dir, "layouts"
96
+
97
+ # @overload config.scope=(scope_class)
98
+ # Set the scope class to use when rendering the view's template.
99
+ #
100
+ # Configuring a custom scope class allows you to provide extra behaviour
101
+ # (alongside exposures) to the template.
102
+ #
103
+ # @see https://dry-rb.org/gems/dry-view/scopes/
104
+ #
105
+ # @param scope_class [Class] scope class (inheriting from `Dry::View::Scope`)
106
+ # @api public
107
+ # @!scope class
108
+ setting :scope
109
+
110
+ # @overload config.default_context=(context)
111
+ # Set the default context object to use when rendering. This will be used
112
+ # unless another context object is applied at render-time to `View#call`
113
+ #
114
+ # Defaults to a frozen instance of `Dry::View::Context`.
115
+ #
116
+ # @see View#call
117
+ #
118
+ # @param context [Dry::View::Context] context object
119
+ # @api public
120
+ # @!scope class
121
+ setting :default_context, Context.new.freeze
122
+
123
+ # @overload config.default_format=(format)
124
+ # Set the default format to use when rendering.
125
+ #
126
+ # Defaults to `:html`.
127
+ #
128
+ # @param format [Symbol]
129
+ # @api public
130
+ # @!scope class
131
+ setting :default_format, :html
132
+
133
+ # @overload config.scope_namespace=(namespace)
134
+ # Set a namespace that will be searched when building scope classes.
135
+ #
136
+ # @param namespace [Module, Class]
137
+ #
138
+ # @see Scope
139
+ #
140
+ # @api public
141
+ # @!scope class
142
+ setting :part_namespace
143
+
144
+ # @overload config.part_builder=(part_builder)
145
+ # Set a custom part builder class
146
+ #
147
+ # @see https://dry-rb.org/gems/dry-view/parts/
148
+ #
149
+ # @param part_builder [Class]
150
+ # @api public
151
+ # @!scope class
152
+ setting :part_builder, PartBuilder
153
+
154
+ # @overload config.scope_namespace=(namespace)
155
+ # Set a namespace that will be searched when building scope classes.
156
+ #
157
+ # @param namespace [Module, Class]
158
+ #
159
+ # @see Scope
160
+ #
161
+ # @api public
162
+ # @!scope class
163
+ setting :scope_namespace
164
+
165
+ # @overload config.scope_builder=(scope_builder)
166
+ # Set a custom scope builder class
167
+ #
168
+ # @see https://dry-rb.org/gems/dry-view/scopes/
169
+ #
170
+ # @param scope_builder [Class]
171
+ # @api public
172
+ # @!scope class
173
+ setting :scope_builder, ScopeBuilder
174
+
175
+ # @overload config.inflector=(inflector)
176
+ # Set an inflector to provide to the part_builder and scope_builder.
177
+ #
178
+ # Defaults to `Dry::Inflector.new`.
179
+ #
180
+ # @param inflector
181
+ # @api public
182
+ # @!scope class
183
+ setting :inflector, Dry::Inflector.new
184
+
185
+ # @overload config.renderer_options=(options)
186
+ # A hash of options to pass to the template engine. Template engines are
187
+ # provided by Tilt; see Tilt's documentation for what options your
188
+ # template engine may support.
189
+ #
190
+ # Defaults to `{default_encoding: "utf-8"}`. Any options passed will be
191
+ # merged onto the defaults.
192
+ #
193
+ # @see https://github.com/rtomayko/tilt
194
+ #
195
+ # @param options [Hash] renderer options
196
+ # @api public
197
+ # @!scope class
198
+ setting :renderer_options, DEFAULT_RENDERER_OPTIONS do |options|
199
+ DEFAULT_RENDERER_OPTIONS.merge(options.to_h).freeze
200
+ end
201
+
202
+ # @overload config.renderer_engine_mapping=(mapping)
203
+ # A hash specifying the (Tilt-compatible) template engine class to use
204
+ # for a given format. Template engine detection is automatic based on
205
+ # format; use this setting only if you want to force a non-preferred
206
+ # engine.
207
+ #
208
+ # @example
209
+ # config.renderer_engine_mapping = {erb: Tilt::ErubiTemplate}
210
+ #
211
+ # @see https://github.com/rtomayko/tilt
212
+ #
213
+ # @param mapping [Hash<Symbol, Class>] engine mapping
214
+ # @api public
215
+ # @!scope class
216
+ setting :renderer_engine_mapping
217
+
218
+ # @!endgroup
219
+
220
+ # @api private
221
+ def self.inherited(klass)
222
+ super
223
+ exposures.each do |name, exposure|
224
+ klass.exposures.import(name, exposure)
225
+ end
226
+ end
227
+
228
+ # @!group Exposures
229
+
230
+ # @!macro [new] exposure_options
231
+ # @param options [Hash] the exposure's options
232
+ # @option options [Boolean] :layout expose this value to the layout (defaults to false)
233
+ # @option options [Boolean] :decorate decorate this value in a matching Part (defaults to
234
+ # true)
235
+ # @option options [Symbol, Class] :as an alternative name or class to use when finding a
236
+ # matching Part
237
+
238
+ # @overload expose(name, **options, &block)
239
+ # Define a value to be passed to the template. The return value of the
240
+ # block will be decorated by a matching Part and passed to the template.
241
+ #
242
+ # The block will be evaluated with the view instance as its `self`. The
243
+ # block's parameters will determine what it is given:
244
+ #
245
+ # - To receive other exposure values, provide positional parameters
246
+ # matching the exposure names. These exposures will already by decorated
247
+ # by their Parts.
248
+ # - To receive the view's input arguments (whatever is passed to
249
+ # `View#call`), provide matching keyword parameters. You can provide
250
+ # default values for these parameters to make the corresponding input
251
+ # keys optional
252
+ # - To receive the Context object, provide a `context:` keyword parameter
253
+ # - To receive the view's input arguments in their entirety, provide a
254
+ # keywords splat parameter (i.e. `**input`)
255
+ #
256
+ # @example Accessing input arguments
257
+ # expose :article do |slug:|
258
+ # article_repo.find_by_slug(slug)
259
+ # end
260
+ #
261
+ # @example Accessing other exposures
262
+ # expose :articles do
263
+ # article_repo.listing
264
+ # end
265
+ #
266
+ # expose :featured_articles do |articles|
267
+ # articles.select(&:featured?)
268
+ # end
269
+ #
270
+ # @param name [Symbol] name for the exposure
271
+ # @macro exposure_options
272
+ #
273
+ # @overload expose(name, **options)
274
+ # Define a value to be passed to the template, provided by an instance
275
+ # method matching the name. The method's return value will be decorated by
276
+ # a matching Part and passed to the template.
277
+ #
278
+ # The method's parameters will determine what it is given:
279
+ #
280
+ # - To receive other exposure values, provide positional parameters
281
+ # matching the exposure names. These exposures will already by decorated
282
+ # by their Parts.
283
+ # - To receive the view's input arguments (whatever is passed to
284
+ # `View#call`), provide matching keyword parameters. You can provide
285
+ # default values for these parameters to make the corresponding input
286
+ # keys optional
287
+ # - To receive the Context object, provide a `context:` keyword parameter
288
+ # - To receive the view's input arguments in their entirey, provide a
289
+ # keywords splat parameter (i.e. `**input`)
290
+ #
291
+ # @example Accessing input arguments
292
+ # expose :article
293
+ #
294
+ # def article(slug:)
295
+ # article_repo.find_by_slug(slug)
296
+ # end
297
+ #
298
+ # @example Accessing other exposures
299
+ # expose :articles
300
+ # expose :featured_articles
301
+ #
302
+ # def articles
303
+ # article_repo.listing
304
+ # end
305
+ #
306
+ # def featured_articles
307
+ # articles.select(&:featured?)
308
+ # end
309
+ #
310
+ # @param name [Symbol] name for the exposure
311
+ # @macro exposure_options
312
+ #
313
+ # @overload expose(name, **options)
314
+ # Define a single value to pass through from the input data (when there is
315
+ # no instance method matching the `name`). This value will be decorated by
316
+ # a matching Part and passed to the template.
317
+ #
318
+ # @param name [Symbol] name for the exposure
319
+ # @macro exposure_options
320
+ # @option options [Boolean] :default a default value to provide if there is no matching
321
+ # input data
322
+ #
323
+ # @overload expose(*names, **options)
324
+ # Define multiple values to pass through from the input data (when there
325
+ # is no instance methods matching their names). These values will be
326
+ # decorated by matching Parts and passed through to the template.
327
+ #
328
+ # The provided options will be applied to all the exposures.
329
+ #
330
+ # @param names [Symbol] names for the exposures
331
+ # @macro exposure_options
332
+ # @option options [Boolean] :default a default value to provide if there is no matching
333
+ # input data
334
+ #
335
+ # @see https://dry-rb.org/gems/dry-view/exposures/
336
+ #
337
+ # @api public
338
+ def self.expose(*names, **options, &block)
339
+ if names.length == 1
340
+ exposures.add(names.first, block, **options)
341
+ else
342
+ names.each do |name|
343
+ exposures.add(name, **options)
344
+ end
345
+ end
346
+ end
347
+
348
+ # @api public
349
+ def self.private_expose(*names, **options, &block)
350
+ expose(*names, **options, private: true, &block)
351
+ end
352
+
353
+ # Returns the defined exposures. These are unbound, since bound exposures
354
+ # are only created when initializing a View instance.
355
+ #
356
+ # @return [Exposures]
357
+ # @api private
358
+ def self.exposures
359
+ @exposures ||= Exposures.new
360
+ end
361
+
362
+ # @!endgroup
363
+
364
+ # @!group Render environment
365
+
366
+ # Returns a render environment for the view and the given options. This
367
+ # environment isn't chdir'ed into any particular directory.
368
+ #
369
+ # @param format [Symbol] template format to use (defaults to the `default_format` setting)
370
+ # @param context [Context] context object to use (defaults to the `default_context` setting)
371
+ #
372
+ # @see View.template_env render environment for the view's template
373
+ # @see View.layout_env render environment for the view's layout
374
+ #
375
+ # @return [RenderEnvironment]
376
+ # @api public
377
+ def self.render_env(format: config.default_format, context: config.default_context)
378
+ RenderEnvironment.prepare(renderer(format), config, context)
379
+ end
380
+
381
+ # @overload template_env(format: config.default_format, context: config.default_context)
382
+ # Returns a render environment for the view and the given options,
383
+ # chdir'ed into the view's template directory. This is the environment
384
+ # used when rendering the template, and is useful to to fetch
385
+ # independently when unit testing Parts and Scopes.
386
+ #
387
+ # @param format [Symbol] template format to use (defaults to the `default_format` setting)
388
+ # @param context [Context] context object to use (defaults to the `default_context` setting)
389
+ #
390
+ # @return [RenderEnvironment]
391
+ # @api public
392
+ def self.template_env(**args)
393
+ render_env(**args).chdir(config.template)
394
+ end
395
+
396
+ # @overload layout_env(format: config.default_format, context: config.default_context)
397
+ # Returns a render environment for the view and the given options,
398
+ # chdir'ed into the view's layout directory. This is the environment used
399
+ # when rendering the view's layout.
400
+ #
401
+ # @param format [Symbol] template format to use (defaults to the `default_format` setting)
402
+ # @param context [Context] context object to use (defaults to the `default_context` setting)
403
+ #
404
+ # @return [RenderEnvironment] @api public
405
+ def self.layout_env(**args)
406
+ render_env(**args).chdir(layout_path)
407
+ end
408
+
409
+ # Returns renderer for the view and provided format
410
+ #
411
+ # @api private
412
+ def self.renderer(format)
413
+ fetch_or_store(:renderer, config, format) {
414
+ Renderer.new(
415
+ config.paths,
416
+ format: format,
417
+ engine_mapping: config.renderer_engine_mapping,
418
+ **config.renderer_options
419
+ )
420
+ }
421
+ end
422
+
423
+ # @api private
424
+ def self.layout_path
425
+ File.join(config.layouts_dir, config.layout)
426
+ end
427
+
428
+ # @!endgroup
429
+
430
+ # The view's bound exposures
431
+ #
432
+ # @return [Exposures]
433
+ # @api private
434
+ attr_reader :exposures
435
+
436
+ # Returns an instance of the view. This binds the defined exposures to the
437
+ # view instance.
438
+ #
439
+ # Subclasses can define their own `#initialize` to accept injected
440
+ # dependencies, but must call `super()` to ensure the standard view
441
+ # initialization can proceed.
442
+ #
443
+ # @api public
444
+ def initialize
445
+ @exposures = self.class.exposures.bind(self)
446
+ end
447
+
448
+ # The view's configuration
449
+ #
450
+ # @api private
451
+ def config
452
+ self.class.config
453
+ end
454
+
455
+ # Render the view
456
+ #
457
+ # @param format [Symbol] template format to use
458
+ # @param context [Context] context object to use
459
+ # @param input input data for preparing exposure values
460
+ #
461
+ # @return [Rendered] rendered view object
462
+ # @api public
463
+ def call(format: config.default_format, context: config.default_context, **input)
464
+ ensure_config
465
+
466
+ env = self.class.render_env(format: format, context: context)
467
+ template_env = self.class.template_env(format: format, context: context)
468
+
469
+ locals = locals(template_env, input)
470
+ output = env.template(config.template, template_env.scope(config.scope, locals))
471
+
472
+ if layout?
473
+ layout_env = self.class.layout_env(format: format, context: context)
474
+ output = env.template(
475
+ self.class.layout_path,
476
+ layout_env.scope(config.scope, layout_locals(locals))
477
+ ) { output }
478
+ end
479
+
480
+ Rendered.new(output: output, locals: locals)
481
+ end
482
+
483
+ private
484
+
485
+ # @api private
486
+ def ensure_config
487
+ raise UndefinedConfigError, :paths unless Array(config.paths).any?
488
+ raise UndefinedConfigError, :template unless config.template
489
+ end
490
+
491
+ # @api private
492
+ def locals(render_env, input)
493
+ exposures.(context: render_env.context, **input) do |value, exposure|
494
+ if exposure.decorate? && value
495
+ render_env.part(exposure.name, value, **exposure.options)
496
+ else
497
+ value
498
+ end
499
+ end
500
+ end
501
+
502
+ # @api private
503
+ def layout_locals(locals)
504
+ locals.each_with_object({}) do |(key, value), layout_locals|
505
+ layout_locals[key] = value if exposures[key].for_layout?
506
+ end
507
+ end
508
+
509
+ # @api private
510
+ def layout?
511
+ !!config.layout # rubocop:disable Style/DoubleNegation
512
+ end
513
+ end
514
+ end