How do I use accepts_nested_attributes_for? - ruby-on-rails

Editing my question for conciseness and to update what I've done:
How do I model having multiple Addresses for a Company and assign a single Address to a Contact, and be able to assign them when creating or editing a Contact?
I want to use nested attributes to be able to add an address at the time of creating a new contact. That address exists as its own model because I may want the option to drop-down from existing addresses rather than entering from scratch.
I can't seem to get it to work. I get a undefined method `build' for nil:NilClass error
Here is my model for Contacts:
class Contact < ActiveRecord::Base
attr_accessible :first_name, :last_name, :title, :phone, :fax, :email, :company,
:date_entered, :campaign_id, :company_name, :address_id, :address_attributes
belongs_to :company
belongs_to :address
accepts_nested_attributes_for :address
end
Here is my model for Address:
class Address < ActiveRecord::Base
attr_accessible :street1, :street2, :city, :state, :zip
has_many :contacts
end
I would like, when creating an new contact, access all the Addresses that belong to the other Contacts that belong to the Company. So here is how I represent Company:
class Company < ActiveRecord::Base
attr_accessible :name, :phone, :addresses
has_many :contacts
has_many :addresses, :through => :contacts
end
Here is how I am trying to create a field in the View for _form for Contact so that, when someone creates a new Contact, they pass the address to the Address model and associate that address to the Contact:
<% f.fields_for :address, #contact.address do |builder| %>
<p>
<%= builder.label :street1, "Street 1" %> </br>
<%= builder.text_field :street1 %>
<p>
<% end %>
When I try to Edit, the field for Street 1 is blank. And I don't know how to display the value from show.html.erb.
At the bottom is my error console -- can't seem to create values in the address table:
My Contacts controller is as follows:
def new
#contact = Contact.new
#contact.address.build # Iundefined method `build' for nil:NilClass
#contact.date_entered = Date.today
#campaigns = Campaign.find(:all, :order => "name")
if params[:campaign_id].blank?
else
#campaign = Campaign.find(params[:campaign_id])
#contact.campaign_id = #campaign.id
end
if params[:company_id].blank?
else
#company = Company.find(params[:company_id])
#contact.company_name = #company.name
end
end
def create
#contact = Contact.new(params[:contact])
if #contact.save
flash[:notice] = "Successfully created contact."
redirect_to #contact
else
render :action => 'new'
end
end
def edit
#contact = Contact.find(params[:id])
#campaigns = Campaign.find(:all, :order => "name")
end
Here is a snippet of my error console:
I am POSTING the attribute, but it is not CREATING in the Address table....
Processing ContactsController#create
(for 127.0.0.1 at 2010-05-12 21:16:17)
[POST] Parameters:
{"commit"=>"Submit",
"authenticity_token"=>"d8/gx0zy0Vgg6ghfcbAYL0YtGjYIUC2b1aG+dDKjuSs=",
"contact"=>{"company_name"=>"Allyforce",
"title"=>"", "campaign_id"=>"2",
"address_attributes"=>{"street1"=>"abc"},
"fax"=>"", "phone"=>"",
"last_name"=>"",
"date_entered"=>"2010-05-12",
"email"=>"", "first_name"=>"abc"}}
Company Load (0.0ms)[0m [0mSELECT
* FROM "companies" WHERE ("companies"."name" = 'Allyforce')
LIMIT 1[0m
Address Create (16.0ms)[0m
[0;1mINSERT INTO "addresses" ("city",
"zip", "created_at", "street1",
"updated_at", "street2", "state")
VALUES(NULL, NULL, '2010-05-13
04:16:18', NULL, '2010-05-13
04:16:18', NULL, NULL)[0m
Contact Create (0.0ms)[0m
[0mINSERT INTO "contacts" ("company",
"created_at", "title", "updated_at",
"campaign_id", "address_id",
"last_name", "phone", "fax",
"company_id", "date_entered",
"first_name", "email") VALUES(NULL,
'2010-05-13 04:16:18', '', '2010-05-13
04:16:18', 2, 2, '', '', '', 5,
'2010-05-12', 'abc', '')[0m

Just a banal question, if you using this in your contact form shouldn't be address in singular?
<% f.fields_for :address, #contact.address do |builder| %>
<p>
<%= builder.label :street1, "Street 1" %> </br>
<%= builder.text_field :street1 %>
<p>
<% end %>
In your action you have also to do
#ycontact.build_address
If you look at the source code your input field should look like.
< input type="text" size="30"
name="contact[address_attributes][street1]"
id="contact_address_attributes_street1">
I think you also have problems with your associations, if you store address_id in contact then
class Contact < ActiveRecord::Base
belongs_to :address
accepts_nested_attributes_for :address
end
class Address < ActiveRecord::Base
attr_accessible :street1
has_many :contacts
end
And i think this is your case because you want to assign an address to multiple contacts.
You can also do in inverse, if you store your contact_id in your address model:
class Contact < ActiveRecord::Base
has_one :address
accepts_nested_attributes_for :address
end
class Address < ActiveRecord::Base
attr_accessible :street1
belongs_to :contact
end
In this case you cant have users with the same address.
Update
In your user show.html.eb you can use
<%= #contact.address.street1 %>
In your company's show.html.erb you can display the addresses like:
<% #company.addresses.each do |address| %>
<%= address.street1 %>
...
<% end %>
On your user edit page the form fields should be the same as at new.html.erb just the form tag should look like:
<% form_for #contact do |form| %>
<%=render :partial=>'fields' %>
<% end %>
The fields partial contains all your fieds togheter with the:
<% f.fields_for :address, #contact.address do |builder| %>
<p>
<%= builder.label :street1, "Street 1" %> </br>
<%= builder.text_field :street1 %>
<p>
<% end %>
This should work.
Update 2
You can access all the addresses which belong to a company with:
#company.addresses
If you have
class Company<Activerecord::Base
has_many :contacts
has_many :addresses, :through=>:contacts
end
About the drafts, or default addresses. You can have different options depending on your needs. I you want the option that a contact could select one of the existing addresses from the database. You can do a select box in your contact form like:
<%= form.select :address_id, options_from_collection_for_select(#company.addresses, 'id', 'street1')%>
Also you can use some fancy javascript to add or remove the address form, in that case if the user chooses one of the existing addresses. In this case you have to remove the address_attributes from the form. I suggest to create a link with javascript code that adds the address_attributes and removes (toggle style).

If each Address belongs to only one Contact, (like a home address), you could do:
class Company < ActiveRecord::Base
has_many :contacts
end
class Contact < ActiveRecord::Base
belongs_to :company
has_one :address
end
class Address < ActiveRecord::Base
belongs_to :contact
end
Of course, you could just move the address columns into the Contact table and get rid of the Address model altogether.
If each Address has multiple contacts (ie, the addresses are company facilities where the contacts work):
class Company < ActiveRecord::Base
has_many :contacts
end
class Contact < ActiveRecord::Base
belongs_to :company
belongs_to :address
end
class Address < ActiveRecord::Base
has_many :contacts
end
You can add a has_many :through association to get company.addresses:
class Company < ActiveRecord::Base
has_many :contacts
has_many :addresses, :through => :contacts
end
In both situations the address components (street, city, state, etc) are columns in the addresses table.
The last part of your question really depends on the data distribution -- if you have only a few addresses, you could simply stick them in a select element with the main address as the default. With more addresses you might need an AJAXified form with autocomplete that search the addresses table.
To get the new contacts/addresses into your tables, you might want to look at nested forms. Railscast 196 is a good introduction.
EDIT -- more about nested attributes
accepts_nested_attributes_for should work with either a has_many or has_one assocation, and should work with multiple levels of nesting. It seems that you want to have a Contact belong to a Company, and an Address to belong to a Contact, in which case:
class Company < ActiveRecord::Base
has_many :contacts
accepts_nested_attributes_for :contact
end
class Contact < ActiveRecord::Base
belongs_to :company
has_one :address # or has_many, if you prefer
end
class Address < ActiveRecord::Base
belongs_to :contact
end
Then in the view use form_for and fields_for as described in the Railscast to get the nested form elements. This can be a bit tricky to get right if your form is unusual. There are excellent examples from ryanb here that cover several typical situations.

Since you want an address to belong both to a company and to a contact, you have two choices. If you aren't going to ever need multiple addresses (which I might recommend against assuming), then you can put a "belongs_to :address" in both Company and Contact. Address would then have both a "has_many :companies" and "has_many :contacts".
The better option that makes more sense is to use polymorphic associations, where the address table would have :addressable_type and :addressable_id columns to link back to either model.
class Company < ActiveRecord::Base
attr_accessible :name, :phone, :addresses
has_many :contacts
accepts_nested_attributes_for :contacts
has_many :addresses, :as => :addressable
accepts_nested_attributes_for :addresses
end
class Contact < ActiveRecord::Base
attr_accessible :first_name, :last_name, :title, :phone, :fax, :email, :company,
:date_entered, :campaign_id, :company_name
belongs_to :company
has_many :addresses, :as => :addressable
accepts_nested_attributes_for :addresses
end
class Address < ActiveRecord::Base
attr_accessible :street1
belongs_to :addressable, :polymorphic => true
end
Now your trouble comes with wanting to have a single association "company.addresses" to get both it's address and those of its contacts. I might recommend staying away from this and using a different approach.
You're going to want to map those addresses in the view to the correct record, so it might be best to separate them:
<% form_for #company do |company_form| %>
<% company_form.fields_for :addresses do |address_form| %>
<%= address_form.text_field :street1 %>
<% end %>
<% company_form.fields_for :contacts do |contact_form| %>
<% contact_form.fields_for :addresses do |contact_address_form| %>
<%= contact_address_form.text_field :street1 %>
<% end %>
<% end %>
<% end %>
Your address table has the polymorphic columns like this:
class CreateAddresses < ActiveRecord::Migration
def self.up
create_table :addresses do |t|
t.string :street1
t.string :addressable_type
t.integer :addressable_id
t.timestamps
end
end
def self.down
drop_table :addresses
end
end
I'm not sure how you could keep all the linking straight if you don't nest the records individually like this.
If you do need to get all the addresses for a company for display purposes, you could use the standard ruby enumerable manipulations like collect to gather them together.
Good luck!

I'd recommend to let the Contact have many addresses. It happens anyways in the wild. If decided for only one, that would be has_one :address
Oh and you must downcase them: has_many :Contacts must be has_many :contacts
In the Address model you reference with (database needs a addressable_id and addressable_type field)
belongs_to :addressable, :polymorphic => true

Related

Validate presence of fields from another model

With the following associations:
class Profile < ActiveRecord::Base
belongs_to :visitor
belongs_to :contact_point
validates :contact_point, presence: true
end
class Visitor < User
has_one :profile, dependent: :destroy
has_one :contact_point, through: :profile
end
class ContactPoint < User
has_many :profiles
has_many :visitors, through: :profiles
end
Each ContactPoint has a email. When the visitor creates her profile using the following form, she needs to determine the profiles contact point using the email address belonging to ContactPoint. The contact point users are already created and the visitors should not be able to update ContactPoint model.
<%= form_for #profile do |f| %>
<%= f.label 'First Name' %>
<%= f.text_field :first_name %>
<%= f.label 'Last Name' %>
<%= f.text_field :last_name %>
<%= fields_for :contact_point, #profile.contact_point do |ff| %>
<%= ff.label 'Contact point email' %>
<%= ff.text_field :email %>
<% end %>
<% end %>
In ProfilesController I am passing parameters to profile model this way:
def create
#profile = Profile.create(profile_params)
end
def profile_params
contact_point = ContactPoint.find_by_email(params[:contact_point][:email])
params.require(:profile).permit(:first_name, :last_name)
.merge(visitor_id: current_user.id, contact_point: contact_point)
end
With the above setup, when there is no ContactPoint with the provided email address, the contact_point variable will set to be nil and the validator can't distinguish whether the contact point email in the filled in from was empty or not.
Now, how can I add a validation to check the presence of this email address in contact_points table and show a custom error message?
You would have to do it yourself in your controller, something like:
def create
#profile = Profile.create(profile_params)
if !#profile.contact_point
if params[:contact_point][:email].present?
#profile.errors.add(:contact_point, 'No contact with found')
else
#profile.errors.add(:contact_point, 'Please provide an email')
end
render :new
end
end
The best would be to use a custom validation that checks if contact_pounts.email is blank? If yes, then return false.
EDIT:
My brain is functioning better now after some sleep. You can do this using rails. This is how I would do it.
class Profile < ActiveRecord::Base
belongs_to :visitor
belongs_to :contact_point
validates :contact_point, presence: true
accepts_nested_attributes_for :contact_point
end
class Visitor < User
has_one :profile, dependent: :destroy
has_one :contact_point, through: :profile
end
class ContactPoint < User
has_many :profiles
has_many :visitors, through: :profiles
validates :email, presence: true
end
What is going on here? We accept nested attributes for the association (ContactPoint) from Profile, so we can pass them through the #profile form you have to the controller. The models will handle the validation and set the error messages accordingly.
Does this makes sense?

Nested Attributes for a Rich Join Table, using simple_form Rails

I want to create a form that has nested attributes, which populates a record within a rich join table. (That created record within the rich join table of course should have the appropriate foreign keys.)
I have yet to find a thorough answer on creating nested form fields on a has_many :through relationship. Please help!
For this example, I have a user form. Within that form, I am also trying to populate a record within the users_pets table (rich join table).
Additional question: are rich join models supposed to be singular or plural? Example: app/models/owners_pets.rb or app/models/owners_pet.rb.
app/models/owner.rb
class Owner < ActiveRecord::Base
has_many :owners_pets, allow_destroy: true
has_many :pets, through: :owners_pets
end
app/models/pet.rb
class Pet < ActiveRecord::Base
has_many :owners_pets, allow_destroy: true
has_many :owners, through: :owners_pets
end
app/models/owners_pets.rb
class OwnersPet < ActiveRecord::Base
belongs_to :owners
belongs_to :pets
end
app/controller/owners.rb
def owner_params
params.require(:owner).permit(:first_name, owners_pets_attributes: [:id, :pet_name, :pet_id])
end
app/views/owners/_form.html.erb
<%= simple_form_for(#owner) do |f| %>
<%= f.input :first_name %>
<%= f.simple_fields_for :owners_pets do |ff|
<%= ff.input :pet_name %>
<% end %>
<div>
<%= f.button :submit %>
</div>
<% end %>
Here is the answer, thanks to a bunch of help from a mentor. It helps me to keep in mind that rich join naming conventions should NOT be pluralized at the very end, just like other non-rich-join models. Ex: book_page.rb NOT books_pages.rb. Even books_page.rb would work (just update your strong params and database table accordingly). The important part is that the entire model must follow the rails convention of the model being singular (no 's' on the end).
Below in the rich join model, I made the decision to name it the completely singular version: owner_pet.rb as opposed to the other version: owners_pet.rb. (Therefore of course, my database table is named: owner_pets)
app/models/owner.rb
class Owner < ActiveRecord::Base
has_many :owner_pets
has_many :pets, through: :owner_pets
accepts_nested_attributes_for :owner_pets, allow_destroy: true
end
app/models/pet.rb
class Pet < ActiveRecord::Base
has_many :owner_pets
has_many :owners, through: :owner_pets
end
app/models/owner_pet.rb
class OwnerPet < ActiveRecord::Base
belongs_to :owner
belongs_to :pet
end
app/controller/owners.rb
def new
#owner = Owner.new
#owner.owner_pets.build
end
private
def owner_params
params.require(:owner).permit(:first_name, owner_pets_attributes: [:_destroy, :id, :pet_name, :pet_id, :owner_id])
end
app/views/owners/_form.html.erb
<%= simple_form_for(#owner) do |f| %>
<%= f.input :first_name %>
<%= f.simple_fields_for :owner_pets do |ff| %>
<%= ff.input :pet_name %>
<%= ff.input :pet_id, collection: Pet.all, label_method: "pet_type" %>
<% end %>
<div>
<%= f.button :submit %>
</div>
<% end %>
Your join table is the problem:
It should be belongs_to :owners belongs_to :pets for the join table to work
Plus the rich join model should be pluralised, as in: owners_pets

Rails multiple Has_one relationship to same model

I am working on a Rails application and currently I have 2 models - Subjects and Lessons.
A Subject has 3 different types of lessons - Lecture, Tutorial and Laboratory. I modelled such that there are 3 has_one to the Lesson model.
Right now, I am trying to create a nested form for subjects and lessons but the lecture, tutorial and laboratory being saved was always the first form that was rendered.
i.e. I have 3 nested forms separately for Lecture, Tutorial and Laboratory but the Lecture, Tutorial and Laboratory that was saved was always the one that was first built. In my codes the lecture was first built so the attributes for tutorial and laboratory would follow the one that I have filled in for my lecture.
I am not sure where I have went wrong or even if having multiple has_one relationship works in this case so any advice would be appreciated.
The related codes are as follows:
The subject model
class Subject < ActiveRecord::Base
has_one :lecture, :class_name => "Lesson"
has_one :laboratory,:class_name => "Lesson"
has_one :tutorial, :class_name => "Lesson"
accepts_nested_attributes_for :lecture
accepts_nested_attributes_for :laboratory
accepts_nested_attributes_for :tutorial
end
The lesson model
class Lesson < ActiveRecord::Base
belongs_to :subject
end
The Subject and lesson nested form
<%= form_for(#subject_list) do |f| %>
<div class="field">
<%= f.label :subject_code %><br />
<%= f.text_field :subject_code %>
</div>
<div>
<%= f.fields_for :lecture do |lecture| %>
<%= render "lecture_fields", :f => lecture %>
<% end %>
</div>
<div>
<%= f.fields_for :tutorial do |tutorial| %>
<%= render "tutorial_fields", :f => tutorial %>
<% end %>
</div>
<div>
<%= f.fields_for :laboratory do |laboratory| %>
<%= render "laboratory_fields", :f => laboratory %>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
The new action in the subject controller
def new
#subject = Subject.new
lecture = #subject.build_lecture
laboratory = #subject.build_laboratory
tutorial = #subject.build_tutorial
respond_to do |format|
format.html # new.html.erb
format.json { render json: #subject }
end
end
I would appreciate if someone could help me out in identifying where I have went wrong. If in the case that I should not be creating such multiple relationships, I would like to have some advice on how could I actually render out 3 forms with a default field indicating the lesson type.
I'm not really sure if that works, but my advise is to use AR inheritance
class Lesson < ActiveRecord::Base
end
class LectureLesson < Lesson
belongs_to :subject
end
class LaboratyLesson < Lesson
belongs_to :subject
end
class TutorialLesson < Lesson
belongs_to :subject
end
class Subject
has_one :lecture_lesson
has_one :laboratory_lesson
has_one :tutorial_lesson
accepts_nested_attributes_for :lecture_lesson
accepts_nested_attributes_for :laboratory_lesson
accepts_nested_attributes_for :tutorial_lesson
end
Migration
class LessonsAndSubjects < ActiveRecord::Migration
def up
remove_column :subjects, :lesson_id
add_column :subjects, :lecture_lesson_id, :integer
add_column :subjects, :laboratory_lesson_id, :integer
add_column :subjects, :tutorial_lesson_id, :integer
add_column :lessons, :type, :string
add_index :subjects, :lecture_lesson_id
add_index :subjects, :laboratory_lesson_id
add_index :subjects, :tutorial_lesson_id
end
def down
remove_column :subjects, :lecture_lesson_id
remove_column :subjects, :laboratory_lesson_id
remove_column :subjects, :tutorial_lesson_id
remove_column :lessons, :type
add_column :subjects, :lesson_id, :integer
end
end
it makes more sense and it may be fix you issue with nested attributes
Actually from the answer from rorra one point is missing, you need to add a polymorphic association for each "children" to not incurr in query problems
class Lesson < ActiveRecord::Base
belongs_to :subject
end
class LectureLesson < Lesson
belongs_to :polymorphic_lecture_lesson, polymorphic: true
end
class Subject
has_one :lesson
has_one :lecture_lesson, as: :polymorphic_lecture_lesson
accepts_nested_attributes_for :lesson
accepts_nested_attributes_for :lecture_lesson
end
in migration you have then to add
add_column :lessons, :polymorphic_lecture_lesson_id, :integer, index: true
add_column :lessons, :polymorphic_lecture_lesson_type, :integer, index: true
Interestingly, I interpreted this question much differently than the other answers seem to have.
If you are looking to have 2 has_one to a single model/table, then one can do the following:
Given a Person has one best pet and one worst pet, which are represented by the same Pet table/model that has an attribute to differentiate between the two...
class Person < ApplicationRecord
has_one :best_pet, -> { where(pet_type: "best") }, class_name: "Pet"
has_one :worst_pet, -> { where(pet_type: "worst") }, class_name: "Pet"
...
end
class Pet < ApplicationRecord
belongs_to :person
validates_presence_of :pet_type
validates_uniqueness_of :pet_type, scope: :person_id
...
end
Now, whether or not this is good database design is up for debate.

How do I include has_many relationships in a Rails form?

I have two models, Artist and User that are connected through a third model, ArtistMembership.
From the edit/new Artist form, I want to be able to edit the role of any User in an existing ArtistMembership relationship for that Artist, delete ArtistMemberships, and add new AtistMembership relationships, which would include a User and :role.
Here's my Artist model:
class Artist < ActiveRecord::Base
has_many :artist_memberships, foreign_key: "artist_id", dependent: :destroy
attr_accessible :bio, :created_at, :email, :location, :name, :updated_at, :website, :pic
accepts_nested_attributes_for :artist_memberships, :allow_destroy => :true
...
end
Here's my User model:
class User < ActiveRecord::Base
...
has_many :artist_memberships, foreign_key: "user_id"
...
end
Here's my ArtistMembership model:
class ArtistMembership < ActiveRecord::Base
belongs_to :artist, class_name: "Artist"
belongs_to :user, class_name: "User"
attr_accessible :artist_id, :created_at, :role, :user_id
end
If I have a _form.hml.erb too, for editing Artists that starts:
<%= form_for #artist do |artist_form| %>
<div class="field">
<%= artist_form.label :name %>
<%= artist_form.text_field :name %>
</div>
..
<div class="actions">
<%= artist_form.submit %>
</div>
<% end %>
how can I create the related ArtistMembership forms for the aforementioned functionality?
May be this is helpful for you, see this field_for
you can use accepts_nested_attributes_for(*attr_names)
Maybe you are looking for this method.
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for
Refer to the "One-to-many" section.
But if I were you, I would rather use the "Nested Resource" technic.
http://guides.rubyonrails.org/routing.html#nested-resources

Has_many accepts_nested_attributes Can't mass assign attribute

I'm having issues with a form in a custom registration for Devise. This form needs to have both User and Business model information in there that I'll be creating both at once on the form. It's important that this can't be a multi-step registration form - I need to have both the User and Business models filled out simultaneously, as part of the requirements.
I have the following models:
user.rb
class User < ActiveRecord::Base
has_many :businessusers, :include => :business
has_many :businesses, :through => :businessusers
accepts_nested_attributes_for :businessusers
attr_accessible :email, :password, :password_confirmation, :remember_me, :name, :role, :businessusers
business.rb
class Business < ActiveRecord::Base
has_many :businessusers
has_many :users, :through => :businessusers
businessuser.rb
class Businessuser < ActiveRecord::Base
belongs_to :business
belongs_to :user
end
businessusers migration
create_table :businessusers do |t|
t.integer :user_id
t.integer :business_id
end
In my registration form, I have the following code:
Business Information
<br>
<%= f.fields_for :businessusers do |business_form| %>
<%= business_form.input :name %>
<%= business_form.input :address_line1 %>
<%= business_form.input :address_line2 %>
<%= business_form.input :city %>
Finally, here is my controller:
custom_registration_controller.rb
def create
#user=User.new(params[:user])
#business = #user.businesses.build(params[:business]) unless params[:business] [:name].blank?
end
EDIT: I've gotten past the mass assign attribute problem, but now I have another one.
Does anyone know where I'm going wrong? Before, I was disabling id on businessusers table. But this gave me an error saying I needed a primary ID on the table. So when I allow the field :id, it makes it so that the form no longer has the business fields on it.
Thanks!

Resources