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

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

Location description
<%= f.text_field :location_description, :size => 40 %>

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

On Apr 4, 8:15 pm, James B. [email protected]
wrote:

has_many :locations

  hidden_field @location, :entity_id
  hidden_field @location, :site_id

-%>

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

On Apr 5, 8:04 pm, James B. [email protected]
wrote:


“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 C. 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?

Frederick C. 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.

On Apr 5, 9:46 pm, James B. [email protected]
wrote:

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

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

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

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

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?

Frederick C. 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.

Frederick C. wrote:

The error:
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]

Well, recalling that the data associations are:

entity has many locations,
site has many locations and
location belongs to entity and belongs to site

What I am usually attempting to do is to retrieve all the locations for
a given entity and the site related to each location. However,
immediately after the location create is completed I rather think that
the situation is that I already have the location to display (id) and
need to retrieve the associated entity and site. Perhaps I am confused
regarding this.

In any case, one of my recurring difficulties lies how to inject a given
key / value pair into the params hash. In the present case I have the
following code in locations_controller:

private

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

Which works, so long as I pass :entity_id. However, in
views/locations/index called from views/entities/index, I have this
code:

<td><%=h location.entity_id %></td>
<td><%=h location.site_id %></td>
<td><%=h location.location_type %></td>
<td><%=h location.location_description %></td>
<td><%=h location.location_notes %></td>
<td><%= link_to 'Show', location %></td>

Which of course passes :id = location.id in params. I am not sure how
to pass entity_id in the show link_to. I tried various things (
:entity_id => location.entity_id) but they did not pass the entity_id in
the params and I am afraid that the API documentation is just too terse
for me to decipher what form to use.

On Apr 6, 2:13 am, James B. [email protected]
wrote:

In the locations_controller

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

The error:
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 B. 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.

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