2

I am trying to render from outside a controller exactly what my Rails app renders from within a controller when a user sends an HTTP request with specific headers and parameters.

ApplicationController.renderer is pretty good for this, however it requires me to pass the specific instance variables the template will need to render. This does not work for me as it skips the logic my app uses to determine the assigns the template will need.

For example, my app has a before_filter to determine which language to use from the HTTP_ACCEPT_LANGUAGE header and send that language to the template. I don't want to have to duplicate this logic outside the controller where I want to render to a string.

One "solution" that works here is to programmatically send an actual HTTP request to my app with the appropriate headers and so forth but this is obviously inefficient.

What I'd like instead is to be able to do something like this:

ApplicationController.renderer.new({
  params: {
    q: 'search term'
  },
  request: {
    env: { 'HTTP_ACCEPT_LANGUAGE': "en" }
  }
}).render(inline: "<%= params[:q] %> in lang <%= request.env['HTTP_ACCEPT_LANGUAGE'] %>")

Is this possible?

Tom Lehman
  • 85,973
  • 71
  • 200
  • 272
  • 1
    My two cents: your renderer may render outside your controller but your controller is what is gonna be hit by the user and holds the logic. Can you not pass what you get from the controller to your renderer ? (As I guess the controller is at a higher point in the logic than the renderer). Or maybe it ils possible to pass the http request as a param ... – Maxence Nov 25 '21 at 17:59
  • 2
    "programmatically send an actual HTTP request to my app with the appropriate headers and so forth but this is obviously inefficient" - Try it before you diss it. The Rails community spent tons of work on ActionDispatch::IntegrationTest to the point where its actually faster then the older functional controller tests. – max Nov 26 '21 at 02:40
  • 1
    There are some key problems with your intended approach which are about the same as with ActionController::TestCase. When you instanciate a controller with a mocked request you're bypassing the entire middleware stack so your controller may not actually behave as intended. For example Devise will break as it depends on Warden being inserted in the middleware. – max Nov 26 '21 at 02:46
  • @max Good points! I am actually using the "actual HTTP request" approach right now and it has actually worked okay but it's starting to break as my site gets bigger. My use case is cache warming and I want to be aggressive about keeping every page cached but I don't want to compete with my users / DDOS myself to do this. I am on Heroku so I could throw more dynos at the problem, but this is expensive, etc. Your point about the middleware is a good one! However it does seem like in principle you should be able to simulate exactly what the Heroku router tells my web dyno? Tricky! – Tom Lehman Nov 26 '21 at 15:21
  • 1
    I haven't tried this scenario but in function controller tests the constant issue is that mocking requests and huge parts of the framework introduces lots of subtile bugs and differences. A controller entire purpose is to process HTTP requests so that means there is a lot of input and context going in. – max Nov 26 '21 at 15:28
  • @max But there is still *something* between the client and the Rails app (Heroku router, some non-Ruby web server, etc) / the Rails app doesn't have access to the "actual" HTTP request but rather only the "router's" report of the HTTP request and so it seems like there must be some way to simulate that "router" and give the Rails app something HTTP request-esque and then receive the output the Rails app would create if the HTTP request-esque thing were a real HTTP request as in the case of a real router. Like, fundamentally you give the Rails app a bunch of data and it spits back a response? – Tom Lehman Nov 26 '21 at 18:06
  • 1
    The devil is still in the details - doing all that while maintaining acuity and matching the performance of a highly optimized pathway is going to be tricky. – max Nov 28 '21 at 12:05
  • Instead of breaking the rails strong opinionated MVC workflow: request -> action -> view. Why not just refactor the logic you need into a method in ApplicationController, and call it to set the instance variables(assigns) needed and then render the view template needed? Even you really make what you want, the future maintainer may be confused by this "inner HTTP request" hack, right? – kevinluo201 Dec 05 '21 at 15:23

0 Answers0