'Rails: How to save attribute for parent User (current_user) when submitting form to create child object?

I have a User model (using Devise) and a Listing model. User has_many :listings and Listing belongs_to :user.

The user has email, password and phone number attributes, but when creating an account, only email and password are required. Phone number can be left blank.

When a logged-in user creates a Listing, I'd like to provide the option for the current user to include his phone number using the same form. The expectation is to store the phone number of the user who is creating the listing.

I've created forms to accept nested attributes for child objects before, but not for parent object like this case. I haven't figured out how to get the form to do this, and whether I need to change something in the controller or models. Any help is appreaciated.

User model

class User < ApplicationRecord
  has_many :listings, dependent: :destroy
end

Listing model

class Listing < ApplicationRecord
    belongs_to :user
end

Listings controller

  def create
    @listing = current_user.listings.build(listing_params)

    respond_to do |format|
      if @listing.save
        format.html { redirect_to listing_url(@listing), notice: "Listing was successfully created." }
        format.json { render :show, status: :created, location: @listing }
      else
        ...
      end
    end
  end

New Listing Form

<%= form_with model: @listing do |f| %>

  <div class="field">
   <%= f.label :title, class: "label required" %>
    <div class="control"> 
      <%= f.text_field :title, autofocus: true, autocomplete: "title", required: true, class: "input" %>
    </div>
  </div>


...


<% end>

Thanks



Solution 1:[1]

For save the phone at the user, you can do something like that in the controller:

def create
    @listing = current_user.listings.build(listing_params)

    user = current_user
    user.phone = params[:phone]

    respond_to do |format|
      if @listing.save && user.save
        format.html { redirect_to listing_url(@listing), notice: "Listing was successfully created." }
        format.json { render :show, status: :created, location: @listing }
      else
        ...
      end
    end
  end

Solution 2:[2]

My solution is a little overcomplicated, but it uses the standard nested attributes and strong params. It will also prepopulate the phone input if the data is available.

Enable nested_attributes on Listing.user. Limit it to update_only since you don't want to be able to create a user in this way.

class Listing < ApplicationRecord
  belongs_to :user
  accepts_nested_attributes_for :user, update_only: true
end

Add the following nested form to the Listing form partial. Note that include_id is set to false so that the User's ID is not exposed in the form.

  <%= f.fields_for :user, include_id: false do |uf| %>
    <div class="field">
      <%= uf.label :phone, class: "label required" %>
      <div class="control">
        <%= uf.text_field :phone, autofocus: true, 
        autocomplete: "title", required: true, class: "input" %>
      </div>
    </div>
  <% end %>

Permit the nested user params in the controller:

  def listing_params
    params.fetch(:listing, {}).permit(:title, user_attributes: [:phone])
  end

Initialize the Listing with a user in the new action of the controller.

  def new
    @listing = Listing.new(user: @current_user)
  end

Finally, in your create action, you'll need to merge the user id into the params, since it wasn't included with the form submission. The user id has to be set both in the nested attributes, and on the new listing record.

  def create
    @listing = Listing.new(
      listing_params.to_h.deep_merge(
        user_id: @current_user.id,
        user_attributes: { id: @current_user.id }
      ))

    ...

  end

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Matteo
Solution 2 TonyArra