Chiefwarpaint
Chiefwarpaint

Reputation: 673

Rails - Set additional attribute on has_many through record

Using Rails 6.0.3.3 and ruby '2.6.0'.

I have these 3 models connected via a has_many through relation.

class Recipe < ApplicationRecord
  has_many :layers
  has_many :glazes, through: :layers
end

class Glaze < ApplicationRecord
  has_many :layers
  has_many :recipes, through: :layers
end

class Layer < ApplicationRecord
  belongs_to :recipe
  belongs_to :glaze
  # there is a column named "coat_type" here that I would like to set
end

Everything is working great for creating a new Recipe and it automagically creating its related Layer record. But now I would like to also set a coat_type attribute on the Layer record when it's created, but I can't seem to figure out how I could do something like that.

The Form View Partial

= form_with model: recipe, local: true do |form|
  %p
    = form.label :name
    %br
    = form.text_field :name
  %p
    = form.label :description
    %br
    = form.text_area :description
  .recipe-glaze
    = form.collection_select(:glaze_id, Glaze.all, :id, :name, { prompt: "Select Glaze" }, { name: 'recipe[glaze_ids][]' })  
  .recipe-glaze
    = form.collection_select(:glaze_id, Glaze.all, :id, :name, { prompt: "Select Glaze" }, { name: 'recipe[glaze_ids][]' })  
  %p
    = form.submit

The Controller Create Action (and strong params acton)

def create
  @recipe = Recipe.new(recipe_params)

  if @recipe.save
    redirect_to @recipe
  else
    render 'new'
  end
end

private

def recipe_params
  params.require(:recipe).permit(:name, :description, glaze_ids: [])
end

Ideally, I would be able to add another select box in the form and the user would use that to set the coat_type of the Layer record that will be created. But I can't figure out how I could pass that into the controller and have it know what to do with that value.

Is this something that is possible, or am I approaching this incorrectly?

Upvotes: 0

Views: 334

Answers (1)

Chiefwarpaint
Chiefwarpaint

Reputation: 673

So I actually ended up stumbling upon the "cocoon" gem thanks to this comment. By following the setup instructions for "cocoon", I was able to tweak my code to do what I needed.

My Recipe model changed to this ::

class Recipe < ApplicationRecord
  has_many :layers, inverse_of: :recipe
  has_many :glazes, through: :layers
  accepts_nested_attributes_for :layers, reject_if: :all_blank, allow_destroy: true
end

My controller's strong params action changed to this ::

  private

  def recipe_params
    params.require(:recipe).permit(:name, :description, layers_attributes: [:id, :glaze_id, :coat_type, :_destroy])
  end

My form view partial changed to ::

  - if recipe.errors.any?
    = render partial: 'errors', locals: { recipe: recipe }
  %p
    = form.label :name
    %br
    = form.text_field :name
  %p
    = form.label :description
    %br
    = form.text_area :description
  %h3 Layers
  #layers
    = form.fields_for :layers do |layer|
      = render 'layer_fields', f: layer
    .links
      = link_to_add_association 'add layer', form, :layers
  %p
    = form.submit

and the "layer_fields" partial referenced in the form looks like this ::

.nested-fields
  .field
    = f.label :glaze_id
    %br
    = f.collection_select(:glaze_id, Glaze.all, :id, :name, { prompt: "Select Glaze" } )
  .field
    = f.label :coat_type
    %br
    = f.text_field :coat_type
  = link_to_remove_association 'remove layer', f

Making those changes using the "Cocoon" gem, I was able to accomplish what I needed. Hopefully this helps someone else in the future.

Upvotes: 1

Related Questions