How to? Wire a join relation create

I have the following models:

Entity has_many :locations

Location belongs_to :entity belongs_to :site

Site has_many :locations

I want to create a location and its related site in the locations/new.html.erb view.

I have this in the controller:

  def create     @entity = Entity.find params[:entity_id]     @location = @entity.build_location(params[:location])     @site = @location.build_site

  def update     @entity = Entity.find params[:entity_id]     @location = Location.find(params[:id])     @site = @location.site

The new form contains this:

<% form_for(@location) do |f| %>

  <%=       hidden_field @location, :entity_id       hidden_field @location, :site_id   -%>

  <p>     <b>Location type</b><br />       <%= f.select :location_type,         [           ['POST - Postal or Main', 'POST'],           ['DELV - Delivery', 'DELV'],           ['SHIP - Shipping', 'SHIP'],           ['OTHR - Other', 'OTHR'],                 ],               :size => 4,               :prompt => 'Primary use'       -%>   </p>

  <p>     <b>Location description</b><br />     <%= f.text_field :location_description, :size => 40 %>   </p>

  <!--   <p>     <b>Site</b><br />     <%#= f.text_field :site_id, :size => 8 %>   </p>   -->

  <%= render :partial => 'sites/site_detail',               :object => @location.site -%>

...

The problem is in the parameters passed back on the create. What is being sent back to the locations_controller is:

{"#<Location:0x4953d10>"=>{"site_id"=>""}, "commit"=>"Create", "authenticity_token"=>"547f4ee57048fb38fc253ce809bb7c7e4405b546", "location"=>{"location_description"=>" test", "location_type"=>"POST"}, "nil_class"=>{"site_postal_code"=>"A1A Z9Z", "site_municipality"=>"Toronto", "site_street_number"=>"9", "site_region"=>"Ontario", "site_name"=>"test site", "site_country_code"=>"CA", "site_building_floor"=>"1", "site_street_name"=>"test", "site_building_name"=>"Test, Test and Test Building", "site_building_unit_number"=>""}}

So, I have a nil class where I expect @location.site and entity_id is completely absent. I am evidently wrecking something in the way I have set up my form and I would great appreciate it if anyone could tell me what I am doing wrong and how to fix it.

if i´m not totallz wrong:

in controller:

def new   @location = Locaton.new end

Your mistake is right here: The first parameter here is supposed to be the name of an instance variable, whereas your passing an actual instance. So at the very least you would want hidden_field 'location', :entity_id (or since you're using form_for, that might as well be f.location :entity_id. The second thing is that if you do that, the entity_id will be available in params[:location] [:entity_id]. If that's not what you want, have a look at hidden_field_tag

Fred

Frederick Cheung wrote:

Your mistake is right here: The first parameter here is supposed to be the name of an instance variable, whereas your passing an actual instance. So at the very least you would want hidden_field 'location', :entity_id (or since you're using form_for, that might as well be f.location :entity_id. The second thing is that if you do that, the entity_id will be available in params[:location] [:entity_id]. If that's not what you want, have a look at hidden_field_tag

Fred

Thanks Fred.

This is what I have now:

locations/new.html.erb   <%=       hidden_field :entity, :entity_id, :value => "#{@location.entity_id}"   -%>

locations_controller.rb ...   # A violation of all that is wholesome and pure, but...   before_filter :load_entity ...   private

  def load_entity     @entity = Entity.find params[:entity_id]   end

Now in the params I am seeing:

"commit"=>"Create", "entity"=>{"entity_id"=>"1"}, "authenticity_token"=>"b702e00b80678bf1e8e8e7c2daaa828ea5c5da22", "location"=>{"location_description"=>" None", "location_type"=>"POST"}, ...

Which is good, since I now have an entity_id with the right value in the hash. But on POST create I am still triggering this error:

Couldn't find Entity without an ID

and the traceback points to the find call in the load_entity method.

What else am I missing?

locations/new.html.erb <%= hidden_field :entity, :entity_id, :value => "#...@location.entity_id}" -%>

locations_controller.rb ... # A violation of all that is wholesome and pure, but... before_filter :load_entity ... private

def load_entity @entity = Entity.find params[:entity_id] end

Now in the params I am seeing:

"commit"=>"Create", "entity"=>{"entity_id"=>"1"}, "authenticity_token"=>"b702e00b80678bf1e8e8e7c2daaa828ea5c5da22", "location"=>{"location_description"=>" None", "location_type"=>"POST"}, ...

The answer is staring you in the face :slight_smile: Your params hash is { :entity => {:entity_id => 1}}, and you're accessing params[:entity_id]. Either change your hidden_field into a hidden_field_tag (in which case the parameter won't be 2 levels down like it currently is) or look for params[:entity][:entity_id]

Fred

Frederick Cheung wrote: ..

The answer is staring you in the face :slight_smile: Your params hash is { :entity => {:entity_id => 1}}, and you're accessing params[:entity_id]. Either change your hidden_field into a hidden_field_tag (in which case the parameter won't be 2 levels down like it currently is) or look for params[:entity][:entity_id]

Fred

Ahh. Things are becoming a little less murky. Thank you.

Progress. I am now failing on the insert. No doubt because I still do not grasp the details.

Here is my controller code:

  # POST /locations   # POST /locations.xml   def create     @location = @entity.locations.build(params[:location])     @site = @location.build_site(params[:site]) ...

  # PUT /locations/1   # PUT /locations/1.xml   def update    # Already have @entity from the before_filter    @location = Location.find(params[:id])    @site = @location.site ...

and here is the params hash.

{"entity_id"=>"1", "commit"=>"Create", "authenticity_token"=>"b702e00b80678bf1e8e8e7c2daaa828ea5c5da22", "location"=>{"location_description"=>" None", "location_type"=>"POST"}, "nil_class"=>{"site_postal_code"=>"A1A 9Z9", "site_municipality"=>"Hamilton", "site_street_number"=>"9", "site_region"=>"Ontario", "site_name"=>"Head Office", "site_country_code"=>"CA", "site_building_floor"=>"1", "site_street_name"=>"Brockley", "site_building_name"=>"Byrne & Lyne Building", "site_building_unit_number"=>""}}

locations/new.html.erb contains this partial for site:

  <%= render :partial => 'sites/site_detail',               :object => @location.site -%>

and this invokes _site_detail.rb containing:

<%- fields_for(site_detail) do |ff| -%>

  <p>     <%= label site_detail.class.to_s.downcase,               :site_name,               "<b>Site's Common Name: </b><br />",               :title => "for example: head office, general delivery, etc.",               :class => :input_box -%>

              <%= ff.text_field :site_name, :size => 40 %>   </p> ...

What I want for this iteration is to simply create a new site with the data entered on locations/new. I infer that I either am failing to create a site object or failing to pass it properly to the the partial and that is why am I am getting a nil.class in the params. Any suggestions as to what I am doing wrong here?

<%= render :partial => 'sites/site_detail', :object => @location.site -%>

and this invokes _site_detail.rb containing:

<%- fields_for(site_detail) do |ff| -%>

When you do this, rails looks at site detail to see what class it is, so if it was an instance of Site then it would know that all these params should go into params[:site]. However in the new/create case I'm guessing your @location is just Location.new and so @location.site is nil (and thus rails' guesswork doesn't work). If you were to ensure that @location.site wasn't nil in the create action (eg do @location.build_site) that would probably do the trick

Fred

Frederick Cheung wrote:

<%- fields_for(site_detail) do |ff| -%>

When you do this, rails looks at site detail to see what class it is, so if it was an instance of Site then it would know that all these params should go into params[:site]. However in the new/create case I'm guessing your @location is just Location.new and so @location.site is nil (and thus rails' guesswork doesn't work). If you were to ensure that @location.site wasn't nil in the create action (eg do @location.build_site) that would probably do the trick

Fred

Based on your suggestion I did this in locations_controller.rb

  def new     @location = @entity.locations.build     @site = @location.build_site # <== added this

And everything worked! Well, almost.

At the moment the data gets added to the DB table (sites and locations) as required but I get a failure on the show action following the successful update (this was originally failing in the show view but I moved it back to the controller by adding line 24):

In the locations_controller

22 def show 23 @location = @entity.locations(params[:id]) 24 @site = @location.site

The error:

undefined method `site' for #<Class:0x4a21bc0>

Now, in the console, if I do this:

@entity = Entity.new

...

@location = @entity.locations.build

...

print @location.methods.sort.to_yaml

Then, among other things, I see this:

- silence_stream - silence_warnings - singleton_methods - site - site= - site_id - site_id= - site_id?

So, why is @location.site undefined in the controller?

P.S. Fred, your help to date is greatly appreciated.

The clue should be in the grammar here: @entity.locations returns an array of locations, not the location with a specified id (no error is thrown because you can pass a parameter to indicate whether or not to force a reload of the association. If you are just trying to make sure that a location belonging to the current entity is being retrieved (ie people can't play around with ids too much) you can do @location = @entity.locations.find params[:id]

Fred

James Byrne wrote:

From the API I understand that the link_to method takes the following:

link_to("label",model object or options hash, html options hash)

If a model object is provided then ActionView will attempt to infer the linkage from the named routes but thsat this will not work for nested routes, per the note on the url_for method.

From this I infer that, given the default:

  <%= link_to 'Show', location %>

If I want to pass additional params like entity_id then I must do something like:

  <%= link_to 'Show',       { :action => "show", :controller => "locations",         :id => location.id, :entity_id => location.entity_id       }   %>

Is this correct?

James Byrne wrote:

If I want to pass additional params like entity_id then I must do something like:

  <%= link_to 'Show',       { :action => "show", :controller => "locations",         :id => location.id, :entity_id => location.entity_id       }   %>

Is this correct?

Evidently, yes.