Skip to main content Link Search Menu Expand Document (external link)

Nested Form - One-to-One

This article will discuss how one-to-one relationship works. We will also walk you through the process of making nested form for models with one-to-one relationship.

Table of Contents

  1. What is One-to-one Relationship?
  2. One-to-one Relationship with Nested Form

What is One-to-one Relationship?

In a one-to-one relationship, one record in a table is associated with one and only one record in another table. For example, each user can have their own profile, this profile displays their personal information. Logically, each user should only have one profile, and that profile should also belong to its respective user only.

One-to-one Relationship with Nested Form

We will use the the example above as our basis for this tutorial. We will create a nested form for a one-to-one relationship between user and profile.

First, we need to create a model for profile. To do this we will run rails g model profile.

 root@0122:/usr/src/app# rails g model profile
      invoke  active_record
      create    db/migrate/xxxxxxxxxxxxxx_create_profiles.rb
      create    app/models/profile.rb

Add the columns in the generated migration.

#db/migrate/xxxxxxxxxxxxxx_create_profiles

class CreateProfiles < ActiveRecord::Migration[7.0]
  def change
    create_table :profiles do |t|
+     t.belongs_to :user
+     t.date :birthday
+     t.string :legal_name
+     t.string :location
+     t.string :education
+     t.string :occupation
+     t.text :bio
+     t.text :specialty
      t.timestamps
    end
  end
end

Now let’s add has_one :profile in User model.

#app/models/user.rb

class User < ApplicationRecord
  #...
  has_many :orders 
+ has_one :profile
  #...
end

Just like in one-to-many relationship, add belongs_to :user in profile model.

#app/models/profile.rb

class Profile < ApplicationRecord
+ belongs_to :user
end

To enable nested forms, add accepts_nested_attributes_for :profile in user model.

#app/models/user.rb

class User < ApplicationRecord
  #...
  enum genre: { client: 0, admin: 1 }

+ accepts_nested_attributes_for :profile
  #...
end

After that, create user_params to filter and set the permitted attributes. Since we put accepts_nested_attributes_for :profile in the User model, we can now permit nested attributes, in this case profile_attributes.

#app/controllers/users_controller.rb

class UsersController < ApplicationController
  #...
  def edit
    @user = current_user
+   @user.create_profile unless @user.profile
  end

  #...

  private

  def user_params
-   params.require(:user).permit(:time_zone)
+   params.require(:user).permit(:time_zone, profile_attributes: [:id, :legal_name, :birthday, :location, :education, :occupation, :bio, :specialty] )
  end
end

A newly created user doesn’t have a profile, and we can’t edit a profile that doesn’t exist yet. Thus, you need to check if @user.profile is nil first. If its nil, we will create one using create_profile before we proceed to the editing form, otherwise we will select the old one and edit it.

create_profile is a create_model method generated by rails for models with has_one relationship, this method creates an instance of a model and saves it to the database. You can also add parameters when using it, just like the normal create method in one-to-many. Its counterpart build_model creates an instance of model but doesn’t save it in database.

Finally, we can create the nested form. We will use fields_for form helper to do this.

<!--app/views/users/edit.html.erb-->

<h2>Edit User</h2>

<%= form_for @user do |f| %>
  <!--...-->
+ <%= f.fields_for :profile do |ff| %>
+   <div class="form-group">
+     <%= ff.label :legal_name %>
+     <%= ff.text_field :legal_name, class: "form-control" %>
+   </div>
+
+   <div class="form-group">
+     <%= ff.label :birthday %>
+     <%= ff.date_field :birthday, class: "form-control" %>
+   </div>
+
+   <div class="form-group">
+     <%= ff.label :location %>
+     <%= ff.text_field :location, class: "form-control" %>
+   </div>
+
+   <div class="form-group">
+     <%= ff.label :education %>
+     <%= ff.text_field :education, class: "form-control" %>
+   </div>
+
+   <div class="form-group">
+     <%= ff.label :occupation %>
+     <%= ff.text_field :occupation, class: "form-control" %>
+   </div>
+
+   <div class="form-group">
+     <%= ff.label :bio %>
+     <%= ff.text_area :bio, class: "form-control" %>
+   </div>
+
+   <div class="form-group">
+     <%= ff.label :specialty %>
+     <%= ff.text_area :specialty, class: "form-control" %>
+   </div>
+  <% end %>
+
  <div class="form-group">
    <%= f.submit "Save", class: "btn btn-primary" %>
  </div>
<% end %>

Since it’s nested, we will actually process the data of both user and profile simultaneously.

The only thing that’s left is adding the show action. Since we will also use the codes in edit action here, we can use before_action instead.

#app/controllers/users_controller.rb

class UsersController < ApplicationController
  before_action :authenticate_user!
+ before_action :find_user

+ def show; end
  
- def edit
-   @user = current_user
-   @user.create_profile unless @user.profile
- end
+ def edit; end

  def update
-   @user = current_user
    if @user.update(user_params)
      flash[:notice] = "updated successfully"
      redirect_to edit_user_path
    else
      render :edit
    end
  end

  private

+ def find_user
+   @user = current_user
+   @user.create_profile unless @user.profile
+ end

  def user_params
    params.require(:user).permit(:time_zone, profile_attributes: [:id, :legal_name, :birthday, :location, :education, :occupation, :bio, :specialty])
  end
end

create the show page in app/views/users and add the codes below.

<!--app/views/users/show.html.erb-->

<h1>Personal Information</h1>

<p>Email: <%= @user.email %></p>
<p>Timezone: <%= @user.time_zone %></p>
<p>Birthday: <%= @user.profile.birthday %></p>
<p>Bio: <%= simple_format @user.profile.bio %></p>

Add the link for personal information in application layout for navigation.

<!--app/views/layouts/application.html.erb-->

<!DOCTYPE html>
<html>
  <!--...-->
  <body>
    <!--...-->
    <%= link_to 'edit_user', edit_user_path if current_user %>
+   <%= link_to 'personal information', user_path if current_user %>
    <!--...-->
  </body>
</html>

Update your profile and check if your changes are applied in personal information page


Back to top

Copyright © 2020-2022 Secure Smarter Service, Inc. This site is powered by KodaCamp.