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