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
data/lib/dry/view/scope.rb
CHANGED
@@ -1,43 +1,189 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/equalizer"
|
4
|
+
require "dry/core/constants"
|
5
|
+
require_relative "render_environment_missing"
|
2
6
|
|
3
7
|
module Dry
|
4
|
-
|
8
|
+
class View
|
9
|
+
# Evaluation context for templates (including layouts and partials) and
|
10
|
+
# provides a place to encapsulate view-specific behaviour alongside a
|
11
|
+
# template and its locals.
|
12
|
+
#
|
13
|
+
# @abstract Subclass this and provide your own methods adding view-specific
|
14
|
+
# behavior. You should not override `#initialize`
|
15
|
+
#
|
16
|
+
# @see https://dry-rb.org/gems/dry-view/templates/
|
17
|
+
# @see https://dry-rb.org/gems/dry-view/scopes/
|
18
|
+
#
|
19
|
+
# @api public
|
5
20
|
class Scope
|
6
|
-
|
21
|
+
# @api private
|
22
|
+
CONVENIENCE_METHODS = %i[format context locals].freeze
|
23
|
+
|
24
|
+
include Dry::Equalizer(:_name, :_locals, :_render_env)
|
25
|
+
|
26
|
+
# The scope's name
|
27
|
+
#
|
28
|
+
# @return [Symbol]
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
attr_reader :_name
|
7
32
|
|
33
|
+
# The scope's locals
|
34
|
+
#
|
35
|
+
# @overload _locals
|
36
|
+
# Returns the locals
|
37
|
+
# @overload locals
|
38
|
+
# A convenience alias for `#_format.` Is available unless there is a
|
39
|
+
# local named `locals`
|
40
|
+
#
|
41
|
+
# @return [Hash[<Symbol, Object>]
|
42
|
+
#
|
43
|
+
# @api public
|
8
44
|
attr_reader :_locals
|
9
|
-
attr_reader :_context
|
10
|
-
attr_reader :_renderer
|
11
45
|
|
12
|
-
|
46
|
+
# The current render environment
|
47
|
+
#
|
48
|
+
# @return [RenderEnvironment] render environment
|
49
|
+
#
|
50
|
+
# @api private
|
51
|
+
attr_reader :_render_env
|
52
|
+
|
53
|
+
# Returns a new Scope instance
|
54
|
+
#
|
55
|
+
# @param name [Symbol, nil] scope name
|
56
|
+
# @param locals [Hash<Symbol, Object>] template locals
|
57
|
+
# @param render_env [RenderEnvironment] render environment
|
58
|
+
#
|
59
|
+
# @return [Scope]
|
60
|
+
#
|
61
|
+
# @api public
|
62
|
+
def initialize(
|
63
|
+
name: nil,
|
64
|
+
locals: Dry::Core::Constants::EMPTY_HASH,
|
65
|
+
render_env: RenderEnvironmentMissing.new
|
66
|
+
)
|
67
|
+
@_name = name
|
13
68
|
@_locals = locals
|
14
|
-
@
|
15
|
-
|
69
|
+
@_render_env = render_env
|
70
|
+
end
|
71
|
+
|
72
|
+
# @overload render(partial_name, **locals, &block)
|
73
|
+
# Renders a partial using the scope
|
74
|
+
#
|
75
|
+
# @param partial_name [Symbol, String] partial name
|
76
|
+
# @param locals [Hash<Symbol, Object>] partial locals
|
77
|
+
# @yieldreturn [String] string content to include where the partial calls `yield`
|
78
|
+
#
|
79
|
+
# @overload render(**locals, &block)
|
80
|
+
# Renders a partial (named after the scope's own name) using the scope
|
81
|
+
#
|
82
|
+
# @param locals[Hash<Symbol, Object>] partial locals
|
83
|
+
# @yieldreturn [String] string content to include where the partial calls `yield`
|
84
|
+
#
|
85
|
+
# @return [String] the rendered partial output
|
86
|
+
#
|
87
|
+
# @api public
|
88
|
+
def render(partial_name = nil, **locals, &block)
|
89
|
+
partial_name ||= _name
|
90
|
+
|
91
|
+
unless partial_name
|
92
|
+
raise ArgumentError, "+partial_name+ must be provided for unnamed scopes"
|
93
|
+
end
|
94
|
+
|
95
|
+
if partial_name.is_a?(Class)
|
96
|
+
partial_name = _inflector.underscore(_inflector.demodulize(partial_name.to_s))
|
97
|
+
end
|
98
|
+
|
99
|
+
_render_env.partial(partial_name, _render_scope(**locals), &block)
|
16
100
|
end
|
17
101
|
|
18
|
-
|
19
|
-
|
102
|
+
# Build a new scope using a scope class matching the provided name
|
103
|
+
#
|
104
|
+
# @param name [Symbol, Class] scope name (or class)
|
105
|
+
# @param locals [Hash<Symbol, Object>] scope locals
|
106
|
+
#
|
107
|
+
# @return [Scope]
|
108
|
+
#
|
109
|
+
# @api public
|
110
|
+
def scope(name = nil, **locals)
|
111
|
+
_render_env.scope(name, locals)
|
112
|
+
end
|
113
|
+
|
114
|
+
# The template format for the current render environment.
|
115
|
+
#
|
116
|
+
# @overload _format
|
117
|
+
# Returns the format.
|
118
|
+
# @overload format
|
119
|
+
# A convenience alias for `#_format.` Is available unless there is a
|
120
|
+
# local named `format`
|
121
|
+
#
|
122
|
+
# @return [Symbol] format
|
123
|
+
#
|
124
|
+
# @api public
|
125
|
+
def _format
|
126
|
+
_render_env.format
|
127
|
+
end
|
128
|
+
|
129
|
+
# The context object for the current render environment
|
130
|
+
#
|
131
|
+
# @overload _context
|
132
|
+
# Returns the context.
|
133
|
+
# @overload context
|
134
|
+
# A convenience alias for `#_context`. Is available unless there is a
|
135
|
+
# local named `context`.
|
136
|
+
#
|
137
|
+
# @return [Context] context
|
138
|
+
#
|
139
|
+
# @api public
|
140
|
+
def _context
|
141
|
+
_render_env.context
|
20
142
|
end
|
21
143
|
|
22
144
|
private
|
23
145
|
|
146
|
+
# Handles missing methods, according to the following rules:
|
147
|
+
#
|
148
|
+
# 1. If there is a local with a name matching the method, it returns the
|
149
|
+
# local.
|
150
|
+
# 2. If the `context` responds to the method, then it will be sent the
|
151
|
+
# method and all its arguments.
|
24
152
|
def method_missing(name, *args, &block)
|
25
153
|
if _locals.key?(name)
|
26
154
|
_locals[name]
|
27
155
|
elsif _context.respond_to?(name)
|
28
156
|
_context.public_send(name, *args, &block)
|
157
|
+
elsif CONVENIENCE_METHODS.include?(name)
|
158
|
+
__send__(:"_#{name}", *args, &block)
|
29
159
|
else
|
30
160
|
super
|
31
161
|
end
|
32
162
|
end
|
163
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
33
164
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
37
|
-
|
165
|
+
def respond_to_missing?(name, include_private = false)
|
166
|
+
_locals.key?(name) ||
|
167
|
+
_render_env.context.respond_to?(name) ||
|
168
|
+
CONVENIENCE_METHODS.include?(name) ||
|
169
|
+
super
|
170
|
+
end
|
171
|
+
|
172
|
+
def _render_scope(**locals)
|
173
|
+
if locals.none?
|
38
174
|
self
|
175
|
+
else
|
176
|
+
self.class.new(
|
177
|
+
# FIXME: what about `name`?
|
178
|
+
locals: locals,
|
179
|
+
render_env: _render_env
|
180
|
+
)
|
39
181
|
end
|
40
182
|
end
|
183
|
+
|
184
|
+
def _inflector
|
185
|
+
_render_env.inflector
|
186
|
+
end
|
41
187
|
end
|
42
188
|
end
|
43
189
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/cache"
|
4
|
+
require "dry/core/equalizer"
|
5
|
+
require_relative "scope"
|
6
|
+
|
7
|
+
module Dry
|
8
|
+
class View
|
9
|
+
# Builds scope objects via matching classes
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
class ScopeBuilder
|
13
|
+
extend Dry::Core::Cache
|
14
|
+
include Dry::Equalizer(:namespace)
|
15
|
+
|
16
|
+
# The view's configured `scope_namespace`
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
attr_reader :namespace
|
20
|
+
|
21
|
+
# @return [RenderEnvironment]
|
22
|
+
#
|
23
|
+
# @api private
|
24
|
+
attr_reader :render_env
|
25
|
+
|
26
|
+
# Returns a new instance of ScopeBuilder
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
def initialize(namespace: nil, render_env: nil)
|
30
|
+
@namespace = namespace
|
31
|
+
@render_env = render_env
|
32
|
+
end
|
33
|
+
|
34
|
+
# @api private
|
35
|
+
def for_render_env(render_env)
|
36
|
+
return self if render_env == self.render_env
|
37
|
+
|
38
|
+
self.class.new(namespace: namespace, render_env: render_env)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns a new scope using a class matching the name
|
42
|
+
#
|
43
|
+
# @param name [Symbol, Class] scope name
|
44
|
+
# @param locals [Hash<Symbol, Object>] locals hash
|
45
|
+
#
|
46
|
+
# @return [Dry::View::Scope]
|
47
|
+
#
|
48
|
+
# @api private
|
49
|
+
def call(name = nil, locals) # rubocop:disable Style/OptionalArguments
|
50
|
+
scope_class(name).new(
|
51
|
+
name: name,
|
52
|
+
locals: locals,
|
53
|
+
render_env: render_env
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
DEFAULT_SCOPE_CLASS = Scope
|
60
|
+
|
61
|
+
def scope_class(name = nil)
|
62
|
+
if name.nil?
|
63
|
+
DEFAULT_SCOPE_CLASS
|
64
|
+
elsif name.is_a?(Class)
|
65
|
+
name
|
66
|
+
else
|
67
|
+
fetch_or_store(namespace, name) do
|
68
|
+
resolve_scope_class(name: name)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def resolve_scope_class(name:)
|
74
|
+
name = inflector.camelize(name.to_s)
|
75
|
+
|
76
|
+
# Give autoloaders a chance to act
|
77
|
+
begin
|
78
|
+
klass = namespace.const_get(name)
|
79
|
+
rescue NameError # rubocop:disable Lint/HandleExceptions
|
80
|
+
end
|
81
|
+
|
82
|
+
if !klass && namespace.const_defined?(name, false)
|
83
|
+
klass = namespace.const_get(name)
|
84
|
+
end
|
85
|
+
|
86
|
+
if klass && klass < Scope
|
87
|
+
klass
|
88
|
+
else
|
89
|
+
DEFAULT_SCOPE_CLASS
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def inflector
|
94
|
+
render_env.inflector
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/cache"
|
4
|
+
require "tilt"
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
class View
|
8
|
+
# @api private
|
9
|
+
module Tilt
|
10
|
+
extend Dry::Core::Cache
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def [](path, mapping, **options)
|
14
|
+
ext = File.extname(path).sub(/^./, "").to_sym
|
15
|
+
activate_adapter ext
|
16
|
+
|
17
|
+
with_mapping(mapping).new(path, **options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def default_mapping
|
21
|
+
::Tilt.default_mapping
|
22
|
+
end
|
23
|
+
|
24
|
+
def register_adapter(ext, adapter)
|
25
|
+
adapters[ext] = adapter
|
26
|
+
end
|
27
|
+
|
28
|
+
def deregister_adapter(ext)
|
29
|
+
adapters.delete(ext)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def adapters
|
35
|
+
@adapters ||= {}
|
36
|
+
end
|
37
|
+
|
38
|
+
def activate_adapter(ext)
|
39
|
+
fetch_or_store(:adapter, ext) {
|
40
|
+
adapter = adapters[ext]
|
41
|
+
return unless adapter
|
42
|
+
|
43
|
+
*requires, error_message = adapter.requirements
|
44
|
+
|
45
|
+
begin
|
46
|
+
requires.each(&method(:require))
|
47
|
+
rescue LoadError => e
|
48
|
+
raise e, "#{e.message}\n\n#{error_message}"
|
49
|
+
end
|
50
|
+
|
51
|
+
adapter.activate
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def with_mapping(mapping)
|
56
|
+
fetch_or_store(:mapping, mapping) {
|
57
|
+
if mapping.any?
|
58
|
+
build_mapping(mapping)
|
59
|
+
else
|
60
|
+
default_mapping
|
61
|
+
end
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def build_mapping(mapping)
|
66
|
+
default_mapping.dup.tap do |new_mapping|
|
67
|
+
mapping.each do |extension, template_class|
|
68
|
+
new_mapping.register template_class, extension
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
require_relative "tilt/erb"
|
78
|
+
require_relative "tilt/haml"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
class View
|
5
|
+
module Tilt
|
6
|
+
module ERB
|
7
|
+
def self.requirements
|
8
|
+
["dry/view/tilt/erbse", <<~ERROR]
|
9
|
+
dry-view requires erbse for full compatibility when rendering .erb templates (e.g. implicitly capturing block content when yielding)
|
10
|
+
|
11
|
+
To ignore this and use another engine for .erb templates, deregister this adapter before calling your views:
|
12
|
+
|
13
|
+
Dry::View::Tilt.deregister_adapter(:erb)
|
14
|
+
ERROR
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.activate
|
18
|
+
Tilt.default_mapping.register ErbseTemplate, "erb"
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
register_adapter :erb, ERB
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tilt/template"
|
4
|
+
require "erbse"
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
class View
|
8
|
+
module Tilt
|
9
|
+
# Tilt template class copied from cells-erb gem
|
10
|
+
class ErbseTemplate < ::Tilt::Template
|
11
|
+
def prepare
|
12
|
+
@template = ::Erbse::Engine.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def precompiled_template(_locals)
|
16
|
+
@template.call(data)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
class View
|
5
|
+
module Tilt
|
6
|
+
module Haml
|
7
|
+
def self.requirements
|
8
|
+
["hamlit/block", <<~ERROR]
|
9
|
+
dry-view requires hamlit-block for full compatibility when rendering .haml templates (e.g. implicitly capturing block content when yielding)
|
10
|
+
|
11
|
+
To ignore this and use another engine for .haml templates, dereigster this adapter before calling your views:
|
12
|
+
|
13
|
+
Dry::View::Tilt.deregister_adatper(:haml)
|
14
|
+
ERROR
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.activate
|
18
|
+
# Requiring hamlit/block will register the engine with Tilt
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
register_adapter :haml, Haml
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|