今回作成する Webサーバ の構成は,
サーバ
OS : ubuntu 22.04 LTS
Webサーバ : nginx
APサーバ : ruby ( sinatra )
DB : mysql
アプリ : todoList 管理アプリ
開発環境
mac
です。少しずつ機能を追加していく予定です。
さて、第1回で疎通した ubuntu – nginx – ruby ( sinatra ) – mysql の webアプリサーバ で todoList を作ってみます。
開発機は mac で、ソース管理は visual studio code ( 以下 vscode ) と git を使います。
1 開発環境 ( vscode , git ) を準備します。
vscode は公式ページからダウンロードしてください。mac は git がプレインストールされています。それを使いましょう。
( input )
@dev:~$ git -v
( output )
git version 2.39.3 (Apple Git-145)
2.作業フォルダをつくります。
開発機で作業ディレクトリを作成し、その場所を vscode で開きます。vscode にと git ( github ) をリンクしておけば、自動で git を使ってバージョン管理をしてくれます。
今回作成するアプリは以下のような表示の todoList です。
構成は
index.html , styles.css , main.js : /var/www/html/ に配置
app.rb : ~/ruby-work/ に配置
@server(ssh):~/ruby-work$ ruby app.rb で ルビーを実行。
開発機のブラウザで http://192.168.0.11 ( うちの場合 ) で表示します。
まずは index.html と css を入力します。大きく分けて、①入力エリア、②未達成エリア、③達成済エリアに分けています。データベースから取得した値を動的に生成するために必要な箇所に id を付与するなどして備えます。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<link rel="stylesheet" href="styles.css">
<title>todoList app</title>
</head>
<body>
<h1>todoList ver1.0</h1>
<div id="input-area">
<input id="todo-input" placeholder="todoを入力" type="text" />
<button id="add-button">追加</button>
</div>
<h2>未達成 todo 一覧</h2>
<table id="incomplete-area" border="1">
<thead>
<tr>
<th>ID</th>
<th>todo</th>
<th>作成日時</th>
<th>操作</th>
</tr>
</thead>
<tbody id="mi-tbl-body">
<tr>
<td></td>
<td>error</td>
<td></td>
<td><button id="comp-btn">達成</button><button id="dlt-btn">削除</button></td>
</tr>
</tbody>
</table>
<h2>達成済 todo 一覧</h2>
<table id="complete-area" border="1">
<thead>
<tr>
<th>ID</th>
<th>todo</th>
<th>作成日時</th>
<th>操作</th>
</tr>
</thead>
<tbody id="zumi-table-body">
<tr>
<td></td>
<td>ERROR</td>
<td></td>
<td><button id="comp-btn">達成</button><button id="dlt-btn">削除</button></td>
</tr>
</tbody>
</table>
<script src="main.js"></script>
</body>
</html>
body {
font-family: sans-serif;
}
input {
border-radius: 16px;
border: none;
padding: 6px 16px;
outline: none;
}
button {
border-radius: 16px;
border: none;
padding: 4px 16px;
}
button:hover {
background-color: #ff7fff;
color: #fff;
cursor: pointer;
}
li {
margin-right: 8px;
}
#input-area {
background-color: #c1ffff;
width: 600px;
height: 30px;
border-radius: 8px;
padding: 8px;
margin: 8px;
}
#incomplete-area {
background-color: #c6ffe2;
width: 600px;
min-height: 200px;
padding: 8px;
margin: 8px;
border-radius: 8px;
}
#complete-area {
background-color: #ffffe0;
width: 600px;
min-height: 200px;
padding: 8px;
margin: 8px;
border-radius: 8px;
}
.title {
text-align: center;
margin-top: 0;
font-weight: bold;
color: #666;
}
次に javaScript を記述します。javaScript がコールされた時にまず main() を実行します。あとは html でイベントごとに関連付けした関数がコールされた時の動作を記述していきます。ruby とのやりとりは fetch( ‘url’ ) で行っています。この url は nginx のリバースプロキシの設定で sinatra が受け取るように設定してあります。
const miTableBody = document.getElementById( 'mi-tbl-body' )
const zumiTableBody = document.getElementById( 'zumi-table-body' )
const listTodoInput = document.getElementById( 'todo-input' )
const listAddButton = document.getElementById( 'add-button' )
async function loadTasks() {
const response = await fetch( '/api/list' )
const responseBody = await response.json()
const tasks = responseBody.tasks
while( miTableBody.firstChild ) {
miTableBody.removeChild( miTableBody.firstChild )
}
while( zumiTableBody.firstChild ) {
zumiTableBody.removeChild( zumiTableBody.firstChild )
}
tasks.forEach( ( tasks ) => {
const trRowElement = document.createElement( 'tr' )
const tdIdElement = document.createElement( 'td' )
tdIdElement.innerText = tasks.id
const tdContentElement = document.createElement( 'td' )
tdContentElement.innerText = tasks.todo
const tdCreatedAtTdElement = document.createElement( 'td' )
tdCreatedAtTdElement.innerText = tasks.createdAt
const td2Element = document.createElement( 'td' )
const button1Element = document.createElement( 'button' )
const button2Element = document.createElement( 'button' )
if( tasks.todoState == 0 ) {
button1Element.innerText = "完了"
button1Element.className = "comptt-btn"
button1Element.addEventListener( "click" , { id: tasks.id , handleEvent: completeTask } )
button2Element.innerText = "削除"
button2Element.id = tasks.id
button2Element.addEventListener( "click" , { id: tasks.id , handleEvent: deleteTask } )
}else{
button1Element.innerText = "戻す"
button1Element.className = "undo-btn"
button1Element.addEventListener( "click" , { id: tasks.id , handleEvent: undoTask } )
button2Element.innerText = "削除"
button2Element.className = "dele-btn"
button2Element.addEventListener( "click" , { id: tasks.id , handleEvent: deleteTask } )
}
td2Element.appendChild( button1Element )
td2Element.appendChild( button2Element )
trRowElement.appendChild( tdIdElement )
trRowElement.appendChild( tdContentElement )
trRowElement.appendChild( tdCreatedAtTdElement )
trRowElement.appendChild( td2Element )
if( tasks.todoState == 0 ) {
miTableBody.appendChild( trRowElement )
}else{
zumiTableBody.appendChild( trRowElement )
}
})
}
async function registerTask() {
const todo = listTodoInput.value
const requestBody = {
todo: todo ,
todoState: 0
}
await fetch( '/api/list' , {
method: 'POST' ,
body: JSON.stringify( requestBody )
})
await loadTasks()
}
async function deleteTask( e ) {
const requestBody = {
id: this.id
}
await fetch( '/api/tasks_delete' , {
method: 'POST' ,
body: JSON.stringify( requestBody )
})
await loadTasks()
}
async function undoTask( e ) {
const requestBody = {
id: this.id
}
await fetch( '/api/tasks_undo' , {
method: 'POST' ,
body: JSON.stringify( requestBody )
})
await loadTasks()
}
async function completeTask( e ) {
const requestBody = {
id: this.id
}
await fetch( '/api/tasks_complete' , {
method: 'POST' ,
body: JSON.stringify( requestBody )
})
await loadTasks()
}
async function main() {
listAddButton.addEventListener( 'click' , registerTask )
await loadTasks()
}
main()
最後に ruby を記述します。get または post で受け取った url ごとに処理内容を記述していきます。
一般的な crud ( create , read , update , delete ) に対応しています。
require 'sinatra'
require 'mysql2'
get '/api/hello' do
{
message: 'Hello sinatra'
}.to_json
end
get '/api/list' do
client = connect
result_set = client.query( 'select id , todo , todoState , created_at from todoList' )
tasks = result_set.map do |row|
{
id: row['id'] ,
todo: row['todo'] ,
todoState: row['todoState'] ,
createdAt: row['created_at']
}
end
client.close
{
tasks: tasks
}.to_json
end
post '/api/list' do
request_body = JSON.parse request.body.read
todo = request_body['todo']
client = connect
statement = client.prepare( 'insert into todoList( todo , todoState ) values ( ? , 0 )' )
statement.execute( todo )
client.close
end
post '/api/tasks_complete' do
request_body = JSON.parse request.body.read
id = request_body['id']
client = connect
statement = client.prepare( 'update todoList set todoState = 1 , created_at = current_timestamp where id = ?' )
statement.execute( id )
client.close
end
post '/api/tasks_undo' do
request_body = JSON.parse request.body.read
id = request_body['id']
client = connect
statement = client.prepare( 'update todoList set todoState = 0 , created_at = current_timestamp where id = ?' )
statement.execute( id )
client.close
end
post '/api/tasks_delete' do
request_body = JSON.parse request.body.read
id = request_body['id']
client = connect
statement = client.prepare( 'delete from todoList where id = ?' )
statement.execute( id )
client.close
end
def connect
Mysql2::Client.new(
:host => "localhost",
:database => "todoList",
:username => "todoList",
:password => 'password',
:connect_timeout => 5
)
end
これらのファイルが開発環境で入力できたら、サーバに転送します。転送には scp コマンドを利用します。
@dev:~$ scp ruby-work/app.rb yosuke@192.168.0.11:~/ruby-work/
@dev:~$ scp html/index.html html/styles.css html/main.js yosuke@192.168.0.11:~/
@server(ssh):~$ sudo mv index.html styles.css main.js /var/www/html
それぞれのファイルを所定の場所に配置して ruby app.rb を実行し、開発機のサーバから確認してください。
コメント