web-console 3.5.1 → 4.2.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 +5 -5
- data/CHANGELOG.markdown +91 -8
- data/MIT-LICENSE +1 -1
- data/README.markdown +41 -38
- data/Rakefile +14 -12
- data/lib/web-console.rb +3 -1
- data/lib/web_console/context.rb +8 -6
- data/lib/web_console/errors.rb +2 -0
- data/lib/web_console/evaluator.rb +14 -5
- data/lib/web_console/exception_mapper.rb +33 -10
- data/lib/web_console/extensions.rb +12 -23
- data/lib/web_console/injector.rb +32 -0
- data/lib/web_console/interceptor.rb +17 -0
- data/lib/web_console/middleware.rb +21 -24
- data/lib/web_console/permissions.rb +42 -0
- data/lib/web_console/railtie.rb +36 -19
- data/lib/web_console/request.rb +8 -20
- data/lib/web_console/session.rb +13 -9
- data/lib/web_console/source_location.rb +17 -0
- data/lib/web_console/tasks/extensions.rake +15 -13
- data/lib/web_console/tasks/templates.rake +9 -13
- data/lib/web_console/template.rb +4 -3
- data/lib/web_console/templates/console.js.erb +140 -38
- data/lib/web_console/templates/error_page.js.erb +7 -8
- data/lib/web_console/templates/index.html.erb +4 -0
- data/lib/web_console/templates/layouts/inlined_string.erb +1 -1
- data/lib/web_console/templates/layouts/javascript.erb +1 -1
- data/lib/web_console/templates/regular_page.js.erb +24 -0
- data/lib/web_console/templates/style.css.erb +182 -33
- data/lib/web_console/testing/erb_precompiler.rb +5 -3
- data/lib/web_console/testing/fake_middleware.rb +14 -9
- data/lib/web_console/testing/helper.rb +3 -1
- data/lib/web_console/version.rb +3 -1
- data/lib/web_console/view.rb +11 -3
- data/lib/web_console/whiny_request.rb +7 -5
- data/lib/web_console.rb +17 -8
- metadata +17 -15
- data/lib/web_console/response.rb +0 -23
- data/lib/web_console/whitelist.rb +0 -44
@@ -1,14 +1,13 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/string/strip"
|
2
4
|
|
3
5
|
module WebConsole
|
4
6
|
class Middleware
|
5
|
-
TEMPLATES_PATH = File.expand_path(
|
6
|
-
|
7
|
-
cattr_accessor :mount_point
|
8
|
-
@@mount_point = '/__web_console'
|
7
|
+
TEMPLATES_PATH = File.expand_path("../templates", __FILE__)
|
9
8
|
|
10
|
-
cattr_accessor :
|
11
|
-
|
9
|
+
cattr_accessor :mount_point, default: "/__web_console"
|
10
|
+
cattr_accessor :whiny_requests, default: true
|
12
11
|
|
13
12
|
def initialize(app)
|
14
13
|
@app = app
|
@@ -17,7 +16,7 @@ module WebConsole
|
|
17
16
|
def call(env)
|
18
17
|
app_exception = catch :app_exception do
|
19
18
|
request = create_regular_or_whiny_request(env)
|
20
|
-
return call_app(env) unless request.
|
19
|
+
return call_app(env) unless request.permitted?
|
21
20
|
|
22
21
|
if id = id_for_repl_session_update(request)
|
23
22
|
return update_repl_session(id, request)
|
@@ -25,19 +24,18 @@ module WebConsole
|
|
25
24
|
return change_stack_trace(id, request)
|
26
25
|
end
|
27
26
|
|
27
|
+
|
28
28
|
status, headers, body = call_app(env)
|
29
29
|
|
30
|
-
if session = Session.from(Thread.current)
|
31
|
-
|
32
|
-
|
30
|
+
if (session = Session.from(Thread.current)) && acceptable_content_type?(headers)
|
31
|
+
headers["x-web-console-session-id"] = session.id
|
32
|
+
headers["x-web-console-mount-point"] = mount_point
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
response.write(template.render('index'))
|
37
|
-
response.finish
|
38
|
-
else
|
39
|
-
[ status, headers, body ]
|
34
|
+
template = Template.new(env, session)
|
35
|
+
body, headers = Injector.new(body, headers).inject(template.render("index"))
|
40
36
|
end
|
37
|
+
|
38
|
+
[ status, headers, body ]
|
41
39
|
end
|
42
40
|
rescue => e
|
43
41
|
WebConsole.logger.error("\n#{e.class}: #{e}\n\tfrom #{e.backtrace.join("\n\tfrom ")}")
|
@@ -54,19 +52,18 @@ module WebConsole
|
|
54
52
|
private
|
55
53
|
|
56
54
|
def acceptable_content_type?(headers)
|
57
|
-
|
55
|
+
headers[Rack::CONTENT_TYPE].to_s.include?("html")
|
58
56
|
end
|
59
57
|
|
60
58
|
def json_response(opts = {})
|
61
59
|
status = opts.fetch(:status, 200)
|
62
|
-
headers = {
|
60
|
+
headers = { Rack::CONTENT_TYPE => "application/json; charset = utf-8" }
|
63
61
|
body = yield.to_json
|
64
62
|
|
65
|
-
|
63
|
+
[ status, headers, [ body ] ]
|
66
64
|
end
|
67
65
|
|
68
66
|
def json_response_with_session(id, request, opts = {})
|
69
|
-
return respond_with_unacceptable_request unless request.acceptable?
|
70
67
|
return respond_with_unavailable_session(id) unless session = Session.find(id)
|
71
68
|
|
72
69
|
json_response(opts) { yield session }
|
@@ -113,7 +110,7 @@ module WebConsole
|
|
113
110
|
|
114
111
|
def change_stack_trace(id, request)
|
115
112
|
json_response_with_session(id, request) do |session|
|
116
|
-
session.switch_binding_to(request.params[:frame_id])
|
113
|
+
session.switch_binding_to(request.params[:frame_id], request.params[:exception_object_id])
|
117
114
|
|
118
115
|
{ ok: true }
|
119
116
|
end
|
@@ -121,13 +118,13 @@ module WebConsole
|
|
121
118
|
|
122
119
|
def respond_with_unavailable_session(id)
|
123
120
|
json_response(status: 404) do
|
124
|
-
{ output: format(I18n.t(
|
121
|
+
{ output: format(I18n.t("errors.unavailable_session"), id: id) }
|
125
122
|
end
|
126
123
|
end
|
127
124
|
|
128
125
|
def respond_with_unacceptable_request
|
129
126
|
json_response(status: 406) do
|
130
|
-
{ output: I18n.t(
|
127
|
+
{ output: I18n.t("errors.unacceptable_request") }
|
131
128
|
end
|
132
129
|
end
|
133
130
|
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ipaddr"
|
4
|
+
|
5
|
+
module WebConsole
|
6
|
+
class Permissions
|
7
|
+
# IPv4 and IPv6 localhost should be always allowed.
|
8
|
+
ALWAYS_PERMITTED_NETWORKS = %w( 127.0.0.0/8 ::1 )
|
9
|
+
|
10
|
+
def initialize(networks = nil)
|
11
|
+
@networks = normalize_networks(networks).map(&method(:coerce_network_to_ipaddr)).uniq
|
12
|
+
end
|
13
|
+
|
14
|
+
def include?(network)
|
15
|
+
@networks.any? { |permission| permission.include?(network.to_s) }
|
16
|
+
rescue IPAddr::InvalidAddressError
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
@networks.map(&method(:human_readable_ipaddr)).join(", ")
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def normalize_networks(networks)
|
27
|
+
Array(networks).concat(ALWAYS_PERMITTED_NETWORKS)
|
28
|
+
end
|
29
|
+
|
30
|
+
def coerce_network_to_ipaddr(network)
|
31
|
+
if network.is_a?(IPAddr)
|
32
|
+
network
|
33
|
+
else
|
34
|
+
IPAddr.new(network)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def human_readable_ipaddr(ipaddr)
|
39
|
+
ipaddr.to_range.to_s.split("..").uniq.join("/")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/web_console/railtie.rb
CHANGED
@@ -1,20 +1,19 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/railtie"
|
2
4
|
|
3
5
|
module WebConsole
|
4
6
|
class Railtie < ::Rails::Railtie
|
5
7
|
config.web_console = ActiveSupport::OrderedOptions.new
|
6
|
-
config.web_console.whitelisted_ips = %w( 127.0.0.1 ::1 )
|
7
8
|
|
8
|
-
initializer
|
9
|
-
require
|
10
|
-
require
|
9
|
+
initializer "web_console.initialize" do
|
10
|
+
require "bindex"
|
11
|
+
require "web_console/extensions"
|
11
12
|
|
12
|
-
|
13
|
-
WebConsole.logger = logger
|
14
|
-
end
|
13
|
+
ActionDispatch::DebugExceptions.register_interceptor(Interceptor)
|
15
14
|
end
|
16
15
|
|
17
|
-
initializer
|
16
|
+
initializer "web_console.development_only" do
|
18
17
|
unless (config.web_console.development_only == false) || Rails.env.development?
|
19
18
|
abort <<-END.strip_heredoc
|
20
19
|
Web Console is activated in the #{Rails.env} environment. This is
|
@@ -32,13 +31,13 @@ module WebConsole
|
|
32
31
|
end
|
33
32
|
end
|
34
33
|
|
35
|
-
initializer
|
34
|
+
initializer "web_console.insert_middleware" do |app|
|
36
35
|
app.middleware.insert_before ActionDispatch::DebugExceptions, Middleware
|
37
36
|
end
|
38
37
|
|
39
|
-
initializer
|
38
|
+
initializer "web_console.mount_point" do
|
40
39
|
if mount_point = config.web_console.mount_point
|
41
|
-
Middleware.mount_point = mount_point.chomp(
|
40
|
+
Middleware.mount_point = mount_point.chomp("/")
|
42
41
|
end
|
43
42
|
|
44
43
|
if root = Rails.application.config.relative_url_root
|
@@ -46,26 +45,44 @@ module WebConsole
|
|
46
45
|
end
|
47
46
|
end
|
48
47
|
|
49
|
-
initializer
|
48
|
+
initializer "web_console.template_paths" do
|
50
49
|
if template_paths = config.web_console.template_paths
|
51
50
|
Template.template_paths.unshift(*Array(template_paths))
|
52
51
|
end
|
53
52
|
end
|
54
53
|
|
55
|
-
initializer
|
56
|
-
|
57
|
-
|
54
|
+
initializer "web_console.deprecator" do |app|
|
55
|
+
app.deprecators[:web_console] = WebConsole.deprecator if app.respond_to?(:deprecators)
|
56
|
+
end
|
57
|
+
|
58
|
+
initializer "web_console.permissions" do
|
59
|
+
permissions = web_console_permissions
|
60
|
+
Request.permissions = Permissions.new(permissions)
|
61
|
+
end
|
62
|
+
|
63
|
+
def web_console_permissions
|
64
|
+
case
|
65
|
+
when config.web_console.permissions
|
66
|
+
config.web_console.permissions
|
67
|
+
when config.web_console.allowed_ips
|
68
|
+
config.web_console.allowed_ips
|
69
|
+
when config.web_console.whitelisted_ips
|
70
|
+
WebConsole.deprecator.warn(<<-MSG.squish)
|
71
|
+
The config.web_console.whitelisted_ips is deprecated and will be ignored in future release of web_console.
|
72
|
+
Please use config.web_console.allowed_ips instead.
|
73
|
+
MSG
|
74
|
+
config.web_console.whitelisted_ips
|
58
75
|
end
|
59
76
|
end
|
60
77
|
|
61
|
-
initializer
|
78
|
+
initializer "web_console.whiny_requests" do
|
62
79
|
if config.web_console.key?(:whiny_requests)
|
63
80
|
Middleware.whiny_requests = config.web_console.whiny_requests
|
64
81
|
end
|
65
82
|
end
|
66
83
|
|
67
|
-
initializer
|
68
|
-
config.i18n.load_path.concat(Dir[File.expand_path(
|
84
|
+
initializer "i18n.load_path" do
|
85
|
+
config.i18n.load_path.concat(Dir[File.expand_path("../locales/*.yml", __FILE__)])
|
69
86
|
end
|
70
87
|
end
|
71
88
|
end
|
data/lib/web_console/request.rb
CHANGED
@@ -1,29 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module WebConsole
|
2
|
-
# Web Console tailored request object.
|
3
4
|
class Request < ActionDispatch::Request
|
4
|
-
|
5
|
-
cattr_accessor :whitelisted_ips
|
6
|
-
@@whitelisted_ips = Whitelist.new
|
7
|
-
|
8
|
-
# Define a vendor MIME type. We can call it using Mime[:web_console_v2].
|
9
|
-
Mime::Type.register 'application/vnd.web-console.v2', :web_console_v2
|
5
|
+
cattr_accessor :permissions, default: Permissions.new
|
10
6
|
|
11
|
-
|
12
|
-
|
13
|
-
# For a request to hit Web Console features, it needs to come from a white
|
14
|
-
# listed IP.
|
15
|
-
def from_whitelisted_ip?
|
16
|
-
whitelisted_ips.include?(strict_remote_ip)
|
7
|
+
def permitted?
|
8
|
+
permissions.include?(strict_remote_ip)
|
17
9
|
end
|
18
10
|
|
19
|
-
# Determines the remote IP using our much stricter whitelist.
|
20
11
|
def strict_remote_ip
|
21
|
-
GetSecureIp.new(self,
|
22
|
-
|
23
|
-
|
24
|
-
# Returns whether the request is acceptable.
|
25
|
-
def acceptable?
|
26
|
-
xhr? && accepts.any? { |mime| Mime[:web_console_v2] == mime }
|
12
|
+
GetSecureIp.new(self, permissions).to_s
|
13
|
+
rescue ActionDispatch::RemoteIp::IpSpoofAttackError
|
14
|
+
"[Spoofed]"
|
27
15
|
end
|
28
16
|
|
29
17
|
private
|
data/lib/web_console/session.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module WebConsole
|
2
4
|
# A session lets you persist an +Evaluator+ instance in memory associated
|
3
5
|
# with multiple bindings.
|
@@ -9,8 +11,7 @@ module WebConsole
|
|
9
11
|
# error pages only, as currently, this is the only client that needs to do
|
10
12
|
# that.
|
11
13
|
class Session
|
12
|
-
cattr_reader :inmemory_storage
|
13
|
-
@@inmemory_storage = {}
|
14
|
+
cattr_reader :inmemory_storage, default: {}
|
14
15
|
|
15
16
|
class << self
|
16
17
|
# Finds a persisted session in memory by its id.
|
@@ -30,9 +31,9 @@ module WebConsole
|
|
30
31
|
# storage.
|
31
32
|
def from(storage)
|
32
33
|
if exc = storage[:__web_console_exception]
|
33
|
-
new(ExceptionMapper.
|
34
|
+
new(ExceptionMapper.follow(exc))
|
34
35
|
elsif binding = storage[:__web_console_binding]
|
35
|
-
new([binding])
|
36
|
+
new([[binding]])
|
36
37
|
end
|
37
38
|
end
|
38
39
|
end
|
@@ -40,10 +41,11 @@ module WebConsole
|
|
40
41
|
# An unique identifier for every REPL.
|
41
42
|
attr_reader :id
|
42
43
|
|
43
|
-
def initialize(
|
44
|
+
def initialize(exception_mappers)
|
44
45
|
@id = SecureRandom.hex(16)
|
45
|
-
|
46
|
-
@
|
46
|
+
|
47
|
+
@exception_mappers = exception_mappers
|
48
|
+
@evaluator = Evaluator.new(@current_binding = exception_mappers.first.first)
|
47
49
|
|
48
50
|
store_into_memory
|
49
51
|
end
|
@@ -58,8 +60,10 @@ module WebConsole
|
|
58
60
|
# Switches the current binding to the one at specified +index+.
|
59
61
|
#
|
60
62
|
# Returns nothing.
|
61
|
-
def switch_binding_to(index)
|
62
|
-
|
63
|
+
def switch_binding_to(index, exception_object_id)
|
64
|
+
bindings = ExceptionMapper.find_binding(@exception_mappers, exception_object_id)
|
65
|
+
|
66
|
+
@evaluator = Evaluator.new(@current_binding = bindings[index.to_i])
|
63
67
|
end
|
64
68
|
|
65
69
|
# Returns context of the current binding
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WebConsole
|
4
|
+
class SourceLocation
|
5
|
+
def initialize(binding)
|
6
|
+
@binding = binding
|
7
|
+
end
|
8
|
+
|
9
|
+
if RUBY_VERSION >= "2.6"
|
10
|
+
def path() @binding.source_location.first end
|
11
|
+
def lineno() @binding.source_location.last end
|
12
|
+
else
|
13
|
+
def path() @binding.eval("__FILE__") end
|
14
|
+
def lineno() @binding.eval("__LINE__") end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,23 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
namespace :ext do
|
2
|
-
rootdir = Pathname(
|
4
|
+
rootdir = Pathname("extensions")
|
3
5
|
|
4
|
-
desc
|
5
|
-
task chrome:
|
6
|
+
desc "Build Chrome Extension"
|
7
|
+
task chrome: "chrome:build"
|
6
8
|
|
7
9
|
namespace :chrome do
|
8
|
-
dist = Pathname(
|
10
|
+
dist = Pathname("dist/crx")
|
9
11
|
extdir = rootdir.join(dist)
|
10
|
-
manifest_json = rootdir.join(
|
12
|
+
manifest_json = rootdir.join("chrome/manifest.json")
|
11
13
|
|
12
14
|
directory extdir
|
13
15
|
|
14
|
-
task build: [ extdir,
|
16
|
+
task build: [ extdir, "lib:templates" ] do
|
15
17
|
cd rootdir do
|
16
|
-
cp_r [
|
18
|
+
cp_r [ "img/", "tmp/lib/" ], dist
|
17
19
|
`cd chrome && git ls-files`.split("\n").each do |src|
|
18
20
|
dest = dist.join(src)
|
19
21
|
mkdir_p dest.dirname
|
20
|
-
cp Pathname(
|
22
|
+
cp Pathname("chrome").join(src), dest
|
21
23
|
end
|
22
24
|
end
|
23
25
|
end
|
@@ -34,7 +36,7 @@ namespace :ext do
|
|
34
36
|
cd(extdir) { sh "zip -r ../crx-web-console-#{version}.zip ./" }
|
35
37
|
end
|
36
38
|
|
37
|
-
desc
|
39
|
+
desc "Launch a browser with the chrome extension."
|
38
40
|
task run: [ :build ] do
|
39
41
|
cd(rootdir) { sh "sh ./script/run_chrome.sh --load-extension=#{dist}" }
|
40
42
|
end
|
@@ -45,15 +47,15 @@ namespace :ext do
|
|
45
47
|
end
|
46
48
|
|
47
49
|
namespace :lib do
|
48
|
-
templates = Pathname(
|
49
|
-
tmplib = rootdir.join(
|
50
|
-
js_erb = FileList.new(templates.join(
|
50
|
+
templates = Pathname("lib/web_console/templates")
|
51
|
+
tmplib = rootdir.join("tmp/lib/")
|
52
|
+
js_erb = FileList.new(templates.join("**/*.js.erb"))
|
51
53
|
dirs = js_erb.pathmap("%{^#{templates},#{tmplib}}d")
|
52
54
|
|
53
55
|
task templates: dirs + js_erb.pathmap("%{^#{templates},#{tmplib}}X")
|
54
56
|
|
55
57
|
dirs.each { |d| directory d }
|
56
|
-
rule
|
58
|
+
rule ".js" => [ "%{^#{tmplib},#{templates}}X.js.erb" ] do |t|
|
57
59
|
File.write(t.name, WebConsole::Testing::ERBPrecompiler.new(t.source).build)
|
58
60
|
end
|
59
61
|
end
|
@@ -1,14 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "net/http"
|
4
|
+
|
1
5
|
namespace :templates do
|
2
|
-
desc
|
6
|
+
desc "Run tests for templates"
|
3
7
|
task test: [ :daemonize, :npm, :rackup, :wait, :mocha, :kill, :exit ]
|
4
8
|
task serve: [ :npm, :rackup ]
|
5
9
|
|
6
|
-
workdir = Pathname(EXPANDED_CWD).join(
|
10
|
+
workdir = Pathname(EXPANDED_CWD).join("test/templates")
|
7
11
|
pid = Pathname(Dir.tmpdir).join("web_console_test.pid")
|
8
12
|
runner = URI.parse("http://#{ENV['IP'] || '127.0.0.1'}:#{ENV['PORT'] || 29292}/html/test_runner.html")
|
9
13
|
rackup = "rackup --host #{runner.host} --port #{runner.port}"
|
10
14
|
result = nil
|
11
|
-
browser = 'phantomjs'
|
12
15
|
|
13
16
|
def need_to_wait?(uri)
|
14
17
|
Net::HTTP.start(uri.host, uri.port) { |http| http.get(uri.path) }
|
@@ -20,15 +23,8 @@ namespace :templates do
|
|
20
23
|
rackup += " -D --pid #{pid}"
|
21
24
|
end
|
22
25
|
|
23
|
-
task :npm
|
24
|
-
Dir.chdir(workdir) { system
|
25
|
-
end
|
26
|
-
|
27
|
-
task :phantomjs do
|
28
|
-
unless system("which #{browser} >/dev/null")
|
29
|
-
browser = './node_modules/.bin/phantomjs'
|
30
|
-
Dir.chdir(workdir) { system("test -f #{browser} || npm install --silent phantomjs-prebuilt") }
|
31
|
-
end
|
26
|
+
task :npm do
|
27
|
+
Dir.chdir(workdir) { system "npm install --silent" }
|
32
28
|
end
|
33
29
|
|
34
30
|
task :rackup do
|
@@ -41,7 +37,7 @@ namespace :templates do
|
|
41
37
|
end
|
42
38
|
|
43
39
|
task :mocha do
|
44
|
-
Dir.chdir(workdir) { result = system("
|
40
|
+
Dir.chdir(workdir) { result = system("npx mocha-headless-chrome -f #{runner} -r dot") }
|
45
41
|
end
|
46
42
|
|
47
43
|
task :kill do
|
data/lib/web_console/template.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module WebConsole
|
2
4
|
# A facade that handles template rendering and composition.
|
3
5
|
#
|
@@ -5,8 +7,7 @@ module WebConsole
|
|
5
7
|
# Rails error pages.
|
6
8
|
class Template
|
7
9
|
# Lets you customize the default templates folder location.
|
8
|
-
cattr_accessor :template_paths
|
9
|
-
@@template_paths = [ File.expand_path('../templates', __FILE__) ]
|
10
|
+
cattr_accessor :template_paths, default: [ File.expand_path("../templates", __FILE__) ]
|
10
11
|
|
11
12
|
def initialize(env, session)
|
12
13
|
@env = env
|
@@ -16,7 +17,7 @@ module WebConsole
|
|
16
17
|
|
17
18
|
# Render a template (inferred from +template_paths+) as a plain string.
|
18
19
|
def render(template)
|
19
|
-
view = View.
|
20
|
+
view = View.with_empty_template_cache.with_view_paths(template_paths, instance_values)
|
20
21
|
view.render(template: template, layout: false)
|
21
22
|
end
|
22
23
|
end
|