How to Build a Ruby on Rails API with Scaffold – The Complete 10 Step Guide in 5 Minutes

1) command

$ bundle install
enter fullscreen mode

exit fullscreen mode

$ npm install --prefix client
enter fullscreen mode

exit fullscreen mode

2) Generator

$ rails g scaffold lowercase_snake_formatted_name attr_default_string attr_of_type:datatype model_that_has_many_of_these:belongs_to --no-test-framework
enter fullscreen mode

exit fullscreen mode

Lowercase snake is the only format that will convert test_model to TestModel, TestModels, has_many :test_models, etc in all the right places.

Note on using relation_to relations in this generator as it will create foreign key tables. We will never need to use has_many on this row due to the nature of foreign keys being residing.

If you make mistake. , ,

$ rails destroy scaffold scaffold_to_destroy
enter fullscreen mode

exit fullscreen mode

3) Fill model relation

related_to will be created automatically
has_many will be created like this. , ,

    has_many :signups, dependent: :destroy
    has_many :campers, through: :signups
enter fullscreen mode

exit fullscreen mode

This is a good time to consider dependents: destroy if applicable.

4) Fill in the model validation

Here are some common ones. , ,

    validates :name, presence: true
    validates :age, :inclusion => 8..18
    validates :email, :uniqueness: true
enter fullscreen mode

exit fullscreen mode

5) Seeds

$ rails db:migrate db:seed
enter fullscreen mode

exit fullscreen mode

6) route.rb – fill it in correctly

If all routes are required then we use

resources :model
enter fullscreen mode

exit fullscreen mode

Otherwise the following paths correspond only to the following: array symbol
get /model => [ . . . , :index]
get /models/:id => [ . . . , :show]
post /model => [ . . . , :create]
patch /models/:id => [ . . . , :update]
DELETE /models/:id => [ . . . , :destroy]

Overall we end up with something like this. , ,

resources :models, only: [:index, :show, :create]
enter fullscreen mode

exit fullscreen mode

~) As you do the following, be mindful of which routes you need, or perhaps follow all the steps if unsure. , ,

7) Cleaning work

In any controller that takes a param. , ,

params.require(:model).permit(:attr1, :attr2)
enter fullscreen mode

exit fullscreen mode

… becomes. , ,

params.permit(:attr1, :attr2)
enter fullscreen mode

exit fullscreen mode

…we can also add each of these to the TOP of the controller just to be safe. , ,

wrap_parameters format: []
enter fullscreen mode

exit fullscreen mode

We can also deal with RecordNotFound errors that will be triggered once by any of the above methods in application_controller.rb.
We add the following. , ,

  rescue_from ActiveRecord::RecordNotFound, with: :render_not_found_response

  private

  def render_not_found_response(exception)
    render json: { error: "#{exception.model} not found" }, status: :not_found
  end
enter fullscreen mode

exit fullscreen mode

In controllers, add the bang operator! To create and update methods if they are going to be used.

def create
  @model = Model.new(model_params)

  if @model.save
    render json: @model, status: :created, location: @model
  else
    render json: @model.errors, status: :unprocessable_entity
  end
end
enter fullscreen mode

exit fullscreen mode

… becomes. , ,

def create
  @model = Model.create!(model_params)
  render json: @model, status: :created
end
enter fullscreen mode

exit fullscreen mode

Similarly update methods are adjusted in the same way.

def update
  if @model.update(model_params)
    render json: @model
  else
    render json: @model.errors, status: :unprocessable_entity
  end
end
enter fullscreen mode

exit fullscreen mode

…as simple as it gets. , ,

def update
  @model.update!(model_params)
  render json: @model, status: :accepted
end
enter fullscreen mode

exit fullscreen mode

head add :no_content to delete routes

def destroy
  @activity.destroy
end
enter fullscreen mode

exit fullscreen mode

… becomes. , ,

def destroy
  @activity.destroy
  head :no_content
end
enter fullscreen mode

exit fullscreen mode

8) Add unprocessable_entity errors where necessary

If we have a POST or UPDATE path in use for the model (create or update methods) then we also need to add in our private methods inside the controller. , ,

def render_unprocessable_entity_response(invalid)
  render json: { errors: invalid.record.errors.full_messages }, status: :unprocessable_entity
end
enter fullscreen mode

exit fullscreen mode

…since these methods take parameters (strong) and at the top of this controller we need to add. , ,

rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity_response
enter fullscreen mode

exit fullscreen mode

This should happen in every controller where POST or UPDATE are in use.

9) Special Concerns

“Instead of” case: When a user signs up for signup instead of returning signup we want to return what they signed up for. It is very simple for example we do the following. , ,

def create
  @signup = Signup.create!(signup_params)
  render json: @signup, status: :created
end
enter fullscreen mode

exit fullscreen mode

… becomes. , ,

def create
  @signup = Signup.create!(signup_params)
  render json: @signup.activity, status: :created
end
enter fullscreen mode

exit fullscreen mode

@signup => @signup.activity

We can also solve this by creating a new serializer, but we don’t always need that. If it replaces a wickedly simple example with an associated instance that is formatted using the default serializer we can do it like above.

In other words, we are returning the default-format associated model instance of interest, rather than the one being operated on.

return all associated cases: I have a user with multiple tickets, when I show the method/route on this user I want their tickets to be returned as children. As long as the Model relationship and the DB are properly connected, it’s as simple as adding the UserSerializer to the following line. , ,
has_many :tickets

Tickets will be formatted using the default TicketSerializer.

It’s good to note here that if we have a many-to-many relationship like a doctor who has many patients through appointments, we can return patients the same way as directly using has_many:patients And the serial has to be aware of jumping to the join table.

But only sometimes case: what if I don’t want tickets to come to index method/route when we look at all users and instead see only one? Instead of modifying the main serializer, in this case UserSerializer we can create a new one. , ,

$ rails g serializer user_show_tickets
enter fullscreen mode

exit fullscreen mode

Since this wasn’t serializer scaffolding, we need to make sure we add the appropriate key features on top. They can be copied from the regular UserSerializer as long as we want them. , ,

attributes :id, :name, :age, :email
enter fullscreen mode

exit fullscreen mode

Then we add has_many here instead of UserSerializer. Overall it looks like this. , ,

class UserShowTicketsSerializer < ActiveModel::Serializer
  attributes :id, :name, :age, :email
  has_many :tickets
end
enter fullscreen mode

exit fullscreen mode

Finally, we specify that this method/route uses this serializer inside the controller where it is declared. , ,

def show
  render json: @user, serializer: UserShowTicketsSerializer
end
enter fullscreen mode

exit fullscreen mode

We can specify a serializer to do anything on a specific route like this, however it is important to note that inside a serializer we have access to the instance by calling the object if we want to access specific properties of it. Huh.

,

10) Troubleshooting

If we are using @model instance methods or a set/private method in our controllers then it is a good indication that if we want to access that variable directly then we should include a line like the following at the top of the controller needed. Scaffolding takes care of this automatically, but in case it’s helpful. , ,

before_action :set_instance, only: [:show, :update, :destroy]
enter fullscreen mode

exit fullscreen mode

Here we are saying that we need to set this variable when we use the following method-ways (those that depend on using that variable).

If we are using server and npm to test instead of using test, it is important to clear our database on retest on retest in case bad data is passed into the DB along with the way our code works It is possible , , We can add the following to our Seeds.rb. , , (above creating the invocation)

puts "Clearing database"
ModelOne.destroy_all
ModelTwo.destroy_all
ModelThree.destroy_all
enter fullscreen mode

exit fullscreen mode

now, our

$ rails db:seed
enter fullscreen mode

exit fullscreen mode

Works a lot like a replicator.

Bonus) Comment in controllers or remove routes that are not being used.

Hope this is helpful, let me know if I have missed something or made an error!
-Elliot/Older Sister

Leave a Comment