dry-view 0.5.1 → 0.7.1

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 (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