views:

765

answers:

6

I'd like to be able to dispatch from one controller action to another conditionally, based on a combination of query parameters and data in the database.

What I have right now is something like:

class OldController < ApplicationController
  def old_controller_action
    if should_use_new_controller
      new_params = params.dup
      new_params[:controller] = "new_controller_action"
      redirect_to new_params
      return
    end
    # rest of old and busted
  end
end

class NewController < ApplicationController
  def new_controller_action
    # new hotness
  end
end

This works just fine, but it issues an HTTP redirect, which is slow. I'd like to be able to do this same thing, but within the same HTTP request.

Is there a clean way to do this?

Edit: The bounty will go to someone who can show me a clean way to do this that leaves the controllers and their actions relatively untouched (other than the redirect code itself).

+8  A: 

Instead of calling code across actions, extract the code to lib/ or something, and call that code from both controllers.

# lib/foo.rb
module Foo
  def self.bar
  # ...
  end
end

# posts_controller
def index
  Foo.bar
end

# things_controller
def index
  Foo.bar
end
August Lilleaas
Yeah, I'd thought of this as well, though it's not a terribly exciting option, from a code organization perspective.
Scotty Allen
+2  A: 

Create an instance of the controller class:

@my_other_controller = MyOtherController.new

Then call methods on it:

@my_other_controller.some_method(params[:id])

I prefer the module idea, but this should do the trick.

Jarrod
+1  A: 

I suspect you want option 3, but lets go through the some alternatives first

Option 1 - Push the controller selection logic into a helper that inserts the right link into your view. Benifits - controllers remain clean, Cons - if decision logic depending on submitted values this approach won't work. If URL is being called by external websites then this won't work.

Option 2 - Push the logic back into your model. Pro's - keeps controller clean. Cons - doesn't work well if you've got lots of sesson, params or render / redirect_to interaction.

Option 3 - Stay within the same controller. I suspect you are trying to replace some existing functionality with some new functionality, but only in some cases. Pro's - Simple and have access to everything you need. Cons - only works if it makes sense to use the same controller i.e. you're working with the same entity such as user, place or company.

Lets look an an example for option 3. My links controller has totally diferent behavour for admins than other users ...

class LinksController < ApplicationController
  #...

  def new
    #Check params and db values to make a choice here
    admin? ? new_admin : new_user
  end

  #...

private

  def new_admin
    #All of the good stuff - can use params, flash, etc 
    render :action => 'new_admin'    
  end

  def new_user
    #All of the good stuff - can use params, flash, etc 
    render :action => 'new_user' 
  end

end
RichH
A: 

If two controllers are trying to do the same thing, there's a very good chance this should be in a model. Take a good look at your design and -- I'm sorry I don't know your experience level with MVC -- read up on thin controller techniques:

http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model http://www.robbyonrails.com/articles/2007/06/19/put-your-controllers-on-a-diet-already http://andrzejonsoftware.blogspot.com/2008/07/mvc-how-to-write-controllers.html

If the problem is that you need the other controller to do the render, then maybe the route should have pointed there to begin with, and still the skinny controller technique should save the day.

Walt Gordon Jones
A: 

Do this:

class OldController < ApplicationController
  def old_controller_action
    if should_use_new_controller
      new_controller_action
    end
    # rest of old and busted
  end
end

and the new controller

class NewController < OldController
  def new_controller_action
    # new hotness
  end
end
Toader Mihai Claudiu
A: 

If extracting the common code between controllers into a module doesn't work for you, I would use Rack middleware. I haven't seen code that uses ActiveRecord within middleware but I don't know of any reason why it shouldn't be possible since people have used Redis and the like.

Otherwise I think your only option would be to restart processing of the request with something like (untested, pseudo example):

env['REQUEST_URI'] = new_controller_uri_with_your_params
call(env)

This is similar to how integration tests are implemented. But I don't know if everything from call until you hit a controller is idempotent and safe to rerun like this. You could trace through the source and see. But even if it's ok now, it might break in any future version of rails or rack.

Using middleware would avoid this by letting you intercept the request before it's been run. You should still be able to share code with your rails application by extracting it out into common modules included in both places.

Honestly I think just doing the simple thing of factoring the common controller code is likely cleaner, but it's hard to know without the details of your situation so I thought I'd go ahead and suggest this.

Jason Watkins