0
votes

My application (based on the Hartl Rails 5 tutorial) has a User model that has_many Rooms, and the Room model in turn has_many Students. I have a form for the User to create a Room (with :number attribute) with Students (with :name attribute). Though the User creation form is separate from the Room creation form, I have used the associations code from this example: https://medium.com/@mindovermiles262/triple-nested-forms-in-rails-dedbcccb5799.

When I submit the form it returns the error "Students room can't be blank". Somewhere the association between Room and Student is being lost. Below is a screenshot of the submitted form with error message: Screenshot of Students room can't be blank error

Here is the relevant user.rb code:

class User < ApplicationRecord
has_many        :rooms, dependent: :destroy
has_many        :students, through: :rooms
accepts_nested_attributes_for   :rooms, allow_destroy: true
accepts_nested_attributes_for   :students, allow_destroy: true

...

end

users_controller.rb

    private

def user_params
  params.require(:user).permit(:name, :email, :school, :password,
                               :password_confirmation,
                               :rooms_attributes => [:id, :number, 
                               :students_attributes => [:id, :name]
                               ] )

end

room.rb:

    class Room < ApplicationRecord
  belongs_to                        :user, optional: true
  has_many                          :students, dependent: :destroy, inverse_of:  
                                    :room
  accepts_nested_attributes_for     :students, allow_destroy: true
  default_scope ->                  { order(number: :asc) }
  validates                         :number, presence: true
  validates                         :user_id, presence: true    
end

rooms_controller.rb

    class RoomsController < ApplicationController

before_action :logged_in_user, only: [:create, :destroy, :edit, :update, :show]
before_action :correct_user,   only: [:show]

    def new
        @room = current_user.rooms.build if logged_in?
        @student = @room.students.build
    end

  def create

    @room = current_user.rooms.build(room_params)


    if @room.save && @student.save
      flash[:success] = "New class created."
      redirect_to current_user
    else
      render 'rooms/new'
    end
  end

  def destroy
  end

  def edit
  end

  def update
    @room = Room.find(params[:id])
    if @room.update_attribute(room_params)
        redirect_to @room
        flash[:success] = "Class updated."
    end
  end

  def show
    @user = User.find(params[:id])
    @rooms = @user.rooms
    # @number = @rooms.number
  end

  private

    def room_params
      params.require(:room).permit(:number, :students_attributes => [:id, :name])

    end

end

the current_user method from sessions_helper.rb:

      def current_user
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id])
      user = User.find_by(id: user_id)
      if user && user.authenticated?(:remember, cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
  end

student.rb:

    class Student < ApplicationRecord
  belongs_to        :room, optional: true, inverse_of: :students
  validates         :name, presence: true, length: { maximum: 50 }
  validates         :room_id, presence: true
end

students_controller.rb

    class StudentsController < ApplicationController

    before_action   :logged_in_user, only: [:create, :destroy, :edit, :update, :show]
    before_action   :correct_user,   only: [:show]




    def new
        @room = current_user.rooms.build if logged_in?
        @student = @room.students.build
    end

    def create

        if @student.save
         flash[:success] = "New student created."
        redirect_to current_user

        else

            render 'students/new'
        end
    end

    def show
    end

      private

    def student_params
      params.require(:student).permit(:name)
    end
end

the Room creation form, rooms\new.html.erb:

    <% provide(:title, 'Create a class') %>
    <h1>Create a class</h1>

    <%= form_with(model: @room, local: true) do |f| %>

        <%= render 'shared/error_messages', object: f.object %>

          <%= f.label :number, "Class number" %>
          <%= f.number_field :number, class: 'form-control' %>

           <%= f.fields_for :students do |s| %>
            <%= s.label :name, "Student name" %>
            <%= s.text_field :name, class: 'form-control' %>
          <% end %>


      <%= f.submit "Create class", class: "btn btn-primary" %>
      <%= params.inspect %>
<% end %>

config\routes.rb:

    Rails.application.routes.draw do



  get 'password_resets/new'

  get 'password_resets/edit'

  get 'sessions/new'



      root 'static_pages#home'
    get '/signup',        to: 'users#new'
    post '/signup',       to: 'users#create'
    get    '/login',      to: 'sessions#new'
    post   '/login',      to: 'sessions#create'
    delete '/logout',     to: 'sessions#destroy'
    get '/showall',       to: 'users#index'
    get '/createroom',    to: 'rooms#new'
    post '/createroom',   to: 'rooms#create'
    get 'createstudent',  to: 'students#new'
    get 'createstudent',  to: 'students#create'


    resources :users
    resources :account_activations, only: [:edit]
    resources :password_resets,     only: [:new, :create, :edit, :update]
    resources :rooms
    resources :students

end

and development.log:

Processing by RoomsController#new as 
  Rendering rooms/new.html.erb within layouts/application
  Rendered shared/_error_messages.html.erb (1.0ms)
  Rendered rooms/new.html.erb within layouts/application (388.0ms)
  Rendered layouts/_rails_default.html.erb (244.0ms)
  Rendered layouts/_shim.html.erb (1.0ms)
  Rendered layouts/_header.html.erb (1.0ms)
  Rendered layouts/_footer.html.erb (1.0ms)
Completed 200 OK in 2938ms (Views: 2513.1ms | ActiveRecord: 12.0ms)


Processing by RoomsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"henh7dclciYRcZE6xRFfdSHBPQuRmO03eXXEsmRkSSI25uqkMSo6WFwthixdEzDNSNjY5pPmcIYwi5nmFZirMg==", "room"=>{"number"=>"200", "students_attributes"=>{"0"=>{"name"=>"Roo"}}}, "commit"=>"Create class"}
  Rendering rooms/new.html.erb within layouts/application
  Rendered shared/_error_messages.html.erb (1.0ms)
  Rendered rooms/new.html.erb within layouts/application (35.0ms)
  Rendered layouts/_rails_default.html.erb (237.0ms)
  Rendered layouts/_shim.html.erb (0.0ms)
  Rendered layouts/_header.html.erb (1.0ms)
  Rendered layouts/_footer.html.erb (1.0ms)
Completed 200 OK in 3283ms (Views: 494.7ms | ActiveRecord: 3.0ms)

I added :inverse_of per Rails 5 error message: Child-Model Parent-Model must exist but that made no difference. This (rails 5.1: nested form validation) does not appear relevant, b/c it involves a parent with two different children, not a parent with a child that also has a child.

Thanks in advance for any assistance.

EDIT:

I finally realized that the error message "Students room can't be blank" referred to the room_id being blank. I removed the following line from student.rb:

validates :room_id, presence: true

I had thought that

belongs_to :room, optional: true

would temporarily bypass the validates method, but I was wrong.

1
If this is resolved, please post your solution as an answer and mark it as the correct one, or delete the question altogether. - Tashows
Done. I wasn't sure if I could answer my own question. - Daniel McNaughton
Thanks! That helps people see if the question is answered so they can move on and provide help where needed :) - Tashows

1 Answers

0
votes

Removing

validates :room_id, optional: true

was the solution.