Rails nested resources MVC complete example
This post provides references to the code changes that need to be made to a scaffolded rails app in order to render a form for the resources. In the application I am providing the example from those resources are User and Form, where a form is rendered for a user. This is a survey rendering application, similar to TypeForm or SurveyMonkey.
This post will use the form#edit
action as an example but this can be applied to any of the resources.
Generating the scaffold
For those that want to code along with the post, you can start by creating a new rails application and generating the following two scaffolds:rails g scaffold Form
rails g scaffold User
This will create all of the resources needed to create and render forms and users, but we will make changes to the generated files to account for forms being nested within users.
Creating the tables and ActiveRecord relationships
To establish the relationships between the Form and User I added a reference to the users table in the form create_table
. Adding the reference requires the users table to exist before running the migration:
~/db/migrate/20211111221212_create_forms.rb
class CreateForms < ActiveRecord::Migration[6.1]
def change
create_table :forms do |t|
t.references :user, index: true, null: false
t.string :name
t.timestamps
end
end
end
In the Form
and User
models add the belongs_to
and has_many
associations appropriately:
~/app/models/form.rb
class Form < ApplicationRecord
belongs_to :user
end
~/app/models/user.rb
class User < ApplicationRecord
has_many :forms
end
Update routes to be nested
Update the routes file so :forms
are scoped under :users
because in this system a Form
will only exist under the context of a User
. This will also update the url helpers rails providers, which we will need to update references to later in the post.
~/config/routes.rb
resources :users do
resources :forms
end
Run the rails routes
command to see the changes to the routes definitions. For our edit example the new url is:
/users/:user_id/forms/:id/edit
Updating FormsController#edit
In the FormsController
an update needs to be made to get form for a specific user. Since the route to access the edit
action is /users/:user_id/forms/:id/edit
we now have the user_id
and id
(from_id) available to us as parameters to query for data.
In the edit
example below we query for the user first and then filter the forms to find the correct resource. Setting the @form
and @user
variables will give us access to them in the view template:
class FormsController < ApplicationController
before_action :set_user, only: %i[ edit ]
def edit
@form = @user.forms.find(params[:id].to_i)
end
private
def set_user
@user = User.find(params[:user_id])
end
end
Updating views for FormsController#edit
When generating the scaffold for Form
you an edit template was created for you that looks similar to this:
~/app/views/forms/edit.html.erb
<h1>Editing Form</h1>
<%= render 'form', form: @form %>
<%= link_to 'Show', @form %> |
<%= link_to 'Back', forms_path %>
Since the forms were updated to be nested, we will need to account for two changes:
- The new url path helpers
- The added data needed for the form to render
After the updates the new edit.html.erb
will look like the below code example. The links were updated with the correct url paths and the data we provide to the form partial is accounted for.
<h1>Editing Form</h1>
<%= render 'form', form_and_user: [@user, @form] %>
<%= link_to 'Show', user_form_path(@form) %> |
<%= link_to 'Back', user_forms_path %>
Finally update the references in the form partially to account for the changed parameter name as the example below illustrates:
Orginal form partial:
~/app/views/forms/_form.html.erb
<%= form_with(model: form) do |form| %>
# form elements
<% end %>
Updated form partial:
<%= form_with(model: form_and_user) do |form| %>
# form elements
<% end %>
After making these updates you should be able to navigate to http://localhost:3000/users/1/forms/1/edit
successfully.
Thanks for tuning in! I hope you found this post helpful today.