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
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.profileis nil first. If its nil, we will create one usingcreate_profilebefore we proceed to the editing form, otherwise we will select the old one and edit it.
create_profileis acreate_modelmethod generated by rails for models withhas_onerelationship, 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 normalcreatemethod in one-to-many. Its counterpartbuild_modelcreates 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.
/edit nested user profile.png)
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>
/show user profile.png)
Update your profile and check if your changes are applied in personal information page