Stepan Parunashvili
Stepan Parunashvili

Reputation: 2845

Nested Form complex has_many :through

I have a complex has_many through relationship, where Users can apply to Jobs through Applications.

When creating each job, the admin chooses certain questions. During Applications#new, when the user is applying to a new job, I'd also like the user to answer the questions in order to apply.

To do this, I set up my models like so:

Job
has many users through application
has many questions 

Users
has_many jobs through application 

Application
    belongs_to :user
    belongs_to :job
    has_many :answers

Question
    belongs_to :job
    has_one :answer

Answer
    belongs_to :application
    belongs_to :question

I now have an application controller.

Here's where I am stuck. How do I get the user to have the ability to answer questions on the Applications#new view?

This is what it looks like so far ->

Applications Controller

class ApplicationsController < ApplicationController

 def new 
    @application = Application.new() 
    if params[:job_id] # Applications will only be built by clicking on a specific job, which transfers the id of that job 
      @application.job_id = params[:job_id]
      @application.user_id = current_user.id
    else
      redirect_to root_url, :notice => "Something went wrong. Please contact us and mention this"
    end
    @job = Job.find(@application.job_id)
    @user = current_user

 end


end

The controller essentially sets it up so that when the users clicks submit, the application will has the user id and the job id, effectively making the connection

Applications#new view

<%= form @application do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <% @job.questions.each do |question| %>
  <h4><%= question.content %></h4>
     #What do I do here to allow the user to answer the question?
  <% end %>
   <%= f.submit "Submit the application", class: "button" %>
<% end %>

The Application Model

# == Schema Information
#
# Table name: applications
#
#  id         :integer          not null, primary key
#  user_id    :integer
#  job_id     :integer
#  created_at :datetime
#  updated_at :datetime
#

class Application < ActiveRecord::Base
    belongs_to :job
    belongs_to :user
    validates :job_id, presence: true 
    validates :user_id, presence: true 
    has_many :answers
    accepts_nested_attributes_for :answers, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
end

Upvotes: 1

Views: 249

Answers (1)

Richard Peck
Richard Peck

Reputation: 76774

Tough question....

This might not work (specifically the associations & the save params), but I spent some time thinking about it, so hopefully will point you in the right direction:


Structure

I would create your associations as follows:

#app/models/job.rb
Class Job < ActiveRecord::Base
    has_many :applications
    has_many :questions
end

#app/models/application.rb
Class Application < ActiveRecord::Base
    belongs_to :user
    belongs_to :job

    has_many :questions, class_name: "Question"
    has_many :answers, class_name: "ApplicationAnswer"

    accepts_nested_attributes_for :answers
end

#app/models/question.rb
Class Question < ActiveRecord::Base
    belongs_to :job
end

#app/models/application_answer.rb
Class ApplicationAnswer < ActiveRecord::Base
    belongs_to :application
    belongs_to :question
end

Schemas

jobs
id | name | info | created_at | updated_at

applications
id | user_id | job_id | created_at | updated_at

questions
id | job_id | created_at | updated_at

answers
id | application_id | question_id | answer_info | created_at | updated_at 

Routes

I would create nested routes so that you can only create an application for a specific job:

#config/routes.rb
resources :jobs do 
     resources :applications
end

Controller

#app/controllers/applications_controller.rb
def new
    @job = Job.find(params[:job_id])

    @application = Application.new
    @job.questions.count.times do { @application.answers.build } #-> gives you fields equivalent for number of questions per job 
end

Form

Using accepts_nested_attributes_for, you'll be able to create the forms as follows (upgraded using this idea):

#app/views/applications/new.html.erb
<%= form_for @application do |f| %>
    <% @job.questions.each_with_index do |question| %>
         <%= f.fields_for :answers, question do |question_field| %>
              <%= question_field.label_for :answer, question.text %>
              <%= question_field.text_field :answer %>
              <%= question_field.hidden_field :question_id, question.id %>
         <% end %>
    <% end %>
<% end %>

Saving

#app/controllers/applications_controller.rb
def create
    @application = Application.new(application_params)
    @application.save
end

private
def application_params
    params.require(:application).permit(:job_id, :user_id, answers_attributes:[:answer, :question_id]).merge(user_id: current_user.id)
end

I think your issue is as much a system structure problem as a functional issue. To understand the nested model submission process, you'd benefit from watching this Railscast

Upvotes: 1

Related Questions