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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +143 -18
- data/LICENSE +20 -0
- data/README.md +22 -14
- data/dry-view.gemspec +29 -21
- data/lib/dry-view.rb +3 -1
- data/lib/dry/view.rb +514 -2
- data/lib/dry/view/context.rb +80 -0
- data/lib/dry/view/decorated_attributes.rb +82 -0
- data/lib/dry/view/errors.rb +29 -0
- data/lib/dry/view/exposure.rb +35 -14
- data/lib/dry/view/exposures.rb +18 -6
- data/lib/dry/view/part.rb +166 -53
- data/lib/dry/view/part_builder.rb +140 -0
- data/lib/dry/view/path.rb +35 -7
- data/lib/dry/view/render_environment.rb +62 -0
- data/lib/dry/view/render_environment_missing.rb +44 -0
- data/lib/dry/view/rendered.rb +55 -0
- data/lib/dry/view/renderer.rb +36 -29
- data/lib/dry/view/scope.rb +160 -14
- data/lib/dry/view/scope_builder.rb +98 -0
- data/lib/dry/view/tilt.rb +78 -0
- data/lib/dry/view/tilt/erb.rb +26 -0
- data/lib/dry/view/tilt/erbse.rb +21 -0
- data/lib/dry/view/tilt/haml.rb +26 -0
- data/lib/dry/view/version.rb +5 -2
- metadata +78 -115
- data/.gitignore +0 -26
- data/.rspec +0 -2
- data/.travis.yml +0 -23
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -22
- data/LICENSE.md +0 -10
- data/Rakefile +0 -6
- data/benchmarks/templates/button.html.erb +0 -1
- data/benchmarks/view.rb +0 -24
- data/bin/console +0 -7
- data/lib/dry/view/controller.rb +0 -155
- data/lib/dry/view/decorator.rb +0 -45
- data/lib/dry/view/missing_renderer.rb +0 -15
- data/spec/fixtures/templates/_hello.html.slim +0 -1
- data/spec/fixtures/templates/decorated_parts.html.slim +0 -4
- data/spec/fixtures/templates/edit.html.slim +0 -11
- data/spec/fixtures/templates/empty.html.slim +0 -1
- data/spec/fixtures/templates/greeting.html.slim +0 -2
- data/spec/fixtures/templates/hello.html.slim +0 -1
- data/spec/fixtures/templates/layouts/app.html.slim +0 -6
- data/spec/fixtures/templates/layouts/app.txt.erb +0 -3
- data/spec/fixtures/templates/parts_with_args.html.slim +0 -3
- data/spec/fixtures/templates/parts_with_args/_box.html.slim +0 -3
- data/spec/fixtures/templates/shared/_index_table.html.slim +0 -2
- data/spec/fixtures/templates/shared/_shared_hello.html.slim +0 -1
- data/spec/fixtures/templates/tasks.html.slim +0 -3
- data/spec/fixtures/templates/user.html.slim +0 -2
- data/spec/fixtures/templates/users.html.slim +0 -5
- data/spec/fixtures/templates/users.txt.erb +0 -3
- data/spec/fixtures/templates/users/_row.html.slim +0 -2
- data/spec/fixtures/templates/users/_tbody.html.slim +0 -5
- data/spec/fixtures/templates/users_with_count.html.slim +0 -5
- data/spec/fixtures/templates/users_with_count_inherit.html.slim +0 -6
- data/spec/fixtures/templates_override/_hello.html.slim +0 -1
- data/spec/fixtures/templates_override/users.html.slim +0 -5
- data/spec/integration/decorator_spec.rb +0 -80
- data/spec/integration/exposures_spec.rb +0 -392
- data/spec/integration/part/decorated_attributes_spec.rb +0 -157
- data/spec/integration/view_spec.rb +0 -133
- data/spec/spec_helper.rb +0 -46
- data/spec/unit/controller_spec.rb +0 -37
- data/spec/unit/decorator_spec.rb +0 -61
- data/spec/unit/exposure_spec.rb +0 -227
- data/spec/unit/exposures_spec.rb +0 -103
- data/spec/unit/part_spec.rb +0 -90
- data/spec/unit/renderer_spec.rb +0 -57
- 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
data/lib/dry/view/controller.rb
DELETED
@@ -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
|
data/lib/dry/view/decorator.rb
DELETED
@@ -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 +0,0 @@
|
|
1
|
-
p This is a view with no locals.
|
@@ -1 +0,0 @@
|
|
1
|
-
h1 Hello
|
@@ -1 +0,0 @@
|
|
1
|
-
h1 Hello
|
@@ -1 +0,0 @@
|
|
1
|
-
h1 Partial new hello
|
@@ -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
|