2

Please help me understand where I am going wrong so much so that I receive this message when I try to save my data-entry form(complex form) once I have entered all the data?

I have five models as follows:

class Contract < AR::Base
  has_many :clientlines
  has_many :codelines
  has_many :clients, :through => :clientlines
  has_many :codes, :through => :codelines

  accepts_nested_attributes_for :clientlines
end

class Clientline < AR::Base
  belongs_to :contract
  belongs_to :client

  accepts_nested_attributes_for :contract
end

class Client < AR::Base
  has_many :clientlines
  has_many :contracts, :through => :clientlines
end

class Codeline < AR::Base
  belongs_to :contract
  belongs_to :code
  units_alloc

  accepts_nested_attributes_for :code
end

class Code < AR::Base
  has_many :codelines
  has_many :contracts, :through => :codelines
end

I used the following article as my design source:

http://rubysource.com/complex-rails-forms-with-nested-attributes/

In my app/controller/contracts_controller.rb I have the following:

def new
  @contract = Contract.new
  4.times { @contract.codes.build }
  4.times { @contract.codelines.build }
end

def create
  @contract = Contract.new(params[:contract])
  if @contract.save
    flash[:success] = "New Contract has been saved"
    redirect_to @contract # this redirects to the contract show page
  else
    @title = "You have some errors"
    render 'new'
  end
end
.
.
.
end

I put together the complex form as follows:

- provide(:title, 'Add Contract')
%h2 New Contract
=form_for(@contract) do |f|
  =render 'shared/contract_error_messages', object: f.object
  =render 'fields', f:  f
  .actions
    = f.submit "Save", class: 'save strong round'

the partial _fields:

<fieldset><legend>Enter Contract Details</legend>
.field
  = f.label :name, "AuthNum"
  %br/
  = f.text_field  :authnum, :size => 10, :class => "ui-state-default"
.field
  = f.label :name, "Start Date"
  %br/
  = f.text_field  :st_date, :size => 12, :class => "ui-state-default"
.field
  = f.label :name, "End Date"
  %br/
  = f.text_field  :end_date, :size => 12, :class => "ui-state-default"
</fieldset>
<fieldset><legend>Enter Client Details</legend>
= f.fields_for :clients do |ff|
  .field
    = ff.label :name, "First Name"
    %br/
    = ff.text_field :f_name, :size => 15, :class => "ui-state-default"
  .field
    = ff.label :name, "MI"
    %br/
    = ff.text_field :mi, :size => 3, :class => "ui-state-default"
  .field
     = ff.label :name, "Last Name"
     %br/
     = ff.text_field :l_name, :size => 15, :class => "ui-state-default"
  .field
     = ff.label :name, "Birth Date"
     %br/
     = ff.text_field :birth_date, :size => 12, :class => "ui-state-default"
  .field
     = ff.label :name, "Address1"
     %br/
     = ff.text_field :address1, :size => 25, :class => "ui-state-default"
  .field
     = ff.label :name, "Address2"
     %br/
     = ff.text_field :address2, :size => 25, :class => "ui-state-default"
  .field
     = ff.label :name, "City"
     %br/
     = ff.text_field :city, :size => 15, :class => "ui-state-default"
  .field
     = ff.label :name, "ZipCode"
     %br/
     = ff.text_field :zip_code, :size => 10, :class => "ui-state-default"
  .field
     = ff.label :name, "State"
     %br/
     = ff.text_field :state, :size => 15, :class => "ui-state-default"
  .field
     = ff.label :name, "MedicareNum"
     %br/
     = ff.text_field :medicarenum, :size => 20, :class => "ui-state-default"
  .field
     = ff.label :name, "MedicaidNum"
     %br/
     = ff.text_field :medicaidnum, :size => 20, :class => "ui-state-default"
  .field
     = ff.label :name, "MemberNum"
     %br/
     = ff.text_field :membernum, :size => 20, :class => "ui-state-default"
  .field
     = ff.label :name, "SocSerCareMgr"
     %br/
     = ff.text_field :socsercaremgr, :size => 20, :class => "ui-state-default"
  .field
     = ff.label :name, "SSCM_Ph"
     %br/
     = ff.text_field :sscm_ph, :size => 15, :class => "ui-state-default"
  .field
     = ff.label :name, "NurseCareMgr"
     %br/
     = ff.text_field :nursecaremgr, :size => 20, :class => "ui-state-default"
  .field
     = ff.label :name, "NCM_Ph"
     %br/
     = ff.text_field :ncm_ph, :size => 15, :class => "ui-state-default"
  .field
     = ff.label :name, "EmergencyContact"
     %br/
     = ff.text_field :emergencycontact, :size => 20, :class => "ui-state-default"
  .field
     = ff.label :name, "EC_Ph"
     %br/
     = ff.text_field :ec_ph, :size => 15, :class => "ui-state-default"
  .field
     = ff.label :name, "PrimaryCarePhy"
     %br/
     = ff.text_field :primarycarephy, :size => 20, :class => "ui-state-default"
  .field
     = ff.label :name, "PCPhy_Ph"
     %br/
     = ff.text_field :pcphy_ph, :size => 15, :class => "ui-state-default"
</fieldset>
<fieldset><legend>Enter Billing Code Details</legend>
= f.fields_for :codes do |ff|
  .field
    = ff.label :name, "Code Name"
    %br/
    = ff.text_field :code_name, :size => 15, :class => "ui-state-default"
  .field
    = ff.label :name, "Status"
    %br/
    = ff.text_field :status, :size => 10, :class => "ui-state-default"
  .field
    = ff.label :name, "Description"
    %br/
    = ff.text_field :description, :size => 25, :class => "ui-state-default"
= f.fields_for :codelines do |ff|
  .field
    = ff.label :name, "Units Alloc"
    %br/
    = ff.text_field :units_alloc, :precision => 6, :scale => 2, :size => 10, :class => 
    "ui-state-default"
</fieldset>

My first and immediate problem is that once I have entered all the data on the form and then pressed the 'Save' button I get the following:

ActiveRecord::Association TypeMisMatch in ContractsController#Create. Client(#xxxxxx) expected, got Array(#xxxxxx).

The other problem is that if I include 'accepts_nested_attributes_for :codelines' in my contract model, the 'units_alloc' attribute disappears from my form.

Any help or guidance would be most appreciated on these two questions. I have spent some time reading up on complex forms, watched 'complex forms' railcasts and I have read and re-read Rails Guides on associations as well as the API documentation on accepts_nested_attributes_for method. Obviously my understanding of these concepts have not risen up to the full comprehension needed to solve these issues, thus my call for help.

Update app/controllers/contracts_controller.rb

class ContractsController < ApplicationController

def index
  @contracts = Contract.paginate(page: params[:page])
end

def show
  @contract = Contract.find(params[:id])
end

def new
  @contract = Contract.new
  @contract.codes.build
  @contract.codelines.build
  @contract.clients.build
end

def create
  raise params[:contract].to_s ------ **this is line #19**
  @contract = Contract.new(params[:contract])
  if @contract.save
    flash[:success] = "New Contract has been saved"
    redirect_to @contract # this redirects to the contract show page
  else
    @title = "You have some errors"
    render 'new'
  end
end

def edit
  @contract = Contract.find(param[:id])
end

def update
  if @contract.update_attributes(params[:contract])
    flash[:success] = "Contract Profile updated"
    redirect_to @contract
  else
    render 'edit'
  end
end
end

I added the "raise params[:contract].to_s" as the first line in my create action in the contracts_controller.rb and the output follows:

RuntimeError in ContractsController#create

{"authnum"=>"700900", "st_date"=>"04/03/2012", "end_date"=>"06/29/2012", "clients"=> 
{"f_name"=>"Lefty", "mi"=>"L", "l_name"=>"Right", "birth_date"=>"07/18/1979", 
"address1"=>"54 Frosty Lane", "address2"=>"", "city"=>"Frave", "zip_code"=>"54806",
"state"=>"WI", "medicarenum"=>"789987456", "medicaidnum"=>"931579135", 
"membernum"=>"890333-3", "socsercaremgr"=>"Caring Serving",
"sscm_ph"=>"1-444-444-4444", "nursecaremgr"=>"Caring Nurse", 
"ncm_ph"=>"1-555-555-5555", "emergencycontact"=>"Quick Response", 
"ec_ph"=>"1-666-666-6666", "primarycarephy"=>"This One", "pcphy_ph"=>"1-777-777-7777"},
"codes"=>{"code_name"=>"S-5463", "status"=>"Active", "description"=>"Transition from
sch to mkt"}, "codelines"=>{"units_alloc"=>"80.00"}}

Rails.root: /home/tom/rails_projects/tracking
Application Trace | Framework Trace | Full Trace

app/controllers/contracts_controller.rb:19:in `create'

Request

Parameters:

{"utf8"=>"✓",
"authenticity_token"=>"/i21h2vwzuDPjIrCXzYEIAg41FnMxfGdCQQggjqcZjY=",
"contract"=>{"authnum"=>"700900",
"st_date"=>"04/03/2012",
"end_date"=>"06/29/2012",
"clients"=>{"f_name"=>"Lefty",
"mi"=>"L",
"l_name"=>"Right",
"birth_date"=>"07/18/1979",
"address1"=>"54 Frosty Lane",
"address2"=>"",
"city"=>"Frave",
"zip_code"=>"54806",
"state"=>"WI",
"medicarenum"=>"789987456",
"medicaidnum"=>"931579135",
"membernum"=>"890333-3",
"socsercaremgr"=>"Caring Serving",
"sscm_ph"=>"1-444-444-4444",
"nursecaremgr"=>"Caring Nurse",
"ncm_ph"=>"1-555-555-5555",
"emergencycontact"=>"Quick Response",
"ec_ph"=>"1-666-666-6666",
"primarycarephy"=>"This One",
"pcphy_ph"=>"1-777-777-7777"},
"codes"=>{"code_name"=>"S-5463",
"status"=>"Active",
"description"=>"Transition from sch to mkt"},
"codelines"=>{"units_alloc"=>"80.00"}},
"commit"=>"Save"}

Update 1

I changed my contracts_controller new action to:

def new
  @contract = Contract.new

Build the codelines object through the contract, then build the codes through the codelines object

  codelines = @contract.codelines.build
  codelines.codes.build

Build the clientlines object through the contract, then build the clients through the clientlines object

  clientlines = @contract.clientlines.build
  clientlines.clients.build
end

I also changed my contract model by adding accepts_nested_attributes_for :clientlines, :codelines as well as adding the attr_accessor line.

class Contract < ActiveRecord::Base
  has_many :clientlines
  has_many :codelines
  has_many :clients, :through => :clientlines
  has_many :codes, :through => :codelines

  accepts_nested_attributes_for :clients
  accepts_nested_attributes_for :codes
  accepts_nested_attributes_for :clientlines
  accepts_nested_attributes_for :codelines

  attr_accessor :codes, :clients, :clientlines, :codelines
end

I now have nested_attribute_writers with association names in my params but now my error has changed to:

NoMethodError in ContractsController#new

undefined method `build' for nil:NilClass

The question now is, "Is it correct to have attr_accessor referencing all the associations?" The other question I have is, "Do I have to use a form_helper to create records for client, code, and codeline?" The reason I ask this is that it would seem that despite the build action I have in my contracts_controller it seems that it is still nil. If the answer is yes to the second question can you direct me to some resource that would guide me in building a form_helper? I am checking RailsGuides.

Thanks.

Update 2

I have been going in circles, I changed my contracts_controller new action to:

def new
  @contract = Contract.new

  @contract.codes.build
  @contract.clients.build
end

I also changed my contract model back to:

class Contract < ActiveRecord::Base
  has_many :clientlines
  has_many :codelines
  has_many :clients, :through => :clientlines
  has_many :codes, :through => :codelines

  accepts_nested_attributes_for :clients
  accepts_nested_attributes_for :codes

  attr_accessible :clients_attributes, :codes_attributes etc
end

Lastly I removed the section in my view partial, _fields.html.haml, that pertained to codelines, namely:

= f.fields_for :codelines do |ff| and the next four lines

and now my params has the neccessary "clients_attributes" and "codes_attributes" and as a result the form saves to the appropriate tables. I still have some issues, namely the extra attribute in codeline, 'units_alloc' and some others but things are looking better.

thomasvermaak
  • 302
  • 5
  • 16

2 Answers2

2

Ok, so your problem is with the way that your forms are setup up.

Your contract model can accept nested attributes for clientlines, however, you are trying to accept nested attributes for clients, code.

In order to do this you need to setup your Contract model as follows:

class Contract < AR::Base
  has_many :clientlines
  has_many :codelines
  has_many :clients, :through => :clientlines
  has_many :codes, :through => :codelines

  accepts_nested_attributes_for :codes
  accepts_nested_attributes_for :clients
end

I think I understand your confusion here, since ClientLines is the owner of codes and clients that is the relationship that you should accept nested attributes for, but that is incorrect.

Once you use the has_many :codes, :through => :clientlines you have setup a first class association between Contract and Code, so if you want to create new codes in a contract form, then you need to say accepts_nested_attributes_for :codes in your Contract model.

Hope this helps.

So in regards to your last posting of the params, the problem is the :codes and :clients. If you had the accepts_nested_attributes_for :codes and accepts_nested_attributes_for :clients code in your Contract model, this should work just fine.

Here is the problem, in order to properly build the clients or the codes, you need to also build their parent object. So your controller should look more like this:

class ContractsController < ApplicationController
  def new
    @contract = Contract.new

    #Build the codelines object throught the contract, then build the codes through the codelines object
    codelines =  @contract.codelines.build
    codelines.build_code

    # Do the same for the clients
    clientlines = @contract.clientlines.build
    clientlines.build_client
  end

Now, if you have done this properly, instead of seeing :clients or :codes in your params, you should see :clients_attributes or :codes_attributes.

I really think that should do it.

TheDelChop
  • 7,938
  • 4
  • 48
  • 70
  • Thanks @TheDelChop ... I edited my contract.rb file to reflect the accepts_nested_attribute_for settings you have above. Unfortunately I still get the same error. – thomasvermaak Mar 01 '12 at 21:44
  • Oh, also, your not building any clients in your new action of your controller `@contract.clients.build` – TheDelChop Mar 01 '12 at 21:49
  • Thanks again ... same problem ... it does point to the contract_controller as the source of the error in both the new and create actions, more specifically it points to @contract = Contract.new(params[:contract]) as a source for the error. – thomasvermaak Mar 01 '12 at 21:59
  • ok, in your `ContractsController#create` action do this before `@contract = Contract.new(params[:contract])`, `raise params[:contract].to_s` and put the output in your OP. – TheDelChop Mar 01 '12 at 22:04
  • What is OP?:), Also, I am not sure how output to my OP? My apologies and thanks for your patience. – thomasvermaak Mar 01 '12 at 22:08
  • The 'raise params[:contract].to_s' gives me a listing of all of the arrtributes that I entered on the form ...none of them appear to be incorrect, I do have a different error and it is "RunTime Error in ContractsController#Create". – thomasvermaak Mar 01 '12 at 22:44
  • Please post the output, I need to see the params to determine what the root of your error is. – TheDelChop Mar 01 '12 at 23:37
  • I have added the output of the 'raise params[:contract].to_s' to my original posting. Thanks again for your patience. – thomasvermaak Mar 02 '12 at 15:46
  • Thanks again ... I followed you advice and changed my contracts_controller.rb to reflect your changes in the new action, I then restarted my server and stopped and restarted postgresql and then saved my contract form and unfortunately got the same result. – thomasvermaak Mar 02 '12 at 17:05
  • Well thanks for all your help .... I will do some more research ... obviously I have something wrong with my setup that is not an easy find. Thanks again. – thomasvermaak Mar 02 '12 at 18:46
  • In case you see something in this I thought I would add this comment. I rebooted and my error is now "NoMethodError in ContractsController#new undefined method `codes' for #" and it points to the codelines.codes.build line in my contracts_controller.rb file. – thomasvermaak Mar 02 '12 at 19:40
  • Your welcome, I"m sorry we couldn't get all the way to the bottom of it. – TheDelChop Mar 02 '12 at 19:40
  • See my update one more time then. I though that a `codeline has_many :codes`, but if not, it should be what my update says. – TheDelChop Mar 02 '12 at 19:42
  • No joy ... but thanks again for all your help ... I shall do more research and troubleshoot my associations. – thomasvermaak Mar 02 '12 at 20:19
0

The first answer was a great help, but it did not get me all the way to the solution. I found a solution, that did take me through to the end at this site, debugging nested_forms.

It is the ninth bullet down where you read that if you are working with a has_many :through association then you need to base your nested_form on the join model or something close to that.

I quickly ran a test by refactoring some code here and there and I now have a workable nested form that serves up the correct params to the controller which in turns processes it correctly and my codelines table now has just one record.

thomasvermaak
  • 302
  • 5
  • 16