This is a conceptual summary of a framework I’ve recently rewritten that will be easy for Ruby programmers, Rails programmers, Grape programmers to understand.

Document-oriented development

In today’s environment, there are many development paradigms for back-end developers to choose from, such as test-driven development, behavior-driven development, agile software development, and so on. In contrast, I came up with a new idea that I call document-oriented development.

While writing an API project, you should prepare documentation. I don’t know how people prepare their documentation, but there is always a problem: for the same interface, my implementation has to write a copy, and my documentation has to write a copy at the same time. I often wonder, why can’t I write an interface and get the document done synchronously? To put it another way, why can’t the spirit of the contract of interface documentation automatically become part of the implementation? If I could invent a DSL that could constrain the behavior of an interface while writing documentation, wouldn’t that be what I want?

Just do it!

I’ve noticed that the Grape framework already provides a DSL like this. For example, you can specify parameters like this:

params do
  requires :user.type: Hash do
    requires :name.type: String, desc: 'User name'
    requires :age.type: Integer, desc: 'Age of user'
  end
end
Copy the code

The above code can restrict the parameters by limiting the name and age fields to String and Integer, respectively. Meanwhile, a library called Grape-Swagger renders Params macro definitions as part of a Swagger document. Perfect. Documentation and implementation are combined here.

In addition, the Grape framework provides the DESC macro, which is a purely documented declaration for third-party libraries to read without having any effect on interface behavior.

desc 'Create a new user' do
  tags 'users'
  entity Entities::User
end
Copy the code

But after all, Grape’s framework isn’t a fully document-oriented development framework, and it has many important missions, so that’s about it. As you can see, the Params macro is an example of a perfect fit, while the DESC macro is unfortunately only concerned with document rendering and nothing more.

Given that the Grape framework is open source, it’s a simple matter to modify it to add a few widgets. It took me a few days to add a status macro that can be used to declare return values:

status 200 do
  expose :user.deep: true do
    expose :id.documentation: { type: Integer, desc: 'User ID' }
    expose :name.documentation: { type: String, desc: 'User name' }
    expose :age.documentation: { type: Integer, desc: 'Age of user' }
  end
end
Copy the code

The above statement has two main functions:

  1. Calling the present method in the interface logic does not explicitly specify the Entity type, which is resolved automatically.

    Previously, you had to call:

    present :user, user, with: Entities::User
    Copy the code

    For now, just this:

    present :user, user
    Copy the code

    Because in the Status declaration it already knows how to render the user entity.

  2. The Grape-Swagger library can parse status macro generation documents.

This is just the tip of the iceberg.

Are you sure you don’t need Controller tests?

There are two arguments about unit testing of interfaces: Should interface tests be for Integration tests or Controller tests? An Integration test is like a black box, where the developer invokes the interface and then tests it against the view returned by the interface. The Controller test calls the interface in the same way, but measures the internal state.

The following two examples give you an intuitive feel for how Controller tests and Integration tests are written in the Rails framework.

In earlier versions of Rails, there were Controller tests:

class ArticlesControllerTest < ActionController::TestCase
  test "should get index" do
    get :index
    assert_response :success
    assert_equal users(:one.:two.:three), assigns(:articles)
  end
end
Copy the code

After Rails 5, Integration tests are recommended:

class ArticlesControllerTest < ActionDispatch::IntegrationTest
  test "should get index" do
    get articles_url
    assert_response :success
  end
end
Copy the code

Notice that there is no corresponding AsserT_equal statement in the Integration test because it is more difficult to write. If the view returns JSON data, try the following equivalent:

assert_equal users(:one.:two.:three).as_json, JSON.parse(last_response.body)
Copy the code

But the code that tests the view, and the changes that depend on the view, are also frequently broken, and this is just a tentative way of writing it.

I don’t want to get into the merits of Controller versus Integration testing here, although you may have detected my bias between the lines. If you don’t believe that this topic can be discussed from 2012 to 2020, you can read this thread. I don’t want to make this any worse. Many times I’ve thought that people who object to Controller testing might just see the instance variable of a Controller as its internal state and not realize that it is also a contract between Controller and Template. I don’t blame them, because it’s really not elegant to define contracts by passing instance variables.

Fortunately, I did some simple work to make it more elegant to test the return of the interface within Grape’s framework. You just need to specify render data using the present method in the logical interface:

present :users, users
Copy the code

It can then be tested in combination with the special Presents method in the test case:

assert_equal users(:one.:two.:three), presents(:users)
Copy the code

It’s named assigns, but it’s more comfortable, isn’t it?

Write in the last

This is my transformation of Grape’s framework that has already begun and will continue. My reinvention philosophy is nothing more than two ideas: better documentation and better testing. And in fact, with a little bit of work, it really does work.

If you want to use my Grape framework, clone my scaffolding:

git clone https://github.com/run27017/grape-app-demo.git
Copy the code

The framesets used in the scaffolding after I Fork are:

  • grape
  • grape-entity
  • grape-swagger

Click on their links to check them out and maybe you can join the open source world.