0

I heard about this community while listening to Hypercritical and I am excited to join in with my first question. I am working on my first rails App and I have run into an issue that I cannot seem to crack. I have been watching Railscast, Lynda.com, and Googling for days but I still cannot comprehend how to create a form that that will update my has_many :through associations at once. Allow me to try an explain what I am doing.

My Goal: The firm I work for provides many "Service Offerings" and I want to be able to create a new service offering on one page and have it create the contacts and other information that is associated with it. The additional information such as "contacts" will live in their own tables because they may need to be referenced by many "Service Offerings."

Problem: When I submit the form the "Service Offering" fields submit and are entered into the database, but the fields for the "Business Developer" do not. Obviously, I would like everything to be entered into its appropriate table and the for the IDs to be linked in the join table. I would really appreciate any insight that you could provide.

What I Have So Far: What you see below is Service Offerings and Business Developers. Eventually I will be adding Contacts, Photos, and Files but I thought I would start simply and work my way up.

Models:

class ServiceOffering < ActiveRecord::Base
 attr_accessible :name, :description
 has_many :business_developer_service_offerings
 has_many :business_developers, :through => :business_developer_service_offerings
 accepts_nested_attributes_for :business_developer_service_offerings
end

class BusinessDeveloper < ActiveRecord::Base
 attr_accessible :first_name, :last_name
 has_many :business_developer_service_offerings
 has_many :service_offerings, :through => :business_developer_service_offerings
end

class BusinessDeveloperServiceOffering < ActiveRecord::Base
 belongs_to :business_developer
 belongs_to :service_offering
end

Controller:

def new
 @service_offering = ServiceOffering.new
 @service_offering.business_developers.build
end

def create
 @service_offering = ServiceOffering.new(params[:service_offering])
 if @service_offering.save
  redirect_to(:action => 'list')
 else
  render('new')
 end
end

View:

<%= form_for((@service_offering), :url => {:action => 'create'}) do |f|%>
    <p>
    <%= f.label :name%>             
    <%= f.text_field :name %>

    <%= f.label :description%>  
    <%= f.text_field :description %>
    </p>

<%= f.fields_for :business_developer do |builder| %>
    <p>
    <%= builder.label :first_name%>
    <%= builder.text_field :first_name %>

    <%= builder.label :last_name%>
    <%= builder.text_field :last_name %>
    </p>
<%end%>
    <%= f.submit "Submit" %>
<%end%>

2 Answers2

2

I figured it out. It turns out a few things were wrong and needed to be changed in addition to the two suggestions @Delba made.

The Form:

I took a look at RailsCasts #196 again and noticed that my form looked different than the one used there, so I tried to match it up:

    <%= form_for @service_offering do |f|%>
     <p>
      <%= f.label :name%>             
      <%= f.text_field :name %>

      <%= f.label :description %>  
      <%= f.text_field :description %>
     </p>
    <%= f.fields_for :business_developers do |builder| %>
     <p>
      <%= builder.label :first_name %>
      <%= builder.text_field :first_name %>

      <%= builder.label :last_name %>
      <%= builder.text_field :last_name %>
     </p>
<%end%>
    <%= f.submit "Submit" %>
<%end%>

Initially, this presented an error:

undefined method `service_offerings_path'

Routes:

This lead me to learn about RESTful Routes because I was using the old routing style:

match ':controller(/:action(/:id(.:format)))'

So I updated my routes to the new RESTful Routes style:

  get "service_offerings/list"
  resource :service_offerings
  resource :business_developers

attr_accessible:

That got the form visible but it was still not working. So I did some searching around on this site and found this post that talked about adding "something_attributes" to your parent objects model under attr_accessible. So I did:

class ServiceOffering < ActiveRecord::Base 
 has_many :business_developer_service_offerings
 has_many :business_developers, :through => :business_developer_service_offerings
 accepts_nested_attributes_for :business_developers
 attr_accessible :name, :description, :business_developers_attributes  
end

That change along with @Delba's suggestion shown in the above model and controller listed below solved it.

def new
 @service_offering = ServiceOffering.new
 @business_developer = @service_offering.business_developers.build
end 
Community
  • 1
  • 1
  • I updated my answer with additional explanations. You should read that, it's for sure clearer: http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for – Damien Nov 23 '11 at 16:45
0

You just forgot to assign @business_developper.

def new
  @service_offering = ServiceOffering.new
  @business_developper = @service_offering.business_developpers.build
end

-

@business_developer = @service_offering.business_developers.build

initializes an instance of biz_dev which is then available in the view. fields_for :biz_dev isn't really tied to this instance but to the many-to-many relationship btw serv_off and biz_dev.

In this way, you can add multiple input for additional biz_dev if you initialize another biz_dev instance in your controller. For instance:

5.times { @service_offering.biz_dev.build }

will add additional fields in your form without you having to declare them in your view.

I hope it helped.

Damien
  • 26,933
  • 7
  • 39
  • 40
  • Hi @Delba. Thank you for your response. I made the change you suggested and it sort of worked once, but the values of Business Developer were NULL. Strangely, now it does not even create a Business Developer and is the same as it was. The console says "WARNING: Can't mass-assign protected attributes: business_developer". I think this has something to do with the attr_accessible but I am not sure what. Do you have any ideas? – Zack Stager Nov 22 '11 at 20:37
  • My bad. I didn't look too deep at your code. Write accepts_nested_attributes_for :business_developers (and not :business_developer_service_offerings). Keep me in touch – Damien Nov 22 '11 at 23:43
  • I explain the solution I found in the answer below. However, could you explain how the "@business_developer" in "@business_developer = @service_offering.business_developers.build" is used in the view? Is it the same thing as " :business_developer" in the "fields_for"? If so, why does "@business_developer" not work in the view instead of " :business_developer"? Thank you! – Zack Stager Nov 23 '11 at 15:35