初心者の方でも進められるよう手順は丁寧してあります。
やること
- RailsAPI × Docker での環境構築
- RailsAPIを軽く実装
- APIクライアントからAPIを叩く(PostmanやFlutterアプリから)※Flutterは任意で
APIコンテナ起動のシェルだけ見たい方 → こちら
一応 RailsAPIのソースコード、Flutterのソースコードも用意(※いじり倒してる可能性あり)
手順
- API側のDockerコンテナ起動まで
- ディレクトリ作成
- シェルでRailsAPIコンテナビルド
- コンテナ起動&確認
- RailsAPI実装
- User モデル作成&DBテーブル用意
- User のコントローラ作成
- ルーティング設定
- PostmanでAPIの動作を確認
- Postmanインストール
-
GET
でDBのUserリソースを取得 -
POST
でDBのUserリソースを追加
- FlutterでAPIを叩く
- プロジェクト作成
- Http通信を行えるようにする
- main.dartを編集
前提
- AndroidStudioでFlutterアプリケーションを起動した状態にする
- SDKなどをダウンロードしてシミュレータを起動できる
- ※ Flutterセットアップはこちらの記事で基本全部いけました(3~40分はかかります)
- Docker, docker-compose のコマンドが使える(
$ docker-compose -v
が動けばOK)
1. API側のDockerコンテナ起動まで
RailsAPIのDockerコンテナを作っていきます。
ただ、1からコマンドを打っていくのは非効率なので、基本的なところはシェルにまとめてあります。
1-1. ディレクトリ作成
app_name
にプロジェクト名を当てはめてディレクトリを用意します。
$ mkdir app_name # APIアプリを置くディレクトリ用意
$ cd app_name
1-2. シェルでRailsAPIコンテナビルド
以下がAPIコンテナのビルドまでやってくれるシェルです。(参考にした記事)
サンプルAPI立ち上げ用のシェル
#!/bin/bash
#config setting#############
APP_NAME="app_name" # ← 自由に変更してください(ディレクトリ名と一緒がいいかも)
MYSQL_PASSWORD="password" # ← 自由に変更してください
###########################
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のセットアップ
RUN curl -o- -L https://yarnpkg.com/install.sh | bash
ENV PATH /root/.yarn/bin:/root/.config/yarn/global/node_modules/.bin:\$PATH
# 作業ディレクトリの作成、設定
RUN mkdir /${APP_NAME}
ENV APP_ROOT /${APP_NAME}
WORKDIR \$APP_ROOT
# ホスト側(ローカル)のGemfileを追加する
ADD ./Gemfile \$APP_ROOT/Gemfile
ADD ./Gemfile.lock \$APP_ROOT/Gemfile.lock
# Gemfileのbundle install
RUN bundle install
ADD . \$APP_ROOT
# gem版yarnのuninstall rails6でエラーになるため
RUN gem uninstall yarn -aIx
#webpackerの設定
#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
エディタからプロジェクト直下に新規ファイルとして作成&↑をコピペし、set_up_rails.sh
などとして保存しましょう。「自由に変更してください」となっているapp_name
などは合わせて編集してください。
保存が終わったら以下のようにシェルの権限を変更し、実行してみましょう(5分くらいかかります)。
$ chmod 755 set_up_rails.sh # 権限の変更
$ ./set_up_rails.sh # セットアップのシェル実行
docker pull ruby2.6.6
2.6.6: Pulling from library/ruby
57df1a1f1ad8: Pull complete
71e126169501: Pull complete
1af28a55c3f3: Pull complete
・
・
・
シェルの実行が終わったら、$ docker-compose ps
でコンテナの状態を確認してみましょう。以下のようになっていれば問題ないです。
$ 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. コンテナ起動 & 確認
次に、シェルでビルドしたコンテナを起動します。
$ 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 # ← これが出たらOK!
Dockerコンテナのログが大量に出ますが、最後のUse Ctrl-C to stop
が出ればコンテナが起動しているのでOKです。
コンテナの起動が確認できたので、http://localhost:3000 にアクセスし、ちゃんとRailsとしてレスポンスを返してくれるか確認しましょう。以下のようになっていればAPIコンテナの動作は大丈夫です。
2. RailsAPI実装
動作確認用なので単純なユーザデータにしましょう。今回は、
User ( id, name, email )
みたいなリソースで実装してみます。
2-1. Userモデル作成 & DBテーブル用意
Userモデルとテーブルを用意します。
ここだけマイグレーションを使う方法とRidgepoleを使う方法に分けて紹介します(どちらも結果として同じモデル、スキーマが反映されます)。
Ridgepoleは、マイグレーションを使わずにスキーマを管理できるツールです。僕はこちらで進めていきますが、普段からマイグレーションを使っている方であればそちらで問題ありません。
パターン1) マイグレーションでモデル&テーブル作成
コンテナに入り、ジェネレータでUserモデルを作成。
$ docker-compose exec api bash # コンテナに入る
# rails g model user name:string email:string
# ↑ 出来なければ以後はBundle経由でやってみましょう( # bundle exec rails .... みたいな )
# exit # コンテナから出る(以後はコンテナの出入りは明記しません。
# コマンドラインの「$」や「#」で区別します。)
$
あとはマイグレーションをかけるだけです。
# rake db:migrate
パターン2) Ridgepoleでモデル&テーブル作成
マイグレーションをスキップしてモデルを作成。
# rails g model user --skip-migration
プロジェクト直下にあるGemfile
にgem 'ridgepole', '>= 0.8.0'
を追記します。mysql2の下とかで問題ないでしょう。
gem 'mysql2', '>= 0.4.4' # 元々ある
gem 'ridgepole', '>= 0.8.0' # これを追加
追記したgemをインストール。
# bundle install
次にdb/
のなかに、Schemafile.rb
というファイルを作成します(Ridgepoleではスキーマをここで一元管理する)。
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
ridgepoleのコマンドでスキーマファイルを反映。
# 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 のコントローラ作成
こちらの記事を参考に、URIにAPIのメジャーバージョン(v1.3なら1の部分)を含めます。ただこの記事だと、URIにapi
というネームスペースを使っています。
これは僕個人の意見ですが、URIにapi
と明記する方法ではなく、サブドメインなどで示すことを想定して実装します。
example.com/api/vi/users # URIでapiだと示す
api.example.com/vi/users # サブドメインで示す
では本題のコントローラ作成に戻ります。
コントローラの前に、メジャーバージョンを示すv1/
を作成し、その中でusersコントローラを作成します。
$ mkdir app/controllers/v1
$ rails g controller v1::users
コントローラファイルの中身は以下の感じです。
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. ルーティング設定
config/routes.rb
を以下のようにします。
Rails.application.routes.draw do
namespace 'v1' do
resources :users
end
end
↑が終わると、rake routes
コマンドでルーティングが確認できるはずです。以下のようなルーティングが反映されていればOKです。
# 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. PostmanでAPIの動作を確認
Postmanとは、WebAPIのテストクライアントサービスのひとつです。APIの動作確認にはこれを使います。
3-1. Postmanインストール
公式サイトで、Postmanをインストールします。前にインストールしたのであまり覚えていませんが、登録して進めていけばOKだった気がします...。
Postmanインストール後、まずはコンソールで適当にUserのレコードを用意しておきましょう。以下のような感じでいいかと思います。
# rails c
Loading development environment (Rails 6.0.3.3)
irb(main):001:0> User.create(name: "hoge", email: "hoge@example.com")
3-2. GET
でDBのUserリソースを取得
Postmanで以下のようにGETメソッド
、URI
を指定し、Send
ボタンを押します。
すると、画面下のBodyで返ってきたレスポンスのBodyが確認できます。ここまでできていればAPIの動作確認としては成功です!
3-3. POST
でDBのUserリソースを追加
今度は以下のように、POSTメソッド
を指定し、リクエストBodyの中にもデータを含めてみましょう。この時、形式がJSON
になっているか確認しましょう(画像の"JSON"とオレンジになっているところ)。
成功すれば、登録されたUserのレコードがレスポンスBodyから確認出来ます。
ここで先ほどのGETメソッド
でのアクセスをもう一度行ってみると、POST
メソッドで追加したレコードも一緒に確認できます。
4. FlutterでAPIを叩く
結果こんな感じ
- アプリを起動したらDBにあるユーザのデータを表示
- 右下の
Add
ボタンでユーザデータの登録
動作確認なのでミニマムな感じです。
4-1. プロジェクト作成
File > New > New Flutter Project...
からFlutterプロジェクトを作成します。iPhoneやAndroidのシミュレータ(エミュレータ)などが起動できていればOKです。
4-2. Http通信を行えるようにする
デフォルトだとhttpを行うパッケージのimportでエラーを起こすので少し操作が必要になります。
まず、pubspec.yaml
のdependencies:
に以下のように追記します。
# ~ 省略 ~
dependencies:
http: ^0.12.0 # ← 追加する
flutter:
sdk: flutter
# ~ 省略 ~
すると、Android Studioのエディタの上の方に、以下のようなFlutterコマンドが出てきます。今回はPub get
を選択します。
もしこの先の作業でHttp周りのエラーが出るようであれば、僕が参考にしたこちらの記事も見てみるといいかも知れません。
4-3. main.dartを編集
動作確認をしたいだけであれば、lib/main.dart
を以下のように書き換えれば動きます。
なお、僕はFlutterは初歩の初歩なので物申すことがあればコメントにお願いします。
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()でUserを登録。setState内で再度User一覧を取得
void _postUser() {
postUser();
setState(() {
res = getUsers();
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'APIをPOSTで呼び出しJSONパラメータを渡す',
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: 'foo@example.com'); // Userのパラメータ。自由に変更してOK
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');
}
}
シミュレータを起動すると、、登録されたUserがきちんと返ってきてますね!
右下のAddボタンを押すと、、main.dart
で指定したUserデータが登録され、取得できているのも確認できました!
終わり
質問やご指摘があればコメントにお願いします。