Rails - Internal Requests
I needed to perform an internal request in Rails, that is get the result of a request during another request with a high level of isolation and without resorting to naive/costly measures (i.e. performing a Net::HTTP request against the local app).
Originally I tried an approach that worked around Rails.application.call
, and while this worked fine in the console I ran into a problem executing this during an actual request due to Rack::Lock
causing deadlocking issues.
The final approach involves skipping the Rack middleware (which includes Rack::Lock
being invoked) and can be ran during a request (or through the console without the session merge) for a highly isolated internal request. My current implementation looks like this:
def internal_request(path, params={})
request_env = Rack::MockRequest.env_for(path, params: params.to_query).merge({
'rack.session' => session # remove if session is unavailable/undesired in request
})
# Returns: [ status, headers, body ]
Rails.application.routes.call(request_env)
end
Thanks go to Sam Pohlenz for how he solved a very similar problem as well as insights into Rack.
Written by James Brooks
Related protips
5 Responses
Hi there, I'm curious as to the use case here? usually when you have a process and response that is used by another controller; that process should then be abstracted into a service..
i.e.: app/services/mysharedservice.rb which returns whatever structure you are requiring.
Once you have this layout, your two controllers can instantiate and share use of that service... much cleaner and you dont need to clutter your project?
No no no no no. Don't ever do this.
Extract the common code into a service object, or make the direct DB call or something. Don't ever make internal requests.
"Don't ever do this" is terrible advice. Hard and fast rules are never a good idea.
What if you have a batch API endpoint, for example, that needs to be able to process multiple API requests in a single HTTP request? Obviously in that case, you'd want to be able to simulate multiple internal requests without recreating all that code or parsing it.
I'm not saying this is something you generally want to be doing, but to say you should NEVER do it is just as bad.
The use-case here is a game containing many actions (as mostly POROs). Actions can be invoked depending on a number of things through a common controller that handles execution of actions. As part of a action execution the action can possibly dictate that a user should be redirected to another page (which after handled by a JS layer is just an ajax call with div replacement).
Originally this worked with having the result of the action request possibly return a key/value pair indicating that this redirection should occur, the client would then perform the request and perform the div replace with the entire returned request.
The internal request here was meant to be able to perform this second request in pair with the first request, returning the payload for the body of the page that we know that the client would then be requesting after this current request is complete, while still having everything operate as it did perviously (filters hit, controller action(s) ran, views rendered, return).
While this isn't something I haven't been overly happy to have had implement it isn't something I've had to do as of yet in my professional Rails career, and hopefully not often again, and serves as a reference for how this can be accomplished (a few hours of googling prior didn't reveal much).
Any suggestions for how to implement that without an internal request which doesn't introduce a whole lot of bloat I'll be happy to hear :)
What you describe here makes me think your controller is doing too much.
As was said earlier, just abstract the behavior into another class and send it the appropriate message(s). That abstraction shouldn't tell you whether or not you need to redirect anywhere, or replace DIVs, or anything of that nature. All of that logic still belongs in the controller, but the abstraction should give you enough information to be able to make that determination.
The same can be said in regard to @ctide's response.