asthasr
asthasr

Reputation: 9407

How do I modify the request object before routing in Rails in a testable way?

So, I have a situation where I need to determine something about a request before it is dispatched to any of the routes. Currently, this is implemented using several constraints that all hit the database, and I want to reduce the database hit to one. Unfortunately, doing it inline in routes.rb doesn't work, because the local variables within routes.rb don't get refreshed between requests; so if I do:

# Database work occurs here, and is then used to create comparator lambdas.
request_determinator = RequestDeterminator.new(request)

constraint(request_determinator.lambda_for(:ninja_requests)) do
  # ...
end

constraint(request_determinator.lambda_for(:pirate_requests)) do
  # ...
end

This works great on the first request, but then subsequent requests get routed as whatever the first request was. (D'oh.)

My next thought was to write a Rack middleware to add the "determinator" to the env hash, but there are two problems with this: first, it doesn't seem to be sticking in the hash at all, and specs don't even go through the Rack middleware, so there's no way to really test it.

Is there a simple mechanism I'm overlooking where I can insert, say, a hook for ActionDispatch to add something to the request, or just to say to Rails routing: "Do this before routing?"

I am using Rails 3.2 and Ruby 1.9.

Upvotes: 2

Views: 1563

Answers (2)

Milind
Milind

Reputation: 5112

if you really want to intercept the request,try rack as it is the first one to handle request in any Rails app...refer http://railscasts.com/episodes/151-rack-middleware to understand how rack works....

hope it helps.

Upvotes: 0

Chris Heald
Chris Heald

Reputation: 62658

One way to do this would be to store your determinator on the request's env object (which you have since ActionDispatch::Request is a subclass of Rack::Request):

class RequestDeterminator
  def initialize(request)
    @request = request
  end

  def self.for_request(request)
    request.env[:__determinator] ||= new(request)
  end

  def ninja?
    query_db
    # Verify ninjaness with @request
  end

  def pirate?
    query_db
    # Verify piratacity with @request
  end

  def query_db
    @result ||= begin
      # Some DB lookup here
    end
  end
end

constraint lambda{|req| RequestDeterminator.for_request(req).ninja? } do
  # Routes
end

constraint lambda{|req| RequestDeterminator.for_request(req).pirate? } do
  # Routes
end

That way, you just instantiate a single determinator which caches your DB request across constraint checks.

Upvotes: 2

Related Questions