128

I need to call the create action in controller A, from controller B.

The reason is that I need to redirect differently when I'm calling from controller B.

Can it be done in Rails?

djGrill
  • 1,480
  • 2
  • 14
  • 10
ddayan
  • 4,052
  • 6
  • 27
  • 32
  • 2
    Possible duplicate of http://stackoverflow.com/questions/4581269/rendering-an-action-in-another-controller and http://stackoverflow.com/questions/3370234/render-controller-action-from-another-controller – Rachel Shallit Apr 23 '11 at 22:04
  • Are you speaking about POST or GET action? If GET you can simply redirect to that action. But the reason for that is not quiet clear as you can redirect from controller A to whatever url you want. – Voldy Apr 23 '11 at 22:07
  • possible duplicate of [Calling a controller from another](http://stackoverflow.com/questions/1833857/calling-a-controller-from-another) – Ciro Santilli OurBigBook.com Jul 22 '14 at 10:07
  • Can't you just make this redirect by changing bindings in routes.rb? – RealMan Jan 16 '21 at 05:12

10 Answers10

79

To use one controller from another, do this:

def action_that_calls_one_from_another_controller
  controller_you_want = ControllerYouWant.new
  controller_you_want.request = request
  controller_you_want.response = response
  controller_you_want.action_you_want
end
Sammy Larbi
  • 3,062
  • 3
  • 26
  • 21
  • 24
    If you were to want callbacks to still be executed in `controller_you_want` you would do `controller_you_want.process(:action_you_want)` – Constantijn Schepens May 01 '17 at 11:31
  • 4
    Thanks! This is very helpful for situations where you don't want to redirect, you just need a quick solution to adopt an action from one controller to another. Redirecting is really something different. Also: `render status: :ok, json: JSON.parse(controller.render(:action_you_want).first)` seems to work to return JSON from the other controller – Yourpalal Jul 28 '17 at 15:08
  • 1
    Great answer, just what I was looking for. One question - what format would request parameters need to take here? I assume they live under `controller_you_want.request` but was unable to get this firing passing a hash or parameters instance. – SRack Nov 30 '17 at 12:01
  • I'm not sure what you're asking @SRack. The `params` become available to `controller_you_want` by setting the `request` in the 3rd line. Is that what you're asking? – Sammy Larbi Nov 30 '17 at 15:58
  • If you're calling this from another controller, you just set the one you want's request to this controller's request like in the answer. If you're trying to instantiate it from a model, say, then I'm afraid that's outside of my working knowledge at the moment. (well, unless you're doing it where there is an actual request, as opposed to just building one manually. If there's an actual request, you can just pass it through methods into the object in which you'd like to use it). – Sammy Larbi Nov 30 '17 at 19:52
  • 1
    tried in rails 5, for rendering it should be `render html: controller_you_want.process(:action_you_want)` – nazar kuliyev Nov 12 '19 at 04:24
  • How can I pass and retrieve parameters from the controller instance ? – von spotz Mar 15 '20 at 14:05
  • Hello, this quote is from reddit ```Posted byu/sanjibukai 1 year ago How to continue execution to another controller#action? Hello, I want to run an action from some other controller (eg. according to some session value). I know I can use render 'other/action' but this will only render the associated view. This will not perform all the stuff that exist in the actual action method and even not the before_action methods of that other controller (which is really bad).``` This seems to be whats the case when I try to call render render on the controller. The view – von spotz Mar 17 '20 at 06:35
  • lacks the instance variables from inside the controller action. – von spotz Mar 17 '20 at 06:35
  • I believe you can access it by `controller_you_want.request.params` – Sammy Larbi Mar 17 '20 at 15:02
  • I couldn't get the Rails 5 version by @nazarkuliyev to work. Instead, my approach was: ```controller_you_want.process(:action)``` followed by ```render html: controller_you_want.render_to_string(:the_view_you_want)``` – A Fader Darkly Dec 08 '20 at 11:45
  • Is it possible to change the (some) parameters while passing the request? – estani Apr 23 '21 at 11:05
  • @estani I think that is part of the request object. So just make your changes in your controller params before setting the `controller_you_want.request` and see what happens. – Sammy Larbi Apr 23 '21 at 15:24
  • To add parameters: `controller_you_want.params.merge!({ test: 1 })`. To overwrite the whole parameters hash: `controller_you_want.params = { test: 1 }` (tested in Rails 6) – Gianpaolo Scrigna Jul 21 '21 at 15:37
  • I get Missing Template – Nathan B Oct 21 '21 at 13:43
67

You can use a redirect to that action :

redirect_to your_controller_action_url

More on : Rails Guide

To just render the new action :

redirect_to your_controller_action_url and return
Yan Foto
  • 10,850
  • 6
  • 57
  • 88
Spyros
  • 46,820
  • 25
  • 86
  • 129
  • 5
    I'm sorry but can you tell what is the difference between first line and second line? I think first will execute the remaining lines of code and then redirect. Is that correct? – aks Mar 02 '17 at 10:47
  • I'm thinking maybe the last example was a typo and should be `render` instead of `redirect_to`. What do you say, @Spyros ? – Magne Jun 09 '17 at 13:26
  • 3
    Hi @spyros, the `redirect_to` does not allow to use `post: :method` and this can be useful particularly to redirecting to an already existing `create` action of another controller as @ddayan asked at the first time. I have a similar situation where in some situation I should create another object. Calling the other `create` action can be DRYer.. – SanjiBukai Jul 14 '17 at 08:41
  • But how to modify the params – Nathan B Oct 21 '21 at 13:31
41

The logic you present is not MVC, then not Rails, compatible.

  • A controller renders a view or redirect

  • A method executes code

From these considerations, I advise you to create methods in your controller and call them from your action.

Example:

 def index
   get_variable
 end

 private

 def get_variable
   @var = Var.all
 end

That said you can do exactly the same through different controllers and summon a method from controller A while you are in controller B.

Vocabulary is extremely important that's why I insist much.

apneadiving
  • 114,565
  • 26
  • 219
  • 213
  • I know I should not do that, but this is not part of my application it's just for testing and evaluating my application. – ddayan Apr 24 '11 at 22:37
  • 9
    Love it .. separate out the method from the render, and then call the individual method where needed. Just one question .. how can `get_variable` now be called from another controller? – FloatingRock Aug 30 '14 at 18:15
  • 1
    @FloatingRock just noticed your question/comment: you have several options: could be defined in common ancestor or you could include common mixin – apneadiving Jul 24 '15 at 07:34
  • 2
    love the answer, unsure about the trailing sentence fragment – Gerard Simpson Aug 28 '18 at 07:01
30

You can use url_for to get the URL for a controller and action and then use redirect_to to go to that URL.

redirect_to url_for(:controller => :controller_name, :action => :action_name)
austin
  • 5,816
  • 2
  • 32
  • 40
  • 2
    the other one didn't seem to work for me, this looks better, but how do we pass parameters? – msanjay Jul 16 '14 at 13:36
  • 3
    @msanjay you can pass them as other parameters to url_for. For example `redirect_to url_for(:controller => :controller_name, :action => :action_name, :param1 => :val1, :param2 => :val2)` will results in `/contorller_name/action_name?param1=val1&param2=val2`. See [the docs](http://api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html) – Ariel Allon Dec 31 '14 at 03:02
  • if I try to redirect_to a root controller like "MyOtherController", from a controller like "Module::MyController".. it will resolve to calling "module/MyOtherController" .. any idea how I can call the root? – ggez44 Nov 14 '18 at 18:00
  • How to (1) redirect to POST (2) change/add POST params – Nathan B Oct 21 '21 at 13:36
13

This is bad practice to call another controller action.

You should

  1. duplicate this action in your controller B, or
  2. wrap it as a model method, that will be shared to all controllers, or
  3. you can extend this action in controller A.

My opinion:

  1. First approach is not DRY but it is still better than calling for another action.
  2. Second approach is good and flexible.
  3. Third approach is what I used to do often. So I'll show little example.

    def create
      @my_obj = MyModel.new(params[:my_model])
      if @my_obj.save
        redirect_to params[:redirect_to] || some_default_path
       end
    end
    

So you can send to this action redirect_to param, which can be any path you want.

Conor
  • 3,279
  • 1
  • 21
  • 35
fl00r
  • 82,987
  • 33
  • 217
  • 237
  • Hi, for solution B wrap as a model method, how to wrap up if there is no model at all? We are working on a search engine and would like to call search views in search engine from other engines. There is no data model for the search action at all. – user938363 Dec 06 '13 at 20:00
  • @user938363 - Maybe have the two actions both render the same view (even if those actions are in different controllers). The "render" call itself will be duplicated but that in itself is only one line of duplication - not so bad. If you have a lot of logic that prepares the hash of parameters to pass to the render call, then you could avoid duplicating that by moving it into its own file (perhaps a model in `/models` or an ordinary class or module in `/lib`). The only problem is if your controller communicates with view via instance variables - you'd have to fix that duplication another way. – antinome Aug 25 '14 at 17:39
  • What if you have a UserController that creates a new User (registration) and upon success you would like to invoke a SessionsController and authenticate user? In this case you in one way or another invoke SessionsController. Any thoughts on this? – dipole_moment Jan 26 '15 at 22:11
  • Why is it a bad practice? what's the problem with Sammy Lambi answer ? – vasilakisfil Apr 03 '17 at 08:21
7

Composition to the rescue!

Given the reason, rather than invoking actions across controllers one should design controllers to seperate shared and custom parts of the code. This will help to avoid both - code duplication and breaking MVC pattern.

Although that can be done in a number of ways, using concerns (composition) is a good practice.

# controllers/a_controller.rb
class AController < ApplicationController
  include Createable

  private def redirect_url
    'one/url'
  end
end

# controllers/b_controller.rb
class BController < ApplicationController
  include Createable

  private def redirect_url
    'another/url'
  end
end

# controllers/concerns/createable.rb
module Createable
  def create
    do_usefull_things
    redirect_to redirect_url
  end
end

Hope that helps.

Oleg Afanasyev
  • 1,754
  • 1
  • 21
  • 16
7

Perhaps the logic could be extracted into a helper? helpers are available to all classes and don't transfer control. You could check within it, perhaps for controller name, to see how it was called.

Michael Durrant
  • 93,410
  • 97
  • 333
  • 497
2

You can call another action inside a action as follows:

redirect_to action: 'action_name'

class MyController < ApplicationController
  def action1
   redirect_to action: 'action2'
  end

  def action2
  end
end
CodecPM
  • 423
  • 6
  • 18
0

You can call the controller's dispatch method like Rails does:

class CurrentConroller < ApplicationController
  def create
    # Updates the request params before to dispatch to the "remote" controller
    request.params[:my_param] = request.params[:a_param]

    AnotherController.dispatch(:create, request, response)
  end
end

Here are some details about this line of code:

  • AnotherController is the "remote" controller class where you want to call the action
  • :create is the action you'd like to call
  • request and response are the one from you current controller instance you pass to the "remote" controller

Source: https://blog.petitviolet.net/post/2020-11-16/call-another-controller-action-in-rails

ZedTuX
  • 2,859
  • 3
  • 28
  • 58
-6

Separate these functions from controllers and put them into model file. Then include the model file in your controller.

bensiu
  • 24,660
  • 56
  • 77
  • 117
  • Bad suggestion. Will mess up responsibilities, point to have MVC. Problems with session/cookie calls in model etc. – Ain Tohvri Dec 05 '14 at 13:13