Ask A Question

Notifications

You’re not receiving notifications from this thread.

How to create new contacts from nested attributes form

Seth asked in Rails

I'm building my first semi "real world" rails app (an Open House management app for real estate). I am currently stuck on a "accepts nested attributes" problem. Here are the details... (I've provided all code below)

An open house can have many contacts (attendees), and a contact can attend many open houses. So, I have a join model called Signin. When someone "signs in" to an open house via the Open House > Sign In form, I would like a "sign in" instance to be created along with a contact via nested attributes. Note: Since this is a "has many through", the "Sign in" and "contact" models are not the traditional "parent/child" relationship.

When attempting to submit my Signin form, the form error says "Contact must exist" and the log reports "Unpermitted parameter: :contacts". (I do have strong parameter sit, but perhaps incorrectly).

At this point, I just feel like I've tried everything, but I can't get the form to create a Contact as well as a Signin instance.

The relevant models:

class OpenHouse < ApplicationRecord
belongs_to :listing
has_many :signins, inverse_of: :open_house
has_many :contacts, through: :signins

class Signin < ApplicationRecord
belongs_to :open_house
belongs_to :contact
accepts_nested_attributes_for :contact

class Contact < ApplicationRecord
has_many :signins
has_many :open_houses, through: :signins

Routes: (Signins are nested within each open house to allow easy sharing of sign in form)
resources :open_houses do
resources :signins
end
resources :contacts

The Signin Controller:

class SigninsController < ApplicationController
def new
@open_house = OpenHouse.find(params[:open_house_id])
@signin = @open_house.signins.build
end

def create
@open_house = OpenHouse.find(params[:open_house_id])
@signin = @open_house.signins.new(signin_params)
if @signin.save
redirect_to @signin, notice: 'You are now signed in'
else
render :new
end
end

def show
@signin = Signin.find(params[:id])
end

private
def signin_params
params.require(:signin).permit( contacts_attributes: [:name, :email, :phone])
end
end

Signin Form:

<%= form_with model: [ @open_house, @open_house.signins.build ], local: true do |f| %>
<% if @signin.errors.any? %>



    <% @signin.errors.full_messages.each do |msg| %>
  • <%= msg %>

  • <% end %>


<% end %>
<%= f.fields_for :contacts do |c| %>

<%= c.label :name %>
<%= c.text_field :name, placeholder: "Your name", class: "form-control" %>


<%= c.label :email %>
<%= c.text_field :email, placeholder: "Your email", class: "form-control" %>


<%= c.label :phone %>
<%= c.text_field :phone, placeholder: "Your phone", class: "form-control" %>

<% end %>
<%= f.submit 'Sign In', class: "btn btn-primary" %>
<% end %>

Reply

You probably just need to add contact_id: [] to the signin_params.

Need to check your migrations as well and make sure you have your associations set correctly.

Suggest reading the Rails Guides on has_many_through
https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association

You should also rename signin to viewings or something else. I would say the vast majority of people reading your code would think that the signin controller is for users signing in and not for what it actually does.

Sorry for the formatting of my post, I’m on a mobile device.

Reply

Thanks Red, I've added the ID back in (mistakenly pulled it out as I was experimenting). I'll definitely reconsider my :signin model name as well. I am now running into an errors that says: Could not find the inverse association for contact (:signin in Contact). I'm wondering if this comes back to an incorrect Inverse_of declaration (Thoughtbot wrote on it here: https://thoughtbot.com/blog/accepts-nested-attributes-for-with-has-many-through). But I just can't seem to get it working.

Reply

Hey Seth,

You most likely don't need to use inverse_of, a correctly setup has_many_though association will give you access to things like Contact.signins or OpenHouses.contacts etc... Try:

Signin Model:
belongs_to :contact
belongs_to :open_house

Contact Model:
has_many :signins
has_many :open_houses, through: signins

OpenHouse Model:
has_many :signins
has_many :contacts, through: signins

Then check your signin migration, and makes sure you have:
t.belongs_to :contact, index: true
t.belongs_to :open_house, index: true

Let me know if you need any more help.
Red

Reply

Thanks Red, I finally found my problem and solved it! You are right, all the inverse_of stuff was unnecessary (I removed). Everything boiled down to my f.fields_for :contact line. I changed that to:

<%= f.fields_for :contact, Contact.new do |c| %>

...and voila, everything worked! I feel like the Contact instantiation should be in the controller though.. it's just that I can't seem to get the Contact instantiated from the :new action with everything I tried. 🤷🏼‍♂️

Reply

Is that the nested_attributes bit? Without seeing your code it's hard to say for sure, but you quite probably don't need to use nested_attributes, they'll just be adding unnecessary complexity.

I'm happy to look at your repo if you add redhendery to it on Github.

Reply

Hey Red, I am running into a new challenge that I thought you would understand well. Picking up the discussion above, now I would like to update my form action to create a new contact (if a contact doesn't exist ... as defined by the email field) or find and update that contact with the provided form details. Through some research, I discovered:

autosave_associated_records_for_

In my join model I now have:

def autosave_associated_records_for_lead
    if new_lead = Lead.find_by_email(lead.email)
       self.lead = new_lead
    else
      new_lead.save!
    end
  end

The problem I'm running into is that this doesn't update the contact, if the contact exists. Any chance you could help me solve that last problem?

Reply

In the signin_params method, you need to use contacts_attributes instead of contacts. Here's the corrected code:
private
def signin_params
params.require(:signin).permit(contacts_attributes: [:name, :email, :phone])
end

In the form, you need to use fields_for with the correct association. Since your Signin model belongs_to :contact, you should use fields_for :contact instead of :contacts. Here's the corrected code:
<%= form_with model: [@open_house, @open_house.signins.build], local: true do |f| %>
<% if @signin.errors.any? %>
<% @signin.errors.full_messages.each do |msg| %>
<%= msg %>
<% end %>
<% end %>

<%= f.fields_for :contact do |c| %>
<%= c.label :name %>
<%= c.text_field :name, placeholder: "Your name", class: "form-control" %>

<%= c.label :email %>
<%= c.text_field :email, placeholder: "Your email", class: "form-control" %>

<%= c.label :phone %>
<%= c.text_field :phone, placeholder: "Your phone", class: "form-control" %>

<% end %>

<%= f.submit 'Sign In', class: "btn btn-primary" %>
<% end %>

Reply
Join the discussion
Create an account Log in

Want to stay up-to-date with Ruby on Rails?

Join 82,329+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.

    Screencast tutorials to help you learn Ruby on Rails, Javascript, Hotwire, Turbo, Stimulus.js, PostgreSQL, MySQL, Ubuntu, and more.

    © 2024 GoRails, LLC. All rights reserved.