dry-view 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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