ruby-on-railsmany-to-manyrails-api

Rails API Controller Update Action -- How to add many-to-many


I have a Rails Api that feeds a Vue front end. I have two main models, Contacts and Outlets, with a many to many relationship via a join table ContactOutlets. I am trying to figure out how in the Contacts controller to add an association to an outlet.

I recognize, I could call the ContactOutlet create action separately, but it seems wasteful if Rails can handle this on the back end. I want vue to call contact#update once.

Contact Model:

class Contact < ApplicationRecord
  has_many :contact_outlets
  has_many :outlets, through: :contact_outlets
  has_many :calls

  validates_uniqueness_of :email
  validates_uniqueness_of :name

end

Outlet Model:

class Outlet < ApplicationRecord
  has_many :contact_outlets
  has_many :contacts, through: :contact_outlets
  has_many :calls

  validates_uniqueness_of :website
end

ContactOutlet:

class ContactOutlet < ApplicationRecord
  belongs_to :contact
  belongs_to :outlet

  validates_uniqueness_of :contact_id, :scope => :outlet_id
end

Contacts Controller:

class ContactsController < ApplicationController
  before_action :set_contact, only: %i[ show update destroy ]

  # GET /contacts
  def index
    @contacts = Contact.all

    render json: @contacts, include: :outlets
  end

  # GET /contacts/1
  def show
    render json: @contact, include: :outlets
  end

  # POST /contacts
  def create
    @contact = Contact.new(contact_params)

    if @contact.save
      render json: @contact, status: :created, location: @contact
    else
      render json: @contact.errors, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /contacts/1
  def update
    if @contact.update(contact_params)
      render json: @contact, include: :outlets
    else
      render json: @contact.errors, status: :unprocessable_entity
    end
  end

  # DELETE /contacts/1
  def destroy
    @contact.destroy
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_contact
      @contact = Contact.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def contact_params
      params.require(:contact).permit(:name, :email, :bio, :image_url)
    end
end

Solution

  • Solved this. In case anyone else is looking the models above are fine. made some adjustments to the contact_params to allow access to the outlets array. Then fixed the update action. Full controller code below:

    class ContactsController < ApplicationController
      before_action :set_contact, only: %i[ show update destroy ]
    
      # GET /contacts
      def index
        @contacts = Contact.all
    
        render json: @contacts, include: :outlets
      end
    
      # GET /contacts/1
      def show
        render json: @contact, include: :outlets
      end
    
      # POST /contacts
      def create
        @contact = Contact.new(contact_params)
    
        if @contact.save
          render json: @contact, status: :created, location: @contact
        else
          render json: @contact.errors, status: :unprocessable_entity
        end
      end
    
      # PATCH/PUT /contacts/1
      def update
        if @contact.outlets
          @contact.outlets.delete_all
        end
        if params[:outlets]
          contactOutlets = params[:outlets]
          contactOutlets.each do |outlet|
            @contact.outlets << Outlet.find(outlet[:key])
          end
        end
        if @contact.update(contact_params)
          render json: @contact, include: :outlets
        else
          render json: @contact.errors, status: :unprocessable_entity
        end
      end
    
      # DELETE /contacts/1
      def destroy
        @contact.destroy
      end
    
      private
        # Use callbacks to share common setup or constraints between actions.
        def set_contact
          @contact = Contact.find(params[:id])
        end
    
        # Only allow a list of trusted parameters through.
        def contact_params
          params.require(:contact).permit(:name, :email, :bio, :image_url, outlet_ids:[])
        end
    end