Skip to content

pernambucano/frevo-on-rails-rswag-tutorial

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rswag tutorial

The purpose of this repository is to show a basic example of how to use rswag in your rails apis. At the end of the steps below you wil have the final project that you can see here. You really should know at least what swagger (openApi) is. I will try to explain what I can, but if you have any questions just go to their documentation.

The example

We will create a rest api for a bookstore. It will be really simple, so we will won't worry about many cool stuff. However, this will give us the oportunity to grasp yet another concept called TDD. We will create a simple rest api that will list all the books available! Pretty neat, huh?

Step-to-Step

Prerequisites

Have ruby and rails installed. I used docker to create this project, but you could just as easily to the same outside docker.

To run this project locally using docker, just run this:

DIR_APP=bookstore docker-compose up rails 

To access the bash inside ( to run tests, for example):

DIR_APP=bookstore docker-compose run runner

Create a new rails api-only project.

rails new bookstore --api

All of the commands below will run inside this new directory (bookstore).

Install rspec and rswag

Follow the installation process in their repos: rspec-rails, rswag.

Create our first test

As we are doing TDD here, we will start with a test for an endpoint that will return the list of books. We will surely have no book at the start, but it is the easiest way go, and we will find a way to insert books later.

Rswag can generate the base code for us:

rails generate rspec:swagger BookController

This created a file at "spec/requests/book_spec.rb", open it and you will see almost nothing. It's ok, we will complete it now.

    require 'swagger_helper'

    RSpec.describe 'book', type: :request do
    end

We are going to clean it a little bit, and add an empty test. We will read it line by line to understand some basic components before we dive in.

    require 'swagger_helper'

    describe 'Books API' do
        path '/books' do
            get 'List all available books' do
                produces 'application/json'
                response '200', 'books listed' do
                    run_test!
                end
            end
        end
    end

At the very first line, we import the swagger_helper. This is a config file created by rswag, and creates the basic structure for our swagger.yaml.

What is this swagger.yaml I'm talking about? Well, it is the document that follows the OpenApi Standards. It is the specifications for your api, the contract between you, the creator, and all your users. It is also possible to create a beautiful Rest Client UI with it. We will talk more about this later.

When rswag creates our swagger.yaml, it grabs the basic structure from the swagger_helper, and joins it with all the small documentations generated by our tests. It's like magic!

Ok, let's continue. We then create a new rspec test, nothing new here, but we add four strange new words here: "path", "get", "produces" and "response". All pretty straighfoward. We have a path (a.k.a the path of our endpoint), and inside of it we can "GET" some "JSON" information. We are also awaiting a response with a 200 code, that means a successful response.

Now that we have the basics working, let's add an validation inside our test.

    require 'swagger_helper'

    describe 'Books API' do 
        path '/books' do
            get 'List all available books' do
                produces 'application/json'
                response '200', 'books listed' do
                    schema type: :array, items: {
                            type: :object,
                            properties: {
                                    name: { type: :string },
                                    author: { type: :string },
                                },
                            },
                            required: ["name"]

                    run_test!
                end
            end
        end
    end

By now, you already can ready most of the code. Here, we added the "schema". Let me tell you something, this is the best part of the standard. We define how we want the response in a format that is organized, simple, and concise. Try reading it before I explain it, I'll wait.

In our schema, we defined that our response will be an array of objects. Inside these objects we'll have a name, and an author. Notice that right at the end we say that we want at least the name of the book.

Now that we have a nice test, let's run it!

error 1

Bam! An error! In TDD we trust that the error messages will guide us to the expected behavior. Let's follow the path that it shows us. Ok, so there's no route for... Of Course! We need to create a route before we try to access. Here we would create just the route, and then go creating component by component, but hey, rails is famous by cool generators right? Let's use one of those to create routes, controller, models... However, we don't need to create another redundant request test so we will tell it so.

rails generate resource book name:string author:string --no-request-specs

Well, it created lots of new and exciting stuff. Let's see if our test passes now (don't forget to migrate before):

error 2

That's what I'm talking about! A wild new error appears. This means we are moving, and when we make our test pass we'll be confident that our test is indeed doing what it should. You probably noticed another test "Not implemented", that's the model test generated with the resource. Don't worry about it right now.

Let's be good programmers, and do what the error message says.

    class BooksController < ApplicationController
        def index
        end
    end

But, why are we not implementing it yet?? Well, baby steps young grasshoper, baby steps...

error 3

Cool, now we now that we are returning a 204 (no content) code, but we want a 200! Lets return something then.

    class BooksController < ApplicationController
        def index
            render json: []
        end
    end

Then we would see this:

error 4

WOW. Now we are talking. We are expecting an array of books, but an empty list is a valid return as well because we didn't specify a minimum size for this list. Now that we have something Green, lets refactor it!

    class BooksController < ApplicationController
        def index
            @books = Book.all()
            render json: @books
        end
    end

This gives the following:

success 1

Yay! We did it! Our first endpoint created purely by following error messages. You are a wizard, Harry!

Wait... What about when we have books, will it work ok too? Only one way to figure it out. Create a file "spec/fixtures/books.yml" and add the following:

    tlotr:
        name: The Lord of The Rings - The two Towers
        author: J. R. R. Tolkien

    hp:
        name: Harry Potter and the Goblet of Fire
        author: J.K. Rowling

Add the following to the begining of the test ( after the first describe):

require 'swagger_helper'

    describe 'Books API' do 
        fixtures :all

        path '/books' do
            get 'List all available books' do
                produces 'application/json'
                response '200', 'books listed' do
                    schema type: :array, items: {
                            type: :object,
                            properties: {
                                    name: { type: :string },
                                    author: { type: :string },
                                },
                            },
                            required: ["name"]

                    run_test!
                end
            end
        end
    end

Let's see the result.

success 2

Still golden. But remember, this data is only used for tests.

We know now that our test is working, but it would be great to visualize and interact with our API.

First, go to swagger_helper.rb and alter the default host from "www.example.com" to "localhost:3000", and the url from "https://{defaultHost}" to "http://{defaultHost}". This will make things easier for us.

Next, run this:

RAILS_ENV=test rails rswag

This will join every part of our tests and the base structure in one file (swagger/v1/swagger.yaml) and update our ui.

It should look like this:

swagger 1

Now, start your application if you haven't yet, and go to "http://localhost:3000/api-docs" and you should see an awesome client for your REST API almost for free!

You can interact with your API too. In the case of our only endpoint, an empty array will be returned because we didn't add anything to the development database. Let's add some data to db to see our code in action. But first, take a look around, feel the UI and how it can help you when you have hundreds of endpoints.

swagger ui 1

Let's add some example data. Go to "db/seeds.db" and add the following:

Book.create(name: "The Chronicles of Narnia", author: "C. S. Lewis")
Book.create(name: "The Creative Habit: Learn It and Use It For Life", author: "Twyla Tharp")
Book.create(name: "Sapiens: A Brief History of Humankind", author: "Yuval Noah Harari")

Then run this:

rails db:seed

This is much more realistic, right?

swagger ui 1

If you want to have this specification in one place but prefer to use another client, you can get the swagger.yaml from this path: "http://localhost:3000/api-docs/v1/swagger.yaml". Pretty much every rest api client can read this, including Postman.

TODO: Add post endpoint here

We did good together, thanks for reading until here. I hope you learned something new today.

Cheers!

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages