dry-view 0.5.3 → 0.6.0

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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +18 -0
  3. data/.travis.yml +15 -10
  4. data/.yardopts +5 -0
  5. data/CHANGELOG.md +60 -1
  6. data/Gemfile +15 -5
  7. data/README.md +38 -13
  8. data/bin/setup +5 -0
  9. data/bin/setup_helpers.rb +27 -0
  10. data/dry-view.gemspec +8 -9
  11. data/lib/dry-view.rb +3 -1
  12. data/lib/dry/view.rb +503 -2
  13. data/lib/dry/view/context.rb +80 -0
  14. data/lib/dry/view/decorated_attributes.rb +81 -0
  15. data/lib/dry/view/exposure.rb +15 -2
  16. data/lib/dry/view/exposures.rb +15 -5
  17. data/lib/dry/view/part.rb +154 -61
  18. data/lib/dry/view/part_builder.rb +136 -0
  19. data/lib/dry/view/path.rb +22 -5
  20. data/lib/dry/view/render_environment.rb +62 -0
  21. data/lib/dry/view/render_environment_missing.rb +44 -0
  22. data/lib/dry/view/rendered.rb +55 -0
  23. data/lib/dry/view/renderer.rb +22 -19
  24. data/lib/dry/view/scope.rb +146 -14
  25. data/lib/dry/view/scope_builder.rb +98 -0
  26. data/lib/dry/view/tilt.rb +78 -0
  27. data/lib/dry/view/tilt/erb.rb +26 -0
  28. data/lib/dry/view/tilt/erbse.rb +21 -0
  29. data/lib/dry/view/tilt/haml.rb +26 -0
  30. data/lib/dry/view/version.rb +5 -2
  31. metadata +50 -88
  32. data/benchmarks/templates/button.html.erb +0 -1
  33. data/benchmarks/view.rb +0 -24
  34. data/lib/dry/view/controller.rb +0 -159
  35. data/lib/dry/view/decorator.rb +0 -45
  36. data/lib/dry/view/missing_renderer.rb +0 -15
  37. data/spec/fixtures/templates/_hello.html.slim +0 -1
  38. data/spec/fixtures/templates/controller_renderer_options.html.erb +0 -3
  39. data/spec/fixtures/templates/decorated_parts.html.slim +0 -4
  40. data/spec/fixtures/templates/edit.html.slim +0 -11
  41. data/spec/fixtures/templates/empty.html.slim +0 -1
  42. data/spec/fixtures/templates/greeting.html.slim +0 -2
  43. data/spec/fixtures/templates/hello.html.slim +0 -1
  44. data/spec/fixtures/templates/layouts/app.html.slim +0 -6
  45. data/spec/fixtures/templates/layouts/app.txt.erb +0 -3
  46. data/spec/fixtures/templates/parts_with_args.html.slim +0 -3
  47. data/spec/fixtures/templates/parts_with_args/_box.html.slim +0 -3
  48. data/spec/fixtures/templates/shared/_index_table.html.slim +0 -2
  49. data/spec/fixtures/templates/shared/_shared_hello.html.slim +0 -1
  50. data/spec/fixtures/templates/tasks.html.slim +0 -3
  51. data/spec/fixtures/templates/user.html.slim +0 -2
  52. data/spec/fixtures/templates/users.html.slim +0 -5
  53. data/spec/fixtures/templates/users.txt.erb +0 -3
  54. data/spec/fixtures/templates/users/_row.html.slim +0 -2
  55. data/spec/fixtures/templates/users/_tbody.html.slim +0 -5
  56. data/spec/fixtures/templates/users_with_count.html.slim +0 -5
  57. data/spec/fixtures/templates/users_with_count_inherit.html.slim +0 -6
  58. data/spec/fixtures/templates_override/_hello.html.slim +0 -1
  59. data/spec/fixtures/templates_override/users.html.slim +0 -5
  60. data/spec/integration/decorator_spec.rb +0 -80
  61. data/spec/integration/exposures_spec.rb +0 -392
  62. data/spec/integration/part/decorated_attributes_spec.rb +0 -193
  63. data/spec/integration/view_spec.rb +0 -133
  64. data/spec/spec_helper.rb +0 -46
  65. data/spec/unit/controller_spec.rb +0 -83
  66. data/spec/unit/decorator_spec.rb +0 -61
  67. data/spec/unit/exposure_spec.rb +0 -227
  68. data/spec/unit/exposures_spec.rb +0 -103
  69. data/spec/unit/part_spec.rb +0 -104
  70. data/spec/unit/renderer_spec.rb +0 -57
  71. data/spec/unit/scope_spec.rb +0 -53
@@ -1 +0,0 @@
1
- <a href="/users/1">User</a>
data/benchmarks/view.rb DELETED
@@ -1,24 +0,0 @@
1
- require 'pathname'
2
- require 'benchmark/ips'
3
- require 'dry/view/renderer'
4
- require 'action_view'
5
-
6
- class ActionRender
7
- include ActionView::Helpers
8
-
9
- def button
10
- link_to('User', '/users/1')
11
- end
12
- end
13
-
14
- action_renderer = ActionRender.new
15
- dry_view_renderer = Dry::View::Renderer.new(Pathname(__FILE__).dirname.join('templates'), format: :html)
16
-
17
- template = Pathname(__FILE__).dirname.join('templates').join('button.html.erb')
18
- SCOPE = {}
19
-
20
- Benchmark.ips do |x|
21
- x.report('actionview') { action_renderer.button }
22
- x.report('dry-view') { dry_view_renderer.render(template, SCOPE) }
23
- x.compare!
24
- end
@@ -1,159 +0,0 @@
1
- require 'dry-configurable'
2
- require 'dry-equalizer'
3
-
4
- require 'dry/view/path'
5
- require 'dry/view/exposures'
6
- require 'dry/view/renderer'
7
- require 'dry/view/decorator'
8
- require 'dry/view/scope'
9
-
10
- module Dry
11
- module View
12
- class Controller
13
- UndefinedTemplateError = Class.new(StandardError)
14
-
15
- DEFAULT_LAYOUTS_DIR = 'layouts'.freeze
16
- DEFAULT_CONTEXT = Object.new.freeze
17
- DEFAULT_RENDERER_OPTIONS = {default_encoding: 'utf-8'.freeze}.freeze
18
- EMPTY_LOCALS = {}.freeze
19
-
20
- include Dry::Equalizer(:config)
21
-
22
- extend Dry::Configurable
23
-
24
- setting :paths
25
- setting :layout, false
26
- setting :template
27
- setting :default_format, :html
28
- setting :renderer_options, DEFAULT_RENDERER_OPTIONS do |options|
29
- DEFAULT_RENDERER_OPTIONS.merge(options.to_h).freeze
30
- end
31
- setting :context, DEFAULT_CONTEXT
32
- setting :decorator, Decorator.new
33
-
34
- attr_reader :config
35
- attr_reader :layout_dir
36
- attr_reader :layout_path
37
- attr_reader :template_path
38
- attr_reader :exposures
39
-
40
- # @api private
41
- def self.inherited(klass)
42
- super
43
- exposures.each do |name, exposure|
44
- klass.exposures.import(name, exposure)
45
- end
46
- end
47
-
48
- # @api public
49
- def self.paths
50
- Array(config.paths).map { |path| Dry::View::Path.new(path) }
51
- end
52
-
53
- # @api private
54
- def self.renderer(format)
55
- renderers.fetch(format) {
56
- renderers[format] = Renderer.new(paths, format: format, **config.renderer_options)
57
- }
58
- end
59
-
60
- # @api private
61
- def self.renderers
62
- @renderers ||= {}
63
- end
64
-
65
- # @api public
66
- def self.expose(*names, **options, &block)
67
- if names.length == 1
68
- exposures.add(names.first, block, options)
69
- else
70
- names.each do |name|
71
- exposures.add(name, options)
72
- end
73
- end
74
- end
75
-
76
- # @api public
77
- def self.private_expose(*names, **options, &block)
78
- expose(*names, **options, private: true, &block)
79
- end
80
-
81
- # @api private
82
- def self.exposures
83
- @exposures ||= Exposures.new
84
- end
85
-
86
- # @api public
87
- def initialize
88
- @config = self.class.config
89
- @layout_dir = DEFAULT_LAYOUTS_DIR
90
- @layout_path = "#{layout_dir}/#{config.layout}"
91
- @template_path = config.template
92
- @exposures = self.class.exposures.bind(self)
93
- end
94
-
95
- # @api public
96
- def call(format: config.default_format, context: config.context, **input)
97
- raise UndefinedTemplateError, "no +template+ configured" unless template_path
98
-
99
- renderer = self.class.renderer(format)
100
-
101
- template_content = renderer.template(template_path, template_scope(renderer, context, input))
102
-
103
- return template_content unless layout?
104
-
105
- renderer.template(layout_path, layout_scope(renderer, context)) do
106
- template_content
107
- end
108
- end
109
-
110
- # @api public
111
- def locals(locals: EMPTY_LOCALS, **input)
112
- exposures.locals(input).merge(locals)
113
- end
114
-
115
- private
116
-
117
- def layout?
118
- !!config.layout
119
- end
120
-
121
- def layout_scope(renderer, context)
122
- scope(renderer.chdir(layout_dir), context)
123
- end
124
-
125
- def template_scope(renderer, context, **input)
126
- scope(renderer.chdir(template_path), context, locals(input))
127
- end
128
-
129
- def scope(renderer, context, locals = EMPTY_LOCALS)
130
- Scope.new(
131
- renderer: renderer,
132
- context: context,
133
- locals: decorated_locals(renderer, context, locals)
134
- )
135
- end
136
-
137
- def decorated_locals(renderer, context, locals)
138
- decorator = self.class.config.decorator
139
-
140
- locals.each_with_object({}) { |(key, val), result|
141
- # Decorate truthy values only
142
- if val
143
- options = exposures.key?(key) ? exposures[key].options : {}
144
-
145
- val = decorator.(
146
- key,
147
- val,
148
- renderer: renderer,
149
- context: context,
150
- **options
151
- )
152
- end
153
-
154
- result[key] = val
155
- }
156
- end
157
- end
158
- end
159
- end
@@ -1,45 +0,0 @@
1
- require 'dry/core/inflector'
2
- require 'dry/view/part'
3
-
4
- module Dry
5
- module View
6
- class Decorator
7
- attr_reader :config
8
-
9
- # @api public
10
- def call(name, value, renderer:, context:, **options)
11
- klass = part_class(name, value, options)
12
-
13
- if value.respond_to?(:to_ary)
14
- singular_name = Dry::Core::Inflector.singularize(name).to_sym
15
- singular_options = singularize_options(options)
16
-
17
- arr = value.to_ary.map { |obj|
18
- call(singular_name, obj, renderer: renderer, context: context, **singular_options)
19
- }
20
-
21
- klass.new(name: name, value: arr, decorator: self, renderer: renderer, context: context)
22
- else
23
- klass.new(name: name, value: value, decorator: self, renderer: renderer, context: context)
24
- end
25
- end
26
-
27
- # @api public
28
- def part_class(name, value, **options)
29
- if options[:as].is_a?(Hash)
30
- options[:as].keys.first
31
- else
32
- options.fetch(:as) { Part }
33
- end
34
- end
35
-
36
- private
37
-
38
- # @api private
39
- def singularize_options(**options)
40
- options[:as] = options[:as].values.first if options[:as].is_a?(Hash)
41
- options
42
- end
43
- end
44
- end
45
- end
@@ -1,15 +0,0 @@
1
- module Dry
2
- module View
3
- class MissingRendererError < StandardError
4
- def initialize(message = "No renderer provided")
5
- super
6
- end
7
- end
8
-
9
- class MissingRenderer
10
- def method_missing(name, *args, &block)
11
- raise MissingRendererError
12
- end
13
- end
14
- end
15
- end
@@ -1 +0,0 @@
1
- h1 Partial hello
@@ -1,3 +0,0 @@
1
- <% form action: '/people' do |f| %>
2
- <%= f.text :name %>
3
- <% end %>
@@ -1,4 +0,0 @@
1
- - customs.each do |custom|
2
- p = custom
3
- p = custom
4
- p = ordinary
@@ -1,11 +0,0 @@
1
- h1 Edit
2
-
3
- - if errors.any?
4
- - errors.each do |error|
5
- p.error #{error}
6
- - else
7
- p
8
- | No Errors
9
-
10
- p
11
- = pretty_id
@@ -1 +0,0 @@
1
- p This is a view with no locals.
@@ -1,2 +0,0 @@
1
- p
2
- = greeting
@@ -1 +0,0 @@
1
- h1 Hello
@@ -1,6 +0,0 @@
1
- doctype html
2
- html
3
- head
4
- title == title
5
- body
6
- == yield
@@ -1,3 +0,0 @@
1
- # <%= title %>
2
-
3
- <%= yield %>
@@ -1,3 +0,0 @@
1
- .users
2
- - users.each do |user|
3
- == user.render :box, label: "Nombre"
@@ -1,3 +0,0 @@
1
- div.box
2
- h2 = label
3
- = user[:name]
@@ -1,2 +0,0 @@
1
- table
2
- == yield
@@ -1,3 +0,0 @@
1
- ol
2
- - tasks.each do |task|
3
- li == task[:title]
@@ -1,2 +0,0 @@
1
- h1 = header[:title]
2
- p = user[:name]
@@ -1,5 +0,0 @@
1
- .users
2
- == render :index_table do
3
- == render :tbody
4
-
5
- img src=assets["mindblown"]
@@ -1,3 +0,0 @@
1
- <% users.each do |user| %>
2
- * <%= user[:name] %> (<%= user[:email] %>)
3
- <% end %>
@@ -1,2 +0,0 @@
1
- tr
2
- == yield
@@ -1,5 +0,0 @@
1
- tbody
2
- - users.each do |user|
3
- == render :row do
4
- td = user[:name]
5
- td = user[:email]
@@ -1,5 +0,0 @@
1
- ul
2
- - users.each do |user|
3
- li = "#{user[:name]} (#{user[:email]})"
4
-
5
- .count = users_count
@@ -1,6 +0,0 @@
1
- ul
2
- - users.each do |user|
3
- li = "#{user[:name]} (#{user[:email]})"
4
-
5
- .count = users_count
6
- .inherit = child_expose
@@ -1 +0,0 @@
1
- h1 Partial new hello
@@ -1,5 +0,0 @@
1
- h1 OVERRIDE
2
-
3
- .users
4
- == render :index_table do
5
- == render :tbody
@@ -1,80 +0,0 @@
1
- RSpec.describe 'decorator' do
2
- before do
3
- module Test
4
- class CustomPart < Dry::View::Part
5
- def to_s
6
- "Custom part wrapping #{_value}"
7
- end
8
- end
9
-
10
- class CustomArrayPart < Dry::View::Part
11
- def each(&block)
12
- (_value * 2).each(&block)
13
- end
14
- end
15
- end
16
- end
17
-
18
- describe 'default decorator' do
19
- it 'supports wrapping array memebers in custom part classes provided to exposure :as option' do
20
- vc = Class.new(Dry::View::Controller) do
21
- configure do |config|
22
- config.paths = SPEC_ROOT.join('fixtures/templates')
23
- config.layout = nil
24
- config.template = 'decorated_parts'
25
- end
26
-
27
- expose :customs, as: Test::CustomPart
28
- expose :custom, as: Test::CustomPart
29
- expose :ordinary
30
- end.new
31
-
32
- expect(vc.(customs: ['many things'], custom: 'custom thing', ordinary: 'ordinary thing')).to eql(
33
- '<p>Custom part wrapping many things</p><p>Custom part wrapping custom thing</p><p>ordinary thing</p>'
34
- )
35
- end
36
-
37
- it 'supports wrapping an array and its members in custom part classes provided to exposure :as option as a hash' do
38
- vc = Class.new(Dry::View::Controller) do
39
- configure do |config|
40
- config.paths = SPEC_ROOT.join('fixtures/templates')
41
- config.layout = nil
42
- config.template = 'decorated_parts'
43
- end
44
-
45
- expose :customs, as: {Test::CustomArrayPart => Test::CustomPart}
46
- expose :custom, as: Test::CustomPart
47
- expose :ordinary
48
- end.new
49
-
50
- expect(vc.(customs: ['many things'], custom: 'custom thing', ordinary: 'ordinary thing')).to eql(
51
- '<p>Custom part wrapping many things</p><p>Custom part wrapping many things</p><p>Custom part wrapping custom thing</p><p>ordinary thing</p>'
52
- )
53
- end
54
- end
55
-
56
- describe 'custom decorator and part classes' do
57
- it 'supports wrapping in custom parts based on exposure names' do
58
- decorator = Class.new(Dry::View::Decorator) do
59
- def part_class(name, value, **options)
60
- name == :custom ? Test::CustomPart : super
61
- end
62
- end.new
63
-
64
- vc = Class.new(Dry::View::Controller) do
65
- configure do |config|
66
- config.decorator = decorator
67
- config.paths = SPEC_ROOT.join('fixtures/templates')
68
- config.layout = nil
69
- config.template = 'decorated_parts'
70
- end
71
-
72
- expose :customs, :custom, :ordinary
73
- end.new
74
-
75
- expect(vc.(customs: ['many things'], custom: 'custom thing', ordinary: 'ordinary thing')).to eql(
76
- '<p>Custom part wrapping many things</p><p>Custom part wrapping custom thing</p><p>ordinary thing</p>'
77
- )
78
- end
79
- end
80
- end