0

The title might not be very explanatory (I tried, sorry).

The scenario

I have a many-to-many relation, Computer and Software and thus a join model ComputerSoftware. I would like to have the ability to connect a computer with many softwares from the list of softwares currently available while I am creating a new computer. Here's some code to explain:

class Computer < ApplicationRecord
  has_many :computer_softwares
  has_many :computers, through: :computer_softwares

  accepts_nested_attributes_for :softwares
end

class Software < ApplicationRecord
  has_many :computer_softwares
  has_many :softwares, through: :computer_softwares
end

class ComputerSoftware < ApplicationRecord
  belongs_to :software
  belongs_to :computer
end

My controller looks like this:

class ComputersController < ApplicationController
  before_action :set_computer, only: %i[show edit update destroy]
  before_action :set_softwares, only: :new

  def index
    @computers = Computer.all
  end

  def show; end

  def new; end

  def edit; end

  def create
    @computer = Computer.new(computer_params)
    # do something to save the associated softwares...
    # params.require(:computer).permit(:softwares) <= unpermitted param

    if @computer.save
      redirect_to computers_path, notice: 'Successfully created.'
    else
      render :new
    end
  end

  def update
    if @computer.update(computer_params)
      redirect_to @computer, notice: 'Successfully updated.'
    else
      render :edit
    end
  end

  def destroy
    @computer.destroy
    redirect_to computers_url, notice: 'Successfully destroyed.'
  end

  private

  def set_computer
    @computer = Computer.find(params[:id])
  end

  def computer_params
    params.require(:computer).permit(:name)
  end

  def set_softwares
    @softwares = Software.all
  end
end

The association works fine, I tested in rails console, all good. But now I come to creating a form, say for Computer:

app/views/computers/_form.html.erb

<%= form_with(model: computer, local: true) do |form| %>
  <div>
    <%= form.label :name %>
    <%= form.text_field :name %>
  </div>

  <%= form.collection_check_boxes :softwares, @softwares, :id, :name, include_hidden: false do |field| %>
    <%= field.check_box %>
    <%= field.text %>
  <% end %>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

I have tried a few variations of this, but invariably I get some error in the view because of softwares, or the param is not permitted in the controller, even with accepts_nested_attributes_for :softwares is set in the Computer model.

In desperation, I opted to try to have the checkboxes not part of the computer at all, and create the join through association in the controller alone. This worked for new, but then of course broke down for edit. Any help greatly appreciated.

Edit 1 (at the request of jvillian)

The strong params I tried look like this:

def computer_params
  required_params.permit(computer_softwares_attributes: [:software_id])
end

def required_params
  params.require(:computer)
end

but this results in the unpermitted params message in the server logs (and computer_params[:computer_softwares] is nil as a result). In the server logs it looks like the following:

Unpermitted parameters: :name, :computer_softwares
<ActionController::Parameters {} permitted: true>

Edit 2

Not sure why it didn't occur to me earlier - but remove the strong requirement and just doing params[:computer][:softwares] has actually worked fine. The only question that remains really is how I can achieve the same result using strong parameters, as otherwise it would be vulnerable.

Michael Evans
  • 971
  • 1
  • 13
  • 30
  • Since you're having an `unpermitted_param` error (according to the notes in your controller code), it would likely help if you show your params as submitted to your controller. – jvillian Feb 20 '20 at 21:41
  • @jvillian done now in Edit 1. I hope that helps. – Michael Evans Feb 20 '20 at 21:57
  • Sorry if I was unclear. I meant it would likely help if you show your params as submitted to your controller *from your server logs* (where you're seeing the `unpermitted_params` message). What, BTW, is `required_params`? – jvillian Feb 20 '20 at 22:00
  • Sorry, I've been chopping and changing it so much, I forgot that I had abstracted `params.require(:computer)` to a separate method. Updated it now. – Michael Evans Feb 20 '20 at 22:05
  • I'm pretty sure you're *still* not showing the `Parameters` as submitted to your controller. It should be the third line in your server log after `Started...` and `Processing by...` – jvillian Feb 20 '20 at 22:15
  • sorry for not replying sooner. I actually found that this is a duplicate of another question (it took quite some time to find), so I will mark it as such, thanks for your help @jvillian – Michael Evans Feb 21 '20 at 23:28

1 Answers1

2

You need to permit those nested attributes inside computer_params:

def computer_params
  params.require(:computer).permit(:name, software: [:something, :something_else])
end

Whatever fields you want to permit for the nested software model need to go inside that software key

Syntactic Fructose
  • 18,936
  • 23
  • 91
  • 177