Pass local variables to another controller

I've been beating by noob head against a wall, I'd appreciate any help that can be offered.

I have a view listing items with a link to append a description to each item. I believe I have the associations in the Models defined correctly (item has_many :descriptions, description belongs_to :item) to be RESTful and such...

My item view includes the following code in the <% for item in @items %>:

<%= link_to 'Describe', :controller => :discriptions, :action => 'new', :locals => {:item_id => item.id} %>

and in the development.log file I see

Parameters: {"locals"=>{"item_id"=>"2"}, "action"=>"new", "controller"=>"descriptions"}

But I can't seem to apply the item_id from the items/index view to the object being created in the descriptions/new view.

The DescriptionsController reads

def new   @description = Description.new   @description.item_id = 'item_id' (I know this doesn't work, it's just the last thing I tried before calling it a day...)

BTW there is an item_id field in the Descriptions table... I must be missing something in plain sight. Thanks for any guidance.

SH

Try it like this:

Item.find(params[:locals][:item_id]).descriptions << Description.new

Or if you really want to, you can do it your way but use: @description.item_id = params[:locals][:item_id] but it's two lines versus one, and you still need a save. The << way will create the description, save it, and add it to the Item's has_many association (i.e. set item_id on the Description object)

Really, though, you can leave off the locals too:

<%= link_to 'Describe', :controller => :descriptions, :action => 'new', :item => item %>

And then:

Item.find(params[:item]).descriptions << Description.new

(when using an object as a hash value, i.e. in the find, Rails will pull out the ID for you automatically... I think that's a relatively new feature... well, I don't think Rails 1.1 was that way at least)

:slight_smile:

-Danimal

Danimal,

Thanks for the reply... I edited the DescriptionsController to read

  def new       Item.find(params[:item]).descriptions << Description.new   end

and changed the link_to code in the view... I got an error but saw that rails created a Description object with that item_id in the database before the error was triggered. I think I'm missing something in the DescriptionsControllers 'new' code?

Switching gears and using @description.item_id = params[:locals] [:item_id] instead, I got the correct id passed to the Description object, but if the text_field for the item_id was removed from the view it was saved as nil for that object! Without the text_field in the view I see in the development.log:

  Parameters: {"commit"=>"Create", "description"=>{"author"=>"joe", "topic"=>"wine"}

... with the text_field in the view I see in the development.log:

  Parameters: {"commit"=>"Create", "description"=>{"item_id"=>"3", "author"=>"joe", "topic"=>"wine

Ugh. Any way to get the item_id saved without that field available for the user to change? Or perhaps figure out what I'm doing wrong to create the error following your suggestion Thanks again for any help.

SH

Hi Danimal, If passing the item id from the view without an text field is what you need, just put a hidden_field in the view.

@SH

It's important to think about the process that's going on here. The language you're using to describe the problem is a little confused and it might help a lot to get a bird's eye view of what you're doing. In the simplest scenario the controller instance is a very short-lived object that springs to life only to answer a question posed by the user. It's job is to gather enough information to answer the question and reply in a way that the user understands... and may eventually ask another question. In a sense it's part of a conversation with the user. In this regard you are not 'passing local variables to another controller', you are answering a user request and the user is making a completely new request to some other controller. It's an important distinction to make because it will help keep you from thinking in terms of sending messages to another controller; controllers respond to browsers.

Specifically here the main question to ask is whether the Descriptions will *always* be attached to Items. If so then you can do a few things that will really simplify your coding.

1. Modify your routes.rb file, nesting the descriptions resource inside the items resource:

map.resources :items do |item|   item.resources :descriptions end

By doing this you explicitly state to the Routing system that descriptions belong to items. The bonus is that you get named routes to help you with your main issue.

2. Use the named route in your link_to <%= link_to 'Describe', new_item_description_path(@item) %>

This will build a url for you that will look something like /items/15/ descriptions/new. The advantage is that the route is created by the Routing system so you can't miss. Better still, the intention of the code is much clearer. (As a side note, I am assuming that you got the :locals reference by reading some about partials... it's not necessary here).

3. Code the expectation for an Item into the Description model. The main argument for the nested route (#1) is that the nested item only makes sense in the context of it's parent. In this situation, the Description is only meaningful with its Item. You can/should make that explicit in your controller. When it responds to the request to make a new Description for an Item it can say so:

class DescriptionsController < ApplicationController   ...   def new     @item = Item.find params[:item_id] # Note: item_id is created by the named route     @description = @item.descriptions.build   end

  def create     @item = Item.find params[:item_id]     @description = @item.descriptions.build params[:description]

    respond_to do |format|       if @description.save       ...       end     end   end end

4. Also let your new.html.erb take advantage of the named route <% form_for :item, :url=>item_descriptions_path, :method=>:post do |f| %> ... <% end %>

Here you're going to post a new entry into the Item.descriptions collection (you can clean this up further if it's a singular resource). The create method (above) will receive the parameters. As with #new, it already understands that it's scoped to an Item and the named route has made sure that you got the item_id. @item.descriptions.build params[:description] will build a new Description object using the values that the user keyed and automatically pop the scoping item_id in as well (note -- item_id was not available to the user!).

I use a very simple and transparent method (in this app, a scenario has_many unit tests):

In the show view for a Scenario, the link for adding a new Unittest just passes the current scenario's ID in the params (if this looks strange, I use haml for my views):

%td.ctl= link_to 'New Unit Test', new_unittest_path(:scenario => scenario_id)

Then in the Unittest controller, see if that param is populated, if so, set the new Unittest's value:

  def new     @unittest = Unittest.new     @unittest.scenario_id = params[:scenario] if params[:scenario]     respond_to do |format|       format.html # new.html.erb       format.xml { render :xml => @unittest }     end   end

@AndyV

Thank you for the explanation... you're right that my understanding of what's going on is confused, I'm new to coding and get myself stuck in rabbit holes then find myself trying to use :locals to solve problems, for example.

Your description of how the routes.rb maps the relationship between models by named routes is clear and intuitive. After my day job I will apply these changes to my project...

Thanks for the help,

SH

@AndyV

Perfect - I got it to work using the named routes. A thing of beauty... Thanks

SH

AndyV,

Great response. One question -- what if Descriptions are not always attached to Items?