Search code examples
ruby-on-railshas-and-belongs-to-many

Has and belongs to many API updates


I have a has_and_belongs_to_many in my Rails project. The schema look like this

    create_table :dogs do |t|
      t.string :name
      t.string :breed
      t.string :dog_type
    end

    create_table :users_dogs, id: false do |t|
      t.belongs_to :user
      t.belongs_to :dog
      t.index ["user_id", "dog_id"], name: "by_user_and_dog", unique: true
      t.index ["user_id"], name: "index_users_dogs_on_user_id"
      t.index ["dog_id"], name: "index_users_dogs_on_dog_id"
    end

    create_table :users do |t|
      t.string :name
      t.string :email
    end

And my models

class User < ApplicationRecord
  has_and_belongs_to_many :dogs

  validate :only_one_good_dog_per_breed
  validate :only_one_bad_dog_per_breed
  validate :at_least_one_good_dog
end

class Dogs < ApplicationRecord
  DOG_TYPES = %w[good bad].freeze
  BREEDS = %w[poodle bulldog]
  has_and_belongs_to_many :users

  validates :name, :breed, :dog_type, presence: true
  validates :breed,
            inclusion: { in: BREEDS, allowed_options: BREEDS.join(", ") }
  validates :dog_type,
            inclusion: { in: DOG_TYPES, allowed_options: DOG_TYPES.join(", ") }
  validates :name, uniqueness: { scope: [:breed, :dog_type] }
end

As you can see there are some restrictions for which types of dogs the user can have. They can only have 1 dog_type for each breed. And the name can be anything really. And they must always have at least 1 good dog.

Ok now for my question. I have an update users API route for my API clients. Currently they can send their Dog ids as part of the user params to update their dogs.

    def update
      if @user.update(user_params)
       render json: @user, status: :ok
      else
       render json: { errors: @user.errors }, status: :unprocessable_entity
      end
    end

    private

    def user_params
      params.require(:user).permit(
        :name,
        dog_ids: []
      )
   end

Now I realized my API clients will have to always send over all the User dog ids if they ever want to do any of the following, update one of their dog's name, add a newly created Dog object to the list of dogs the user has, or remove a dog from the list of dogs belonging to the user.

As of right now my API only allows for a creation of a Dog object. And since these dogs can have and belong to many different users I don't want to allow the update or deletion of a Dog directly since that could affect my other users that share a dog and their dog hasn't changed at all.

I'm wondering if I should continue to require my API client to always send over all the dog_ids that are going to be associated with my user. OR if I should create three actions in my User API controller. One to add a dog to the User's list of dogs. One to update a user's existing dog (i.e. associating a new dog_id to a user and removing an existing dog id that shares the new dog's breed and dog_type per my validations). And one action to remove a dog from the list of user dogs.

What is typical of these type of APIs for a has_and_belongs_to_many type of relationship?


Solution

  • I'm not sure what is typical of APIs for this type of relationship. A quick Google of the topic didn't turn up anything definitive.

    One thing to notice, however, is this isn't managing resources in the classic CRUD sense. It's managing associations between resources.

    I imagine dogs would be managed using a conventional dogs endpoint. For example to create a new dog:

    POST/api/v1/dogs
    PARAMS: 
    - name
    - breed
    - dog_type
    

    Then to create or delete an association, API client would make a request to these endpoints:

    POST /api/v1/users/:user_id/dogs/:id
    
    DELETE /api/v1/users/:user_id/dogs/:id
    

    User-related validations would be enforced on these requests.