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
@@ -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
data/bin/console DELETED
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "dry-view"
5
-
6
- require "pry"
7
- Pry.start
@@ -1,155 +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
- EMPTY_LOCALS = {}.freeze
18
-
19
- include Dry::Equalizer(:config)
20
-
21
- extend Dry::Configurable
22
-
23
- setting :paths
24
- setting :layout, false
25
- setting :template
26
- setting :default_format, :html
27
- setting :context, DEFAULT_CONTEXT
28
- setting :decorator, Decorator.new
29
-
30
- attr_reader :config
31
- attr_reader :layout_dir
32
- attr_reader :layout_path
33
- attr_reader :template_path
34
- attr_reader :exposures
35
-
36
- # @api private
37
- def self.inherited(klass)
38
- super
39
- exposures.each do |name, exposure|
40
- klass.exposures.import(name, exposure)
41
- end
42
- end
43
-
44
- # @api public
45
- def self.paths
46
- Array(config.paths).map { |path| Dry::View::Path.new(path) }
47
- end
48
-
49
- # @api private
50
- def self.renderer(format)
51
- renderers.fetch(format) {
52
- renderers[format] = Renderer.new(paths, format: format)
53
- }
54
- end
55
-
56
- # @api private
57
- def self.renderers
58
- @renderers ||= {}
59
- end
60
-
61
- # @api public
62
- def self.expose(*names, **options, &block)
63
- if names.length == 1
64
- exposures.add(names.first, block, options)
65
- else
66
- names.each do |name|
67
- exposures.add(name, options)
68
- end
69
- end
70
- end
71
-
72
- # @api public
73
- def self.private_expose(*names, **options, &block)
74
- expose(*names, **options, private: true, &block)
75
- end
76
-
77
- # @api private
78
- def self.exposures
79
- @exposures ||= Exposures.new
80
- end
81
-
82
- # @api public
83
- def initialize
84
- @config = self.class.config
85
- @layout_dir = DEFAULT_LAYOUTS_DIR
86
- @layout_path = "#{layout_dir}/#{config.layout}"
87
- @template_path = config.template
88
- @exposures = self.class.exposures.bind(self)
89
- end
90
-
91
- # @api public
92
- def call(format: config.default_format, context: config.context, **input)
93
- raise UndefinedTemplateError, "no +template+ configured" unless template_path
94
-
95
- renderer = self.class.renderer(format)
96
-
97
- template_content = renderer.template(template_path, template_scope(renderer, context, input))
98
-
99
- return template_content unless layout?
100
-
101
- renderer.template(layout_path, layout_scope(renderer, context)) do
102
- template_content
103
- end
104
- end
105
-
106
- # @api public
107
- def locals(locals: EMPTY_LOCALS, **input)
108
- exposures.locals(input).merge(locals)
109
- end
110
-
111
- private
112
-
113
- def layout?
114
- !!config.layout
115
- end
116
-
117
- def layout_scope(renderer, context)
118
- scope(renderer.chdir(layout_dir), context)
119
- end
120
-
121
- def template_scope(renderer, context, **input)
122
- scope(renderer.chdir(template_path), context, locals(input))
123
- end
124
-
125
- def scope(renderer, context, locals = EMPTY_LOCALS)
126
- Scope.new(
127
- renderer: renderer,
128
- context: context,
129
- locals: decorated_locals(renderer, context, locals)
130
- )
131
- end
132
-
133
- def decorated_locals(renderer, context, locals)
134
- decorator = self.class.config.decorator
135
-
136
- locals.each_with_object({}) { |(key, val), result|
137
- # Decorate truthy values only
138
- if val
139
- options = exposures.key?(key) ? exposures[key].options : {}
140
-
141
- val = decorator.(
142
- key,
143
- val,
144
- renderer: renderer,
145
- context: context,
146
- **options
147
- )
148
- end
149
-
150
- result[key] = val
151
- }
152
- end
153
- end
154
- end
155
- 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,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