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