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.
[?]

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

i guess, that the view is initially called by the controller, to which
it should return, so that we can use controller_name(). (otherwise you
would have to set a variable of some kind to tell the view where it
should go)

in this case just do something like

<% form_tag(:controller => controller_name ) %>

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

On Tue, Apr 29, 2008 at 2:58 PM, James B.

Roger P. wrote:

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?

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

Roger P. 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 B.

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.

double check the parameters for form_for [?]

On Wed, Apr 30, 2008 at 8:07 AM, James B.

Thorsten M. 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 -%>

<%= f.submit "Create" -%>

<%- end -%>

<%= link_to ‘Back’, clients_path -%>


<%=
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.

Roger P. 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

Thorsten M. 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 B. 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.

Thorsten M. 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 -%>

<%= f.submit "Create" -%>

<%- 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?

James B. wrote:

James B. 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

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 B.

Roger P. 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 B.

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

On Thu, May 1, 2008 at 8:39 AM, James B.