Class: Jets::Controller::Rendering::RackRenderer

Inherits:
Object
  • Object
show all
Defined in:
lib/jets/controller/rendering/rack_renderer.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(controller, options = {}) ⇒ RackRenderer

Returns a new instance of RackRenderer.



8
9
10
11
# File 'lib/jets/controller/rendering/rack_renderer.rb', line 8

def initialize(controller, options={})
  @controller = controller
  @options = options
end

Instance Attribute Details

#controllerObject (readonly)

Returns the value of attribute controller.



7
8
9
# File 'lib/jets/controller/rendering/rack_renderer.rb', line 7

def controller
  @controller
end

Class Method Details

.find_app_helper_classesObject

Does not include ApplicationHelper, will include ApplicationHelper explicitly first.



232
233
234
235
236
237
# File 'lib/jets/controller/rendering/rack_renderer.rb', line 232

def find_app_helper_classes
  internal_path = File.expand_path("../../internal", File.dirname(__FILE__))
  internal_classes = find_app_helper_classes_from(internal_path)
  app_classes = find_app_helper_classes_from(Jets.root)
  (internal_classes + app_classes).uniq
end

.find_app_helper_classes_from(project_root) ⇒ Object



239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/jets/controller/rendering/rack_renderer.rb', line 239

def find_app_helper_classes_from(project_root)
  klasses = []
  expression = "#{project_root}/app/helpers/**/*"
  Dir.glob(expression).each do |path|
    next unless File.file?(path)
    class_name = path.sub("#{project_root}/app/helpers/","").sub(/\.rb/,'')

    unless class_name == "application_helper"
      klasses << class_name.camelize.constantize # autoload
    end
  end
  klasses
end

.setup!Object



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/jets/controller/rendering/rack_renderer.rb', line 211

def setup!
  require "action_controller"
  require "jets/overrides/rails"

  # Load helpers
  # Assign local variable because scope in the `:action_view do` block changes
  app_helper_classes = find_app_helper_classes
  ActiveSupport.on_load :action_view do
    include Jets::Router::Helpers # internal routes helpers
    include ApplicationHelper  # include first
    app_helper_classes.each do |helper_class|
      include helper_class
    end
  end

  ActionController::Base.append_view_path("#{Jets.root}/app/views")

  setup_webpacker if Jets.webpacker?
end

.setup_webpackerObject



253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/jets/controller/rendering/rack_renderer.rb', line 253

def setup_webpacker
  require 'webpacker'
  require 'webpacker/helper'

  ActiveSupport.on_load :action_controller do
    ActionController::Base.helper Webpacker::Helper
  end

  ActiveSupport.on_load :action_view do
    include Webpacker::Helper
  end
end

Instance Method Details

#clear_view_cacheObject



160
161
162
# File 'lib/jets/controller/rendering/rack_renderer.rb', line 160

def clear_view_cache
  ActionView::LookupContext::DetailsKey.clear if Jets.env.development?
end

#controller_instance_variablesObject

Pass controller instance variables from jets-based controller to ActionView scope



147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/jets/controller/rendering/rack_renderer.rb', line 147

def controller_instance_variables
  instance_vars = @controller.instance_variables.inject({}) do |vars, v|
    k = v.to_s.sub(/^@/,'') # @var => var
    vars[k] = @controller.instance_variable_get(v)
    vars
  end
  instance_vars[:event] = event
  # jets internal variables
  # So ActionView has access back to the jets controller
  instance_vars[:_jets] = { controller: @controller }
  instance_vars
end

#default_template_nameObject

Example: posts/index



101
102
103
# File 'lib/jets/controller/rendering/rack_renderer.rb', line 101

def default_template_name
  "#{template_namespace}/#{@controller.meth}"
end

#rackify_headers(headers) ⇒ Object

Takes headers and adds HTTP_ to front of the keys because that is what rack does to the headers passed from a request. This seems to be the standard when testing with curl and inspecting the headers in a Rack app. Example: gist.github.com/tongueroo/94f22f6c261c8999e4f4f776547e2ee3

This is useful for:

ActionController::Base.renderer.new(renderer_options)

renderer_options are rack normalized headers.

Example input (from api gateway)

{"host"=>"localhost:8888",
"user-agent"=>"curl/7.53.1",
"accept"=>"*/*",
"version"=>"HTTP/1.1",
"x-amzn-trace-id"=>"Root=1-5bde5b19-61d0d4ab4659144f8f69e38f"}

Example output:

{"HTTP_HOST"=>"localhost:8888",
"HTTP_USER_AGENT"=>"curl/7.53.1",
"HTTP_ACCEPT"=>"*/*",
"HTTP_VERSION"=>"HTTP/1.1",
"HTTP_X_AMZN_TRACE_ID"=>"Root=1-5bde5b19-61d0d4ab4659144f8f69e38f"}


137
138
139
140
141
142
143
144
# File 'lib/jets/controller/rendering/rack_renderer.rb', line 137

def rackify_headers(headers)
  results = {}
  headers.each do |k,v|
    rack_key = 'HTTP_' + k.gsub('-','_').upcase
    results[rack_key] = v
  end
  results
end

#renderObject

Example response:

[200, {"my-header" = > "value" }, "my body" ]

Returns rack triplet



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/jets/controller/rendering/rack_renderer.rb', line 18

def render
  # we do some normalization here
  status = normalize_status_code(@options[:status])

  base64 = normalized_base64_option(@options)

  headers = @options[:headers] || {}
  set_content_type!(status, headers)
  # x-jets-base64 to convert this Rack triplet to a API Gateway hash structure later
  headers["x-jets-base64"] = base64 ? 'yes' : 'no' # headers values must be Strings

  if (status)
    body = StringIO.new
  else
    # Rails rendering does heavy lifting
    # _prefixes provided by jets/overrides/rails/action_controller.rb
    ActionController::Base._prefixes = @controller.controller_paths
    renderer = ActionController::Base.renderer.new(renderer_options)
    clear_view_cache
    body = renderer.render(render_options)
    body = StringIO.new(body)
  end

  [status, headers, body] # triplet
end

#render_optionsObject



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/jets/controller/rendering/rack_renderer.rb', line 75

def render_options
  # normalize the template option
  template = @options[:template]
  if template and !template.include?('/')
    template = "#{template_namespace}/#{template}"
  end
  template ||= default_template_name
  # ready to override @options[:template]
  @options[:template] = template if @options[:template]

  render_options = {
    template: template, # weird: template needs to be set no matter because it
      # sets the name which is used in lookup_context.rb:209:in `normalize_name'
    layout: @options[:layout],
    assigns: controller_instance_variables,
    # prefixes: ["posts"],
  }
  types = %w[json inline plain file xml body action].map(&:to_sym)
  types.each do |type|
    render_options[type] = @options[type] if @options[type]
  end

  render_options
end

#renderer_optionsObject

default options:

https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/renderer.rb#L41-L47


46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/jets/controller/rendering/rack_renderer.rb', line 46

def renderer_options
  options = {
    # script_name: "", # unfortunately doesnt seem to effect relative_url_root like desired
    # input: ""
  }

  origin = headers["origin"]
  if origin
    uri = URI.parse(origin)
    options[:https] = uri.scheme == "https"
  end

  # Important to not use rack_headers as local variable instead of headers.
  # headers is a method that gets deleted to controller.headers and using it
  # seems to cause issues.
  rack_headers = rackify_headers(headers)
  options.merge!(rack_headers)

  # Note @options[:method] uses @options vs options on purpose
  @options[:method] = event["httpMethod"].downcase if event["httpMethod"]

  # This is how we pass parameters to actionpack. IE: params to the view.
  # This is because renderer_options is actually the env that is passed to the rack request.
  options.merge!("action_dispatch.request.path_parameters" => @controller.path_parameters)
  options.merge!("action_dispatch.request.query_parameters" => @controller.query_parameters)
  options.merge!("action_dispatch.request.request_parameters" => @controller.request_parameters)
  options
end

#template_namespaceObject

PostsController => “posts” is the namespace



106
107
108
# File 'lib/jets/controller/rendering/rack_renderer.rb', line 106

def template_namespace
  @controller.class.to_s.sub('Controller','').underscore
end