The procedure is carefully done so that even beginners can proceed.
-** RailsAPI × Environment construction with Docker ** -** Lightly implement Rails API ** -** Hit API from API client ** (from Postman or Flutter app) * Flutter is optional
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)
--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
--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)
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.
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
Below is a shell that even builds the API container. (Reference article)
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
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.
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.
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.
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
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
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
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
Postman is one of the Web API test client services. Use this to check the operation of the API.
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]")
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!
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.
If you try to access with the GET method
again, you can see the record added by the POST
method as well.
--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.
Create a Flutter project from ** File> New> New Flutter Project ...
**. It is OK if the iPhone or Android simulator (emulator) can be started.
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.
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!
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!
If you have any questions or suggestions, please leave a comment.
Recommended Posts