Rails, Ruby

Rails Model: has_many And belongs_to

Goal: structuring multiple Birds within each Nest, so that we could find all birds within a specific nest with routes such as /nests/:nest_id/birds.

Step 1: Create Models

#app/models/nest.rb

class Nest < ActiveRecord::Base
  has_many :birds
end

class Bird < ActiveRecord::Base
  belongs_to :nest
end

Step 2: Generate Migrations

$ rails g migration CreateNests

And edit the file generated under db/migrate:

class CreateNests < ActiveRecord::Migration
  def change
    create_table :nests do |t|
      t.string :name
      t.timestamps null: false
    end

    create_table :birds do |t|
      t.belongs_to :nest, index: true
      t.string :name
      t.timestamps null: false
    end
  end
end

And do a rake db:migrate.

Step 3: Create Controller

#app/controllers/nests_controller.rb, in my case #app/controllers/api/nests_controller.rb

class NestsController < Api::BaseController # I'm using module Api therefore ApplicationController was changed to Api::BaseController here
  before_action :set_nest, only: [:show, :edit, :update, :destroy]

  def index               # GET /nests
    nests = Nest.all
    render json: nests
  end

  def show                # GET /nests/1
    render json: @nest
  end

  def create              # POST /nests
    nest = Nest.new(nest_params)

    if nest.save
       render json: nest, root: false
    else
       render json: { error: 'Internal Server Error'}, status: :internal_server_error
    end
  end

  def update              # PATCH/PUT /nests/1
    if @nest.update(nest_params)
      render json: @nest, status: :ok
    else
      render json: @nest.errors, status: :unprocessable_entity
    end
  end

  def destroy             # DELETE /nests/1
    @nest.destroy
    render json: {}, status: :no_content
  end

  private
    def set_nest
      @nest = Nest.find(params[:id])
    end

    def nest_params
      params.permit(:nest, :name)
    end

end

And likewise for birds_controller.rb, except:

class BirdsController < Api::BaseController
  before_action :set_nest, only: [:index, :create]

  def index
    birds = @this_nest.birds
    render json: birds # render json in all conditions
  end

  def create
    bird = @this_nest.birds.build(bird_params)

    if bird.save
      render json: bird, root: false
    else
      render json: { error: 'Internal Server Error'}, status: :internal_server_error
    end
  end
  ...
  private
    ...
    def set_nest
      @this_nest = Nest.find(params[:nest_id])
    end

Step 4: Set Up Routes

#config/routes.rb

# since I'm using module Api, I have this line here:
namespace :api do
  ...
  resources :nests, only: [:index, :show, :create, :update, :destroy] do
    resources :birds, only: [:index, :create, :update, :destroy]
  end
end

And a rake routes reveals that we now have:

GET/POST           /api/nests
PATCH/PUT/DELETE   /api/nests/:id
GET/POST           /api/nests/:nest_id/birds
PATCH/PUT/DELETE   /api/nests/:nest_id/birds/:id

Just what we need.

Standard