Returning to multiple controllers from a single view.

Given:

class Entity

has_one :client

class Client

belongs_to :entity

I have the situation where a client role either can be created coincidentally with an entity or subsequent to an entity's creation. I have therefore created two controllers to handle this within the constraints of ReST: clients_controller, which handles the coincidental creation situation; and entity_clients_controller, which handles the case of adding a client role to an existing entity.

Now it seems desirable that I use the same view to do this since the user input fields are exactly the same in both cases. The only difference being is that in the latter case the entity values are already provided. So, inside entity_clients_controller I have this code:

def new   @entity = Entity.find(params[:entity_id])   @client = @entity.build_client   @client.effective_from = Time.now.to_date

  respond_to do |format|     format.html { render :template => 'clients/new' } # new.html.erb     format.xml { render :xml => @client }   end end

Which displays the contents of the passed entity in the fields of the new template. Everything works up to this point. My problem is that the submit button on the clients/new view redirects back to clients_controller or course. However, clients_controller expects to create a new entity as well as a client and that simply will not do.

My question is therefore, how do I handle this? I want a single view, called from two controllers, to return to the calling controller. How is this properly done in Rails? Do I really need a separate view?

typically I use an if statement on params[:controller] or params[:action] to setup the form so it posts back to the right place. [?]

Roger Pack wrote:

typically I use an if statement on params[:controller] or params[:action] to setup the form so it posts back to the right place. [?]

On Tue, Apr 29, 2008 at 12:20 PM, James Byrne

Ahh. I see. In params I have:

--- !map:HashWithIndifferentAccess user: !map:HashWithIndifferentAccess   userid: authuser   user_id: 13466   user_name: A. N. Authorized-User entity_id: "1" action: new controller: entity_client

form_helper.submit simply calls submit_tag with these parameters:

def submit(value = "Save changes", options = {})    @template.submit_tag(value,      options.reverse_merge(:id => "#{object_name}_submit")) end

which I infer threads back through form_tag_helper.rb

      def submit_tag(value = "Save changes", options = {})         options.stringify_keys!

        if disable_with = options.delete("disable_with")           options["onclick"] = [             "this.setAttribute('originalValue', this.value)",             "this.disabled=true",             "this.value='#{disable_with}'",             "#{options["onclick"]}",             "result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit())",             "if (result == false) { this.value = this.getAttribute('originalValue'); this.disabled = false }",             "return result;",           ].join(";")         end

        if confirm = options.delete("confirm")           options["onclick"] ||= ''           options["onclick"] += "return #{confirm_javascript_function(confirm)};"         end

        tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)       end

But I cannot see where the magic that determines what controller gets to handle the request happens. Can someone with far greater familiarity with the Rails internals point me in the right direction?

It seems to me at first blush that given that the calling controller is passed to a view that it should be possible, indeed even a desirable default, that control should return to the calling controller rather than to one derived from the view/object name, or am I missing something significant?

In any case, it might be useful to have an option for the submit method to pass the controller name as an argument :controller => params[:controller] for instance.

with link_to [or form_for or whatever] if you don't specify a controller as a parameter, it defaults to the current controller [and the calling action is also the default]. -R

I am starting with a form_for block in the view which has a submit method at the end. Is the recommended solution for my proplem to take out the submit and replace it with a link_to method?

a bit clarification: there are several methods to call a controller action:

most simple: link_to "text", url

with data: form_tag url   fields   submit_tag end

and a few more. but in any case you have to give it an url as an option and you can chose that url to point wherever you want. you can make this decision static or variable.

but in case of using a form the submit button has nothing to do with the controller, action or url, that's defined in the form_tag (or form_for or whatever kind you use)

maybe you just post your view code

Thorsten Mueller wrote:

maybe you just post your view code

The entire view: ---> <%- content_tag_for :h2, @client, :heading do %>   Adding Client Role for Entity   <%#= @client.entity.id.to_s -%>   <%- end -%>

<%= error_messages_for :client -%> <%= error_messages_for :entity -%>

<%- form_for(@client) do |f| -%>

  <%= render :partial => 'entities/entity_header',               :object => @entity -%>

  <%= render :partial => 'clients/client_detail',               :object => @client -%>

  <%= render :partial => 'shared/effective_period',               :object => @client -%>

  <p>     <%= f.submit "Create" -%>   </p> <%- end -%>

<%= link_to 'Back', clients_path -%>

<br />

<!-- Disable debug in production -->   <%=       debug(params).to_yaml   -%> <---

This view is called from two controllers, clients_controller and entity_clients_controller. In the first instance (clients) the entity does not, or should not, exist. In the second case (entity_clients_controller), the entity is known to exist and the client role is to be added.

On the submit action (create == post?) control is sent to the clients controller regardless of origin. I simply wish to send control back to the calling controller.

double check the parameters for form_for [?]

Roger Pack wrote:

double check the parameters for form_for [?]

I am afraid that I lack the experience to express my desire to dynamically determine the controller context in the form_to options. Is there an opotion to specify :controller => params[:controller] ?. If so then I cannot see it.

ok, you're using the shortcut version

form_for(@client)

here. this can take a lot of additiional parameters. from what u use, rails will generate the following defaults:

form_for :client, @client, :url => client_path(@client), :html => { :method => :put, :class => "edit_client", :id => "edit_client_45" }

if @client is an existing record or if it's a new one it would be:

form_for :client, @client, :url => clients_path, :html => { :class => "new_client", :id => "new_client" }

so if you want to change details, just overwrite the right parameter like: (assuming @has_entity <% if @entity then %> <%= form_for(@client, :url => client_path(@client)) <% else %> <%= form_for(@client, :url => entity_client_path(@client)) <% end %>

or shorter: <%= form_for(@client, :url => (@entity ? client_path(@client) : entity_client_path(@client)))

there are some more options to handle this, depending if you always have the Client in your db or may have to handle both new/create and edit/update

http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#M000920

Thorsten Mueller wrote:

or shorter: <%= form_for(@client, :url => (@entity ? client_path(@client) : entity_client_path(@client)))

If I understand then this code means that if @entity is not nil, in other words this view was called from entity_clients_controller, then the form submit action will return to client_path but if @entity is nil then control returns to entity_client_path. Is this correct?

If this is so then I get the idea but the behaviour of the example is reversed from what I desire. @entity is nil when called from clients_controller so that the construct that I believe I should use is:

<%= form_for(@client, :url => (@entity ? entity_client_path(@client) :

client_path(@client)))

Is this correct?

right you simply overwrite default behaviour by providing the url explicitly you can make this depend onwhatever you want, the existing of an @entity in your case or the controller name

(and yes, i got it the wrong way around)

James Byrne wrote:

After (re)wrapping my head around the x ? T : F construct (which I read is gone from Ruby 1.9) I did this:

<%- form_for(@client, \       :url => (@entity ? client_path(@client) \                         : entity_client_path(@client))) do |f| -%>

This works fine for the original call from the client controller. New clients and their associated entities are created in one go as was formerly the case.

However, with the call to add a client from entities/index:

<%- if entity.client.nil? -%>   <%= link_to 'Add Client Role', new_entity_client_path(entity) -%> <%- else -%>

I go to:

http://localhost:3000/entities/1/client/new

and then I see this (in addition to the client/new view fields:

--- !map:HashWithIndifferentAccess user: !map:HashWithIndifferentAccess   userid: authuser   user_id: 13466   user_name: A. N. Authorized-User entity_id: "1" action: new controller: entity_client

Named Routes for entity_client Name   Requirements   Conditions formatted_entity_client   {:controller=>"entity_client", :action=>"show"}   {:method=>:get}

entity_client   {:controller=>"entity_client", :action=>"show"}   {:method=>:get}

formatted_edit_entity_client   {:controller=>"entity_client", :action=>"edit"}   {:method=>:get}

edit_entity_client   {:controller=>"entity_client", :action=>"edit"}   {:method=>:get}

formatted_new_entity_client   {:controller=>"entity_client", :action=>"new"}   {:method=>:get}

new_entity_client   {:controller=>"entity_client", :action=>"new"}   {:method=>:get}

Where am I going to when I submit in this case? I end up with the same error as above.

James Byrne wrote:

James Byrne wrote:

  <%= link_to 'Add Client Role', new_entity_client_path(entity) -%>

and then I see this (in addition to the client/new view fields:

--- !map:HashWithIndifferentAccess user: !map:HashWithIndifferentAccess   userid: authuser   user_id: 13466   user_name: A. N. Authorized-User entity_id: "1" action: new controller: entity_client

the new action does not need an id, so it's path helper does not allow one by default. (depending on the kind of action and your routing, rails expects one or more default ids and accepts simple values for them. additional parameters must be defined with a params key)

use: <%= link_to 'Add Client Role', new_entity_client_path(:entity_id => entity) -%>

instead. this will return as params[:entity_id]. the name of the key can be changed if you want to. it's entity that generated that error, since rails wanted to treat it as the hash :params_key => id

Thorsten Mueller wrote:

use: <%= link_to 'Add Client Role', new_entity_client_path(:entity_id => entity) -%>

instead. this will return as params[:entity_id]. the name of the key can be changed if you want to. it's entity that generated that error, since rails wanted to treat it as the hash :params_key => id

Please bear with me, I am very unfamiliar with all this.

What I have now is this:

clients/new

<%- form_for(@client, \         :url => (@entity ? client_path(@client) \                           : entity_client_path(@client))) do |f| -%>

  <%= render :partial => 'entities/entity_header',               :object => @entity -%>

  <%= render :partial => 'clients/client_detail',               :object => @client -%>

  <%= render :partial => 'shared/effective_period',               :object => @client -%>

  <p>     <%= f.submit "Create" -%>   </p> <%- end -%>

Am I to remove <%= f.submit "Create" -%>

and replace it with

<%= link_to 'Add Client Role', etc.?

keeping the rest the same?

The error I am getting is a validations check that the entity already exists. That is the correct behaviour when adding a client and an entity together as the entity should not already be on file. However, in the case where the entity is known to exist beforehand I do not want the validation to fail. Is this really a problem in the way I imagine validations to work?

I thought that the problem arose because in the clients controller the code to add the client role looks like this:

  def create     @entity = Entity.new(params[:entity])

    # need this to strip out observer attributes for datebalks plugin     # see config/initializers/hash_addins.rb

    @client = @entity.build_client(params[:client].datebalk!)

    respond_to do |format|       if @entity.save         flash[:notice] = 'Client was successfully created.'         format.html { redirect_to(@client) }         format.xml { render :xml => @client,           :status => :created, :location => @client }       else         format.html { render :action => "new" }         format.xml { render :xml => @client.errors,           :status => :unprocessable_entity }       end

but in entity_client_controller it looks like this:

def create   @entity = Entity.find(params[:entity_id])

  # need this to strip out observer attributes for datebalks plugin   # see config/initializers/hash_addins.rb

  @client = @entity.build_client(params[:client].datebalk!)

  respond_to do |format|     if @client.save       flash[:notice] = 'Client was successfully created.'       format.html { redirect_to(@client) }       format.xml { render :xml => @client,         :status => :created, :location => @client }     else       format.html { render :action => "new" }       format.xml { render :xml => @client.errors,         :status => :unprocessable_entity }     end   end end

I surmise that I may have something amiss in the controller?

http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html form_for can have parameters     <% form_for :person, @person, :url => { :action => "create", :controller => 'controller_y' } do |f| %> ...

Roger Pack wrote:

ActionView::Helpers::FormHelper form_for can have parameters     <% form_for :person, @person, :url => { :action => "create", :controller => 'controller_y' } do |f| %> ...

On Wed, Apr 30, 2008 at 11:45 AM, James Byrne

This is exactly what I was trying to discover. Thank you very much for all the help. This is what I ended up with in the view:

<%- form_for @client, :url => { :action => 'create', \     :controller => "#{params[:controller]}" } do |f| -%>

nice work. -R