[Rails API x Docker] Easy environment construction with shell & operation check with Flutter

The procedure is carefully done so that even beginners can proceed.

things to do

-** RailsAPI × Environment construction with Docker ** -** Lightly implement Rails API ** -** Hit API from API client ** (from Postman or Flutter app) * Flutter is optional

スクリーンショット 2020-09-13 13.42.28.png

Those who want to see only the shell of API container startup → [here](https://qiita.com/drafts/942ad5fe5c13f70a19c5/edit#1-2-%E3%82%B7%E3%82%A7%E3%83%AB % E3% 81% A7railsapi% E3% 82% B3% E3% 83% B3% E3% 83% 86% E3% 83% 8A% E3% 83% 93% E3% 83% AB% E3% 83% 89) For the time being, Rails API source code and Flutter source code are also available (* messing around) (May be)

procedure

--Until the Docker container on the API side is started --Creating a directory --Rails API container build in shell --Container start & confirmation --Rails API implementation --User model creation & DB table preparation --Create User controller --Routing settings --Check API operation with Postman --Postman installation --Get DB User resource with GET --Add DB User resource with POST --Hit the API with Flutter --Project creation --Enable Http communication --Edit main.dart

Premise

--Make the Flutter application running in Android Studio --You can download SDK etc. and start the simulator -* The Flutter setup was basically all done with this article (it takes 3 to 40 minutes). --You can use Docker and docker-compose commands (OK if $ docker-compose -v works)

1. Until the Docker container on the API side is started

We will create a Docker container for Rails API.

However, it is inefficient to type commands from 1, so the basics are summarized in the shell.

1-1. Directory creation

Prepare a directory by applying the project name to app_name.

$ mkdir app_name  #Prepare a directory to put the API application
$ cd app_name

1-2. Build Rails API container in shell

Below is a shell that even builds the API container. (Reference article)

** Shell for launching sample API **

set_up_rails.sh


#!/bin/bash

#config setting#############
APP_NAME="app_name"            #← Feel free to change it (maybe with the directory name)
MYSQL_PASSWORD="password"      #← Please change freely
###########################

echo "docker pull ruby2.6.6"
docker pull ruby:2.6.6

echo "docker pull mysql:5.7"
docker pull mysql:5.7

echo "docker images"
docker images

echo "make Dockerfile"

cat <<EOF > Dockerfile
FROM ruby:2.6.6
ENV LANG C.UTF-8
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
#yarn setup
RUN curl -o- -L https://yarnpkg.com/install.sh | bash
ENV PATH /root/.yarn/bin:/root/.config/yarn/global/node_modules/.bin:\$PATH
#Create and set working directory
RUN mkdir /${APP_NAME}
ENV APP_ROOT /${APP_NAME}
WORKDIR \$APP_ROOT
#Add host side (local) Gemfile
ADD ./Gemfile \$APP_ROOT/Gemfile
ADD ./Gemfile.lock \$APP_ROOT/Gemfile.lock
#Gemfile bundle install
RUN bundle install
ADD . \$APP_ROOT
#Because an error occurs in uninstall rails 6 of gem version yarn
RUN gem uninstall yarn -aIx
#webpacker settings
#RUN rails webpacker:install
EOF

echo "make Gemfile"
cat <<EOF > Gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem 'rails', '~> 6.0.3.2'
EOF

echo "make Gemfile.lock"
touch Gemfile.lock

echo "make docker-compose.yml"
cat <<EOF > docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD}
      MYSQL_DATABASE: root
    ports:
      - '3306:3306'
  api:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/${APP_NAME}
    ports:
      - '3000:3000'
    links:
      - db
EOF

echo "docker-compose run api rails new . --api --force --database=mysql --skip-bundle"
docker-compose run api rails new . --api --force --database=mysql --skip-bundle

echo "docker-compose run api bundle exec rails webpacker:install"
docker-compose run api bundle exec rails webpacker:install

docker-compose build

# fix config/database.yml
echo "fix config/database.yml"
cat config/database.yml | sed "s/password:$/password: ${MYSQL_PASSWORD}/" | sed "s/host: localhost/host: db/" > __tmpfile__
cat __tmpfile__ > config/database.yml
rm __tmpfile__

echo "docker-compose run api rails db:create"
docker-compose run api rails db:create

Create a new file directly under the project from the editor & copy and paste ↑ and save it as set_up_rails.sh etc. Please edit the app_name etc. that says" Please change freely ".

After saving, change the shell permissions as shown below and execute it (it takes about 5 minutes).

$ chmod 755 set_up_rails.sh  #Change permissions
$ ./set_up_rails.sh          #Setup shell execution
docker pull ruby2.6.6
2.6.6: Pulling from library/ruby
57df1a1f1ad8: Pull complete
71e126169501: Pull complete
1af28a55c3f3: Pull complete
・
・
・

After running the shell, check the status of the container with $ docker-compose ps. There is no problem if it looks like the following.

$ docker-compose ps
     Name             Command       State       Ports
----------------------------------------------------------
app_name_db_1     docker-           Up      0.0.0.0:3306->
                  entrypoint.sh             3306/tcp,
                  mysqld                    33060/tcp

1-3. Container startup & confirmation

Then start the container you built in the shell.

$ docker-compose up
rails-api-handson_db_1 is up-to-date
Creating rails-api-handson_api_1 ... done
Attaching to rails-api-handson_db_1, rails-api-handson_api_1
db_1   | 2020-09-12 08:27:00+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 5.7.31-1debian10 started.
・
・
・
api_1  | * Environment: development
api_1  | * Listening on tcp://0.0.0.0:3000
api_1  | Use Ctrl-C to stop    #← If this comes out, it's OK!

A lot of Docker container logs are displayed, but if the last ** Use Ctrl-C to stop ** is displayed, the container is running and it is OK.

Now that we've confirmed that the container has started, let's access http: // localhost: 3000 and see if it returns a response as Rails. If it looks like the following, the operation of the API container is okay.

スクリーンショット 2020-09-12 17.38.44.png

2. Rails API implementation

Since it is for operation check, let's use simple user data. This time,

User ( id, name, email )

I will implement it with resources like.

2-1. User model creation & DB table preparation

Prepare the User model and table.

Only here, I will introduce ** how to use migration ** and ** how to use Ridgepole ** (both will reflect the same model and schema as a result).

Ridgepole is a tool that allows you to manage your schema without using migration. I will proceed here, but if you are a regular user of migration, there is no problem.

Pattern 1) Model & table creation by migration

Enter the container and create a User model with the generator.

$ docker-compose exec api bash     #Enter the container
# rails g model user name:string email:string
#↑ If you can't, try it via Bundle (# bundle exec rails)....Like)
# exit                             #Exiting the container (after that, entering and exiting the container is not specified.
                                   #Command line "$"And" # "to distinguish. )
$

All you have to do is migrate.

# rake db:migrate

Pattern 2) Model & table creation with Ridgepole

Skip migration and create model.

# rails g model user --skip-migration

Add gem'ridgepole','> = 0.8.0' to the Gemfile directly under the project. There should be no problem under mysql2.

Gemfile


gem 'mysql2', '>= 0.4.4'     #Originally
gem 'ridgepole', '>= 0.8.0'  #Add this

Install the added gem.

# bundle install

Next, create a file called Schemafile.rb indb /(Ridgepole centrally manages the schema here).

Write the following in db / Schemafile.rb and save it.

db/Schemafile.rb


create_table "users", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
  t.string "name"
  t.string "email"
  t.timestamps
end

Reflect the schema file with the ridgepole command.

# bundle exec ridgepole --config ./config/database.yml --file ./db/Schemafile.rb --apply
Apply `./db/Schemafile.rb`
-- create_table("users", {:options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8"})
   -> 0.0716s

2-2. User controller creation

Refer to this article and include the major version of the API (1 part for v1.3) in the URI. However, this article uses the namespace api for the URI.

This is my personal opinion, but I will implement it assuming that it will be indicated by a subdomain etc. instead of specifying api in the URI.

example.com/api/vi/users  #Show as api in URI
api.example.com/vi/users  #Shown by subdomain

Now, let's return to the main subject of controller creation.

Before the controller, create a v1 / indicating the major version, and create a users controller in it.

$ mkdir app/controllers/v1
$ rails g controller v1::users

The contents of the controller file are as follows.

*/v1/users_controller.rb


class V1::UsersController < ApplicationController
  before_action :set_user, only: [:show, :update, :destroy]

  def index
    users = User.order(created_at: :desc)
    render json: { status: 'SUCCESS', message: 'Loaded user', data: users }
  end

  def show
    render json: { status: 'SUCCESS', message: 'Loaded the user', data: @user }
  end

  def create
    user = User.new(user_params)
    if user.save
      render json: { status: 'SUCCESS', data: user }
    else
      render json: { status: 'ERROR', data: user.errors }
    end
  end

  def destroy
    @user.destroy
    render json: { status: 'SUCCESS', message: 'Deleted the user', data: @user }
  end

  def update
    if @user.update(user_params)
      render json: { status: 'SUCCESS', message: 'Updated the user', data: @user }
    else
      render json: { status: 'SUCCESS', message: 'Not updated', data: @user.errors }
    end
  end

  private

  def set_user
    @user = User.find(params[:id])
  end

  def user_params
    params.require(:user).permit(:name, :email)
  end
end

2-3. Routing settings

Make config / routes.rb as follows.

config/routes.rb


Rails.application.routes.draw do
  namespace 'v1' do
    resources :users
  end
end

When ↑ is over, you should be able to see the routing with the rake routes command. It is OK if the following routing is reflected.

# rake routes
  Prefix Verb    URI Pattern               Controller#Action
v1_users GET     /v1/users(.:format)       v1/users#index
         POST    /v1/users(.:format)       v1/users#create
 v1_user GET     /v1/users/:id(.:format)   v1/users#show
         PATCH   /v1/users/:id(.:format)   v1/users#update
         PUT     /v1/users/:id(.:format)   v1/users#update
         DELETE  /v1/users/:id(.:format)   v1/users#destroy

3. Check API operation with Postman

Postman is one of the Web API test client services. Use this to check the operation of the API.

3-1. Postman installation

Install Postman on the official website (https://www.postman.com/). I installed it before, so I don't remember much, but I think it was OK if I registered and proceeded.

After installing Postman, first prepare a user record on the console. I think the following feeling is fine.

# rails c
Loading development environment (Rails 6.0.3.3)
irb(main):001:0> User.create(name: "hoge", email: "[email protected]")

3-2. Get DB User resource with GET

In Postman, specify the GET method and URI as shown below, and press the Send button.

Then, you can check the body of the response returned by the body at the bottom of the screen. If you can do it so far, it is a success to check the operation of the API!

スクリーンショット 2020-09-12 21.15.09.png

3-3. Add DB User resource with POST

Now let's specify the POST method as shown below and include the data in the request Body as well. At this time, make sure that the format is JSON (where" JSON "in the image is orange).

If successful, you can check the registered User record from the response body.

スクリーンショット 2020-09-12 21.21.14.png

If you try to access with the GET method again, you can see the record added by the POST method as well.

スクリーンショット 2020-09-12 21.22.33.png

4. Hit the API with Flutter

The result looks like this

--Display user data in DB when launching app --Register user data with the Add button at the bottom right

Since it is an operation check, it feels like a minimum.

スクリーンショット 2020-09-12 23.28.18.png

4-1. Project creation

Create a Flutter project from ** File> New> New Flutter Project ... **. It is OK if the iPhone or Android simulator (emulator) can be started.

4-2. Enable Http communication

If it is the default, an error will occur when importing a package that performs http, so a little operation is required.

First, add the following to dependencies: of pubspec.yaml.

pubspec.yaml


# ~abridgement~
dependencies:
  http: ^0.12.0   #← Add
  flutter:
    sdk: flutter
# ~abridgement~

Then, the following Flutter command will appear at the top of the editor of Android Studio. This time, select ** Pub get **.

If you get an error around Http in the future work, you may also want to look at the this article that I referred to. I don't know.

スクリーンショット 2020-09-12 22.35.18.png

4-3. Edit main.dart

If you just want to check the operation, rewrite lib / main.dart as follows and it will work.

In addition, I'm a beginner in Flutter, so if you have any suggestions, please leave a comment.

lib/main.dart


import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  MyApp({Key key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Future<GetResults> res;
  @override
  void initState() {
    super.initState();
    res = getUsers();
  }

  // postUser()Register User with. Get User list again in setState
  void _postUser() {
    postUser();
    setState(() {
      res = getUsers();
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Call API with POST and pass JSON parameters',
      theme: ThemeData(
        primarySwatch: Colors.yellow,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('RailsAPI x Flutter'),
        ),
        body: Center(
          child: FutureBuilder<GetResults>(
            future: getUsers(),
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return Text(
                    snapshot.data.message.toString()
                );
              } else if (snapshot.hasError) {
                return Text("${snapshot.error}");
              }
              return CircularProgressIndicator();
            },
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: _postUser,
          tooltip: 'Increment',
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

class GetResults {
  final String message;
  GetResults({
    this.message,
  });
  factory GetResults.fromJson(Map<String, dynamic> json) {
    var datas = '';
    json['data'].forEach((item) => datas += 'id: ' + item['id'].toString() + ', name: ' + item['name'] + ', email: ' + item['email'] + '\n');
    return GetResults(
      message: datas,
    );
  }
}

class PostResults {
  final String message;
  PostResults({
    this.message,
  });
  factory PostResults.fromJson(Map<String, dynamic> json) {
    return PostResults(
      message: json['message'],
    );
  }
}

class SampleRequest {
  final String name;
  final String email;
  SampleRequest({
    this.name,
    this.email,
  });
  Map<String, dynamic> toJson() => {
    'name': name,
    'email': email,
  };
}

Future<GetResults> getUsers() async {
  var url = 'http://127.0.0.1:3000/v1/users';
  final response = await http.get(url);
  if (response.statusCode == 200) {
    return GetResults.fromJson(json.decode(response.body));
  } else {
    throw Exception('Failed');
  }
}

Future<PostResults> postUser() async {
  var url = "http://127.0.0.1:3000/v1/users";                             //API URI
  var request = new SampleRequest(name: 'foo', email: '[email protected]'); //User parameters. You can change it freely
  final response = await http.post(url,
      body: json.encode(request.toJson()),
      headers: {"Content-Type": "application/json"});
  if (response.statusCode == 200) {
    return PostResults.fromJson(json.decode(response.body));
  } else {
    throw Exception('Failed');
  }
}

When you start the simulator, the registered User is returned properly!

スクリーンショット 2020-09-12 21.30.54.png

When you press the Add button at the bottom right, you can see that the User data specified in main.dart has been registered and has been acquired!

スクリーンショット 2020-09-12 21.58.37.png

the end

If you have any questions or suggestions, please leave a comment.

Recommended Posts

[Rails API x Docker] Easy environment construction with shell & operation check with Flutter
Rails6 [API mode] + MySQL5.7 environment construction with Docker
[Docker] Rails 5.2 environment construction with docker
Build Rails (API) x MySQL x Nuxt.js environment with Docker
Rails + MySQL environment construction with Docker
[Environment construction with Docker] Rails 6 & MySQL 8
Rails environment construction with Docker (personal apocalypse)
Build TensorFlow operation check environment with Docker
"Rails 6 x MySQL 8" Docker environment construction procedure for sharing with teams
Rails Docker environment construction
React environment construction with Docker
Easy environment construction of MySQL and Redis with Docker and Alfred
Rails application development environment construction with Docker [Docker, Rails, Puma, Nginx, MySQL]
Build environment with vue.js + rails + docker
Build Rails environment with Docker Compose
Rails on Docker environment construction procedure
Ruby on Rails development environment construction with Docker + VSCode (Remote Container)
GPU environment construction with Docker [October 2020 version]
Building Rails 6 and PostgreSQL environment with Docker
Laravel development environment construction with Docker (Mac)
Create Rails 6 + MySQL environment with Docker compose
Environment construction with Docker (Ubuntu20.04) + Laravel + nginx
[Docker] Development environment construction Rails6 / Ruby2.7 / MySQL8
Laravel + MySQL + phpMyadmin environment construction with Docker
CentOS8.2 (x86_64) + ruby2.5 + Rails5.2 + MariaDB (10.3.17) environment construction
How to build Rails 6 environment with Docker
How to build a Ruby on Rails development environment with Docker (Rails 6.x)
Build a bulletin board API with authentication and authorization with Rails 6 # 1 Environment construction
How to build a Ruby on Rails development environment with Docker (Rails 5.x)
I tried to make a machine learning application with Dash (+ Docker) part1 ~ Environment construction and operation check ~
Introducing Rspec with Ruby on Rails x Docker
A memorandum when building an environment with Ruby3.0 x Rails6.1 x Docker x CentOS Stream
React + Django + Nginx + MySQL environment construction with Docker
[Rails] How to build an environment with Docker
Docker environment construction
Wordpress local environment construction & development procedure with Docker
Easy to display hello world with Rails + Docker
Stable development environment construction manual for "Rails6" with "Docker-compose"
Create an E2E test environment with Docker x Cypress
Environment construction of Rails5 + MySQL8.0 + top-level volumes with docker-compose
Database environment construction with Docker in Spring boot (IntellJ)
[Flutter] Ubuntu 20.04 environment construction
Docker x Rails 6 (memo)
Rails deploy with Docker
I made a development environment with rails6 + docker + postgreSQL + Materialize.
Launch Nuxt.js + Rails API on Docker and try CRUD operation
[Portfolio] Manage site with laravel APP implementation (Docker environment construction)
How to build Rails, Postgres, ElasticSearch development environment with Docker
Create Rails5 and postgresql environment with Docker and make pgadmin available
Pytorch execution environment with Docker
Rails6 development environment construction [Mac]
Rails engineer environment construction ruby2.7.1
Rails environment construction Rails5.2.1 ruby2.5.1 Catalina
MySQL 5.7 (Docker) environment construction memo
API creation with Rails + GraphQL
Run Rails whenever with docker
Redmine (Docker) environment construction memo
Build docker environment with WSL
Docker × Spring Boot environment construction
[Super easy] Ruby environment construction
[Rails / MySQL] Mac environment construction