SJU
SJU

Reputation: 3272

Rails create/destroy association for existing models by JSON call

I have two models that I want to manually associate or dissociate on demand.

class Car < ApplicationRecord
  has_many :wheels, dependent: :nullify
end

class Wheel < ApplicationRecord
  belongs_to :car
end

Say that my database is already full of cars and wheels. I don't know what is the proper way to associate a wheel to a car using JSON API calls or dissociate one in case a wheel have already a car_id filled.

Should I just create a nested route such as :

resources :cars do
  resources :wheels, only: [:add_to_car, :remove_from_car]
end

But the add_to_car and remove_from_car methods doesn't show up in the routes list.

Ideally I would like to do something like /cars/:id/add_wheel/:wheel_id (or something like that more RESTful friendly)

What is the Rails way to do this?

Upvotes: 0

Views: 34

Answers (1)

Bruno Costanzo
Bruno Costanzo

Reputation: 154

Ir order to define this routes:

POST /cars/:id/add_wheel/:wheel_id for adding a wheel to a car.
DELETE /cars/:id/remove_wheel/:wheel_id for removing a wheel from a car.

You can do something like this

# config/routes.rb
resources :cars do
  member do
    post 'add_wheel/:wheel_id', to: 'cars#add_wheel'
    delete 'remove_wheel/:wheel_id', to: 'cars#remove_wheel'
  end
end

Then your controller should look like this:

class CarsController < ApplicationController
  # POST /cars/:id/add_wheel/:wheel_id
  def add_wheel
    car = Car.find(params[:id])
    wheel = Wheel.find(params[:wheel_id])

    wheel.update(car: car)

    if wheel.save
      render json: wheel, status: :ok
    else
      render json: wheel.errors, status: :unprocessable_entity
    end
  end

  # DELETE /cars/:id/remove_wheel/:wheel_id
  def remove_wheel
    car = Car.find(params[:id])
    wheel = car.wheels.find(params[:wheel_id])

    wheel.update(car: nil)

    if wheel.save
      render json: { message: "Wheel removed from car" }, status: :ok
    else
      render json: wheel.errors, status: :unprocessable_entity
    end
  end
end

Anyway, there is another approach even more RESTFUL, because if you see the previous answer, you can notice that there is a perform over the wheel, and that is not very RESTful. But if you have your model like:

# app/models/car.rb
class Car < ApplicationRecord
  has_many :wheels, dependent: :nullify

  # Adds a wheel to the car
  def add_wheel(wheel_id)
    wheel = Wheel.find_by(id: wheel_id)
    wheels << wheel if wheel && wheel.car_id != self.id
  end

  # Removes a wheel from the car
  def remove_wheel(wheel_id)
    wheel = wheels.find_by(id: wheel_id)
    wheel.update(car_id: nil) if wheel
  end
end

Then in your controller you can write st like this:

def add_wheel
  @car = Car.find(params[:id])
  
  if @car.add_wheel(params[:wheel_id])
     good
  else
     not good...
  end
end

The other thing is you can consider is having routes like

# config/routes.rb
resources :wheels, only: [] do
  member do
    post 'add_to_car', to: 'wheels#add_to_car'
    delete 'remove_from_car', to: 'wheels#remove_from_car'
  end
end

And the controller:

class WheelsController < ApplicationController
  # POST /wheels/:id/add_to_car
  def add_to_car
    wheel = Wheel.find(params[:id])
    car = Car.find(params[:car_id]) # Assuming `car_id` is passed as part of the request parameters

    wheel.car = car
    if wheel.save
      render json: wheel, status: :ok
    else
      render json: wheel.errors, status: :unprocessable_entity
    end
  end

  # DELETE /wheels/:id/remove_from_car
  def remove_from_car
    wheel = Wheel.find(params[:id])
    
    wheel.car = nil
    if wheel.save
      render json: { message: 'Car association removed successfully.' }, status: :ok
    else
      render json: wheel.errors, status: :unprocessable_entity
    end
  end
end

At the end, it depends on you and how you think your application domain!

Upvotes: -1

Related Questions