Search code examples
ruby-on-railssimple-formruby-on-rails-5

Is it possible to create two objects from two forms in 1 view and have them related?


I know the title sounded a bit wonky, but this is what I am trying to do.

I have two models - Job and Company. Someone can create a Job listing, but before the listing is saved, it should be associated with a company. This company can be newly created, or should be populated from companies the current_user has previously created.

A job belongs_to :company, and a company has_many :jobs.

I know I could do two different views, and just send the user to two different actions on two different controllers, but I would like to simplify it and just have everything done in one view.

When they go to /jobs/new, they should see the normal jobs/_form.html.erb partial, but I would love to be able to show either a Company dropdown for existing companies or new company creation fields that the user fills out and that new company gets associated with this new job that is being created.

This is what my jobs/_form.html.erb partial looks like:

<%= simple_form_for(@job) do |f| %>
    <%= f.input_field :title %>
    <%= f.input_field :salary %>
    <%= f.input_field :description, as: :text %>
    <%= f.input_field :apply_description, as: :text %>
    <%= f.input_field :language %>
    <%= f.input :premium, as: :boolean, inline_label: true, label: "Make this listing stand out",  class: "form-control" %>
    <%= f.button :submit, class: "btn btn-lg btn-primary pull-right" %>
<% end %>

This is what my companies/_form.html.erb looks like:

<%= simple_form_for(@company) do |f| %>
    <%= f.input :name %>
    <%= f.input :logo %>
    <%= f.input :description %>
    <%= f.input :city %>
    <%= f.input :state %>
    <%= f.input :country %>
    <%= f.input :email %>
    <%= f.button :submit %>
<% end %>

How can I combine them into 1 form, or some other unified workflow within 1 view where it works seamlessly to the user?

Edit 1

Based on Jay-Ar's answer, this is what is happening now.

When I select New Company in the Company Dropdown, it doesn't show the fields in the <fieldset>. I believe that's the case because there is no value=0 in the select tags rendered in the HTML, as can be seen in the screenshot below.

enter image description here

Edit 2

After attempting the latest update from Jay-Ar, the JS still doesn't work and the form is no longer hidden.

This is what it looks like on first load, and always:

enter image description here

Ideally I would like for this form not to show up until they have chosen "New Company" from the dropdown.

This is what the HTML looks like now:

enter image description here

The JS does appear in the source, so I know it is being loaded in the asset pipeline correctly.


Solution

  • UPDATED & TESTED WORKING

    • Now supports Turbolinks

    views/jobs/_form.html.erb

    <%= simple_form_for(@job) do |f| %>
      <%# IMPORTANT: use `include_blank` below instead of `prompt` because prompt does not seem to work when updating, but only works when creating %>
      <%= f.association :company, collection: [['New Company', nil]] + Company.pluck(:name, :id), include_blank: 'Please Select Company', input_html: { id: 'company-select' } %>
      <fieldset id='job-fields'>
        <%= f.simple_fields_for :company, @job.build_company do |ff| %>
          <%= ff.input :name %>
          <%= ff.input :logo %>
          <%= ff.input :description %>
          <%= ff.input :city %>
          <%= ff.input :state %>
          <%= ff.input :country %>
          <%= ff.input :email %>
        <% end %>
      </fieldset>
      <%= f.input_field :title %>
      <%= f.input_field :salary %>
      <%= f.input_field :description, as: :text %>
      <%= f.input_field :apply_description, as: :text %>
      <%= f.input_field :language %>
      <%= f.input :premium, as: :boolean, inline_label: true, label: "Make this listing stand out",  class: "form-control" %>
      <%= f.button :submit, class: "btn btn-lg btn-primary pull-right" %>
    <% end %>
    

    JS

    // for Rails 5, use turbolinks:load instead of page:change below
    $(document).on('page:change', function(){
      var companySelect = $('#company-select');
      var jobFields = $('#job-fields');
    
      companySelect.change(function(){
        // if selected option is the second option
        if ($(this).find('option:selected').index() == 1)
          jobFields.show().find(':input').prop('disabled', false);
        else
          jobFields.hide().find(':input').prop('disabled', true);
      })
      // call change immediately so this still works when already updating and not just creating.
      companySelect.change();
    })
    

    controllers/jobs_controller.rb

    class JobsController < ApplicationController
      ...
      def create
        @job = Job.new(job_params)
        ...
      end
    
      def update
        @job = Job.find(params[:id]) # not needed if using before_action #set_job
        if @job.update(job_params)
        ...
      end
    
      private
    
      def job_params
        params.require(:job).permit(:id, :title, :salary, :description, :apply_description, :language, :premium, :company_id, company_attributes: [:name, :logo, :description, :city, :state, :country, :email]
      end
    end
    

    models/job.rb

    class Job < ActiveRecord::Base
      accepts_nested_attributes_for :company
      validates :company, presence: true
      ...
    end
    

    You should get something like the following:

    • On fresh load:

    fresh page load

    • After selecting 'New Company' option':

    after selecting 'New Company' option