The author:
Run, the chief architect


I have 5 years of working experience and am currently working in a well-known technology company in the industry as the chief architect. I am good at front-end technology stack, Ruby, Python, JavaScript and other languages.

Table of contents

  • Improvements to the Grape framework

    • deepexpose
    • Interface return value declaration
    • Integration of parameters and return values
    • Improvements for testing
  • How do beginners get started
  • concept

    • Document-oriented development
    • Are you sure you don’t need Controller tests?
  • Write in the last

Improvements to the Grape framework

deepexpose

As you may know, Expose can be nested. If a block is passed with no arguments, nested rendering is performed:

class Entities::Article < Grape::Entity
 expose :user do
 expose :name { |article| article.user.name }
 expose :age { |article| article.user.age }
 end
end

As above, a data structure like this is rendered:

{
 "user": {
 "name": "Jim",
 "age": 18
 }
}

If deep: true is passed at this point, the instance of the nested layer binding will be different. The following example shows the same effect as the one above:

class Entities::Article < Grape::Entity
 expose :user, deep: true do
 expose :name
 expose :age
 end
end

Interface return value declaration

Status is used to declare the return value of the interface (Success, Fail, Entity are aliases in special cases). It has the following basic forms of invocation:

Status 200 do expose :article, using: Entities:: article end status 200, 'return article' do expose :article, using: Entities:: article end status 200, 'return article' Entities::Article end status 400, 'request parameter error' do expose :code, desc: 'error' expose :message, desc: 'error' 'end success' do expose :article, using: Entities:: article end fail 'do expose :code, desc: Expose :message, desc: 'error message' end entity do expose :article, using: Entities:: article end

The above statement serves two main purposes:

  1. Called in the interface logicpresentMethods are not specified explicitlyEntityType, which is automatically resolved.

    Previously, you had to call:

    present :article, article, with: Entities::Article

    For now, just use this:

    present :article, article

    Because it already knows how to render the article entity in the status declaration.

  2. statusDeclares can generate documents accordingly.

Integration of parameters and return values

Grape::Entity adds a new to_params method that allows you to reuse it in parameter declarations:

params do
 requires :article, type: Hash do
 optional :all, using: Entities::Article.to_params
 end
end

Better than the Grape: : Entity. Documentation, do the following improvements:

  1. Type can be written as a string:

      expose :foo, documentation: { type: 'string' }
  2. Additional parameters can be mixed, such as specifying the param_type parameter:

      expose :foo, documentation: { param_type: 'body' }
  3. We handled is_array properly:

     expose :bar, documentation: { type: String, is_array: true }

Improvements for testing

You can now test the return value of an interface programmatically, without testing things like JSON strings, XML text, and so on. If you implement the interface as follows:

present :article, article

You can test it like this:

get '/article/1'
assert_equal 200, last_response.status
assert_equal articles(:one), presents(:article)

Note that this feature is not implemented in the framework and needs to clone the scaffold project:

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

How do beginners get started

If you’re a complete beginner, it’s recommended to start getting familiar with Grape. I suggest you read the documentation in my repository. After you’re familiar with the Grape Framework, read the above section on my Grape Framework improvements. For the design concept of the whole framework, read the following article.

The project started with the scaffolding project I provided, and all the features were integrated:

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

concept

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 have come up with a new idea, which I call Document-Oriented Development.

Documentation is required when writing an API project. I don’t know how people prepare their documentation, but there is always a problem: I have to write an implementation of the same interface, and I have to write a document at the same time. I often wonder why I can’t write the interface and synchronize the document at the same time. To put it another way, why isn’t the contractual spirit of an interface document automatically part of the implementation? If I could invent a DSL that could constrain the interface’s behavior while writing documentation, wouldn’t that be what I want?

Just do it!

I found that Grape already provides a similar DSL. For example, you can specify parameters like this:

Params do requires :user, type: Hash do requires :name, type: String, desc: 'user's name' requires :age, type: Integer, desc: 'User's age

The above code restricts the parameters to fields Name and Age, and restricts their types to String and Integer, respectively. Meanwhile, a library called Grape – Swagger renders Params’ macro definitions as part of the Swagger document. Perfect, documentation and implementation come together here.

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

Desc "Create new User 'do tags 'users' entity Entities::User end

However, Grape is not a fully document-oriented development framework, and it has many important missions, so its seamless interface with documentation can only go so far. As you can see, the params macros are a great example of this combination, and the desc macros are unfortunately only about document rendering and nothing else.

Given that Grape is an open source framework, it’s fairly easy to modify it to add a few small parts. It took me a few days to add a Status macro, which you can use to declare the return value:

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: 'user age'} end end

The above statement serves two main purposes:

  1. Called in the interface logicpresentMethods are not specified explicitlyEntityType, which is automatically resolved.

    Previously, you had to call:

    present :user, user, with: Entities::User

    For now, just use this:

    present :user, user

    Because it already knows how to render the User entity in the status declaration.

  2. grape-swaggerLibraries can parsestatusMacros generate documentation.

This is just the tip of the iceberg.

Are you sure you don’t need Controller tests?

There are two arguments regarding unit testing of interfaces: should interface testing be for Integration tests or Controller tests? Integration tests are like a black box, where the developer calls the interface and then tests against the view that the interface returns. The Controller test will call the interface the same way, but will measure the internal state.

Use the following two examples to get a feel for how Controller tests and Integration tests are written differently in the Rails framework.

In earlier versions of Rails, there was a Controller test:

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

After Rails 5, Integration testing is more recommended:

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

Note that there is no corresponding ASSERT_EQUAL statement in the Integration test because it is 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)

But the code that tests the view, and changes to the view that depend on it, often fails, too. This is just a tentative way of writing it.

I don’t want to discuss here the Controller testing and Integration testing on relative questions, although you may have sensed my propensity to read between the lines. For this topic to be discussed from 2012 to 2020, you can read this post. I don’t want to make it worse. Many times I’ve thought that people who object to the Controller test might just see the Controller’s instance variable as its internal state, not realizing that it’s also a contract between the Controller and Template. I don’t blame them, because it’s really not elegant to define contracts by passing instance variables.

Fortunately, I’ve done some simple work to make it more elegant to test the return of an interface within Grape. All you need to do is specify the render data in the logical interface using the present method:

present :users, users

You can then test it in a test case with the special Presents method:

assert_equal users(:one, :two, :three), presents(:users)

It’s like ASSIGNS, but it’s more comfortable, right?

Write in the last

This is my process of reinventing Grape, which has begun and continues. My reinvention philosophy is basically two ideas: better documentation and better testing. In fact, with just a little bit of work, it really does work.

If you want to use what I’m doing with Grape, just clone my scaffold:

git clone https://github.com/run27017/g…

The scaffolding used is my forked frameset. They are:

  • grape
  • grape-entity
  • grape-swagger

Check out their links and maybe you too can become a contributor to the open source world.

recommended

If you want to learn more you can check out my latest video

Course links: https://ke.sifou.com/course/1…

The original purpose of the course
  • The dominant pattern in Web development today: front end separation
  • Web API development doesn’t have to be eclectic, it should adopt best practices
  • Summarize and share my years of practical experience
What is the course about?
  • Follow the standard: least surprise
  • Interface completeness: input and output, authentication, error handling, etc
  • Integration with the framework
Why should everyone have their own set of scaffolding?
  1. The framework is only a provider of generic templates and does not prescribe a specific solution
  2. The framework is not refined to best practices