XHack勉強会 第4回『Ruby「ゼロから」画像共有サービスを作る』で学んだこと
今回は掲示板アプリ作成に伴うデータベースの情報を
ブラウザに表示をすることを学びましたので、
その情報をまとめました。
このノートでざっくり分かること。
【設定内容をGithub上にアップロードしない方法】
■設定内容をアップロードする危険
前回作成した、Github&Herokuにアップロードしたファイルには
DBの接続情報が書かれていた。
しかし、そのアップロードしたGitHubに接続すれば
誰でもその設定内容が確認出来て、データベースに接続出来てしまう。
これはセキュリティ上良くないので"dotenv"というgemfileを利用し、
データベース情報を記載した「.env」ファイルを作成。
GitHubにアップロードするファイルには直接情報を書かずに管理する。
■"dotenv"の導入方法
まずGemファイルのインストールをするため、「Gemfile」に下記を追記。
gem 'dotenv'
$ bundle install --path vendor/bundle
インストール後、プロジェクトディレクトリのルートに
「.env」ファイルを作成し下記を記載する。
DATABASE_HOST='hoge'
DATABASE_USER='hoge'
DATABASE_PASSWORD='hoge'
DATABASE_NAME='hoge'
DATABASE_PORT='hoge'
そして「app.rb」に使用Gemの追記と
元々書いていたDB接続情報を下記に変更。
# 使用Gemの記載
require "dotenv/load"
# データベース接続の記載変更
host: "hoge",
user: "hoge",
password: "hoge",
dbname: "hoge",
port: "hoge"
↓
host: ENV['DATABASE_HOST'],
user: ENV['DATABASE_USER'],
password: ENV['DATABASE_PASSWORD'],
dbname: ENV['DATABASE_NAME'],
port: ENV['DATABASE_PORT']
これで「app.rb」から「.env」にデータベース接続情報を読みに行く。
接続も問題なく行える。
gemfileを変更後は、サーバーを再起動しないとGemfileを読み込まない。
(正確にはgemfileだけではないみたい)
■Githubにアップロードしない方法
「app.rb」上からはDB接続情報は消えたが、
「.env」ファイルをgitでアップロードしてしまう恐れもある。
それを防ぐために「.gitignore」ファイルという物を作成する。
これをプロジェクトディレクトリのルートに置いておけば、
その中に記載されたファイルは"git add"されなくなる。
下記に記載サンプルとして書いておく。
.env
vendor/
.DS_Store
・「.DS_Store」ファイルについて
アイコンの位置や表示設定などのフォルダ表示設定に関するメタデータを記録するための隠しファイル。
ディレクトリ作成した際に作成される場合がある。
消しても特に問題はない。
ただ共同開発で他の人がGitHubからデータをcloneした時に、
どういう接続情報を用意すればいいのか分からないことになる。
なので「.env.example」というファイルを作成し、
そこに接続に必要な情報を書いてgithubにあげておく。
・本当に一例だけど、例えば下記のような感じ。
DATABASE_HOST="localhost"
DATABASE_NAME="ccc"
DATABASE_USER="root"
DATABASE_PASSWORD=""
DATABASE_SOCKET="/tmp/mysql.sock"
【スクレイピングしたデータをDBに保存する方法】
前に作成したスクレイピングプログラムを使い、
今度は取得&加工データをDBに保存していく。
■スクレイピングにて要素の抽出
今回はクラウドワークスさんの下記部分をスクレイピングにて抽出する。
①=title、②=category、③=name、④=price とする。
タイトル部分の抽出方法をサンプルで書いておく。
まずGoogleChromeにてページを開き、デベロッパーツールを起動。
取得したい要素の範囲を選択すると、デベロッパーツール内の
Elementsタブ内にて、要素が展開される。
そして更にElements内で要素にカーソルを合わせていき、
取得したい部分まで開いていく。
今回取りたい「title①」は、"item_title"クラス内の"aタグ"のHTML内文章。
こういう風に各部分を抽出していく。
取り出した部分が余計な文字や空白が含まれているなら、
それを削除するプログラムも書いていく。
「scraping.rb」を作成し、下記を記載。
require 'nokogiri'
require 'open-uri'
require 'pg'
require 'dotenv/load'
url = '取得したいページのURL'
charset = nil
# 不要な改行、スペース、文字列を削除するメソッド(正規表現にて、マッチした文字や空白を""に置き換えてる)
def text_format(str)
str.strip.gsub(/(\r\n|\r|\n|\f|......>|.....>)/,"")
end
html = open(url) do |f|
sleep(2) # サイトに負荷をかけないように、取得間隔に制限をかけている
charset = f.charset # 文字種別を取得
f.read # htmlを読み込んで変数htmlに渡す
end
# 指定したHTML内の部分を抽出
doc = Nokogiri::HTML.parse(html, nil, charset)
doc.css('.item').each do |data|
title = text_format(data.css('.item_title a').inner_html) # 先程の説明の箇所
category = text_format(data.css('.sub_category a').inner_html)
name = text_format(data.css('.user-name a').inner_html)
price = text_format(data.css('.amount').inner_html).gsub(" ","")
# DBへ接続
connect = PG::connect(
host: ENV['DATABASE_HOST'],
user: ENV['DATABASE_USER'],
password: ENV['DATABASE_PASSWORD'],
dbname: ENV['DATABASE_NAME'],
port: ENV['DATABASE_PORT']
)
# データ挿入
@data = connect.exec("
INSERT INTO work_board (title, category, name, price)
VALUES ('#{title}', '#{category}', '#{name}', '#{price}');
")
end
これを起動すれば下記のように取得し、DBへデータを挿入できる。
【掲示板アプリ投稿フォーム作成】
■rubyファイルにてテーブル作成
ここから掲示板サービスを作っていく。
まずデータベースの作成。
掲示板作成に必要なデータを考えて、項目を書いておく。
今回は下記のようにデータベースを作成する。
・テーブル名:board_contents
・カラム1:id
・カラム2:name
・カラム3:comment
・カラム4:commented_at
require "pg"
require 'dotenv/load'
# DB接続情報
connect = PG::connect(
host: ENV['DATABASE_HOST'],
user: ENV['DATABASE_USER'],
password: ENV['DATABASE_PASSWORD'],
dbname: ENV['DATABASE_NAME'],
port: ENV['DATABASE_PORT']
)
# DBのテーブル作成
@data = connect.exec("
CREATE TABLE board_contents (
id serial,
name text,
comment text,
commented_at timestamp,
PRIMARY KEY (id)
);")
connect.finish
この構文を今回は「create.rb」に書いて、
ターミナルにて下記コマンドで起動。
$ bundle exec ruby create.rb
PG Commanderにて作成されてることを確認。
■フォームの見た目作成
フォームから入力にて、データを挿入する為に
「index.erb」のbodyタグ内に下記を記入。
〜省略〜
<form action="/comments" method="post"> # このURLに飛ぶ
名前:<input type="text" name="name"> # 入力をnameへ
<input type="text" placeholder="コメント入力" name="comment"> # 入力をcommentへ
<input type="submit" value="送信する"> # 上記入力を確定
</form>
〜省略〜
ちゃんとデータが表示されるか「app.rb」にて下記を追記。
post '/comments' do # 「index.erb」の送信ボタンを押すとここを実行する。
p "#{params["name"]}#{params["comment"]}"
end
ブラウザにて入力フォームに入れた内容が表示されてればOK。
■POSTされたデータをデータベースに保存
今度は入力したデータをデータベース内に保存する為に、
「app.rb」のpost内を下記に書き換える。
〜省略〜
post '/comments' do # POSTにしてデータ送信
@name = params["name"] # params["hoge"]でデータ取得
@comment = params["comment"]
conn = PG::connect(
host: ENV['DATABASE_HOST'],
user: ENV['DATABASE_USER'],
password: ENV['DATABASE_PASSWORD'],
dbname: ENV['DATABASE_NAME'],
port: ENV['DATABASE_PORT']
)
# DBにデータを挿入する
@data = conn.exec("
INSERT INTO board_contents (name, comment)
VALUES ('#{@name}', '#{@comment}');
")
redirect "/" # いちいち元のページに戻るのは面倒なので、処理が終わったら自動的に戻る
end
そして「index.erb」にて、挿入されたデータを表示出来るように下記を
bodyタグ内に追記。
<ul>
<% @data.each do |data| %>
<li><%= data["id"] %>/<%= data["name"] %>/<%= data["comment"] %></li>
<% end %>
</ul>
そして再度入力フォームに打ち込み送信して、
下記のように表示されていればOK。
これでブラウザ上でDBのデータを表示できる。
【モジュール化の方法】
■モジュール化とは
ファイル内で何度も使われている処理を別ファイルにまとめること。
必要な時に関数として呼び出す事が出来るので、結果として
・記述量を減らすことが出来る。
・コード量が減ることにより、コードが見やすくなる。
今回はデータベースの接続を復数行っているので、
そのモジュール化を行う。
■モジュール化する手順
まずどの部分をまとめれるのか確認。
これをまとめるので、今回は「Mydatabase.rb」を作成し下記を記載。
require 'pg'
require 'dotenv/load'
module Mydatabase # こことファイル名を同一にしておく。
def self.exec(sql) # self付けてクラスメソッドにしないと外部から呼び出せない。
conn = PG.connect(
host: ENV['DATABASE_HOST'],
user: ENV['DATABASE_USER'],
password: ENV['DATABASE_PASSWORD'],
dbname: ENV['DATABASE_NAME'],
port: ENV['DATABASE_PORT']
)
data = conn.exec(sql) # 外部からのSQLコマンドを引数として入力する。
conn.finish
data # ここで呼び出したデータが返り値として外部から参照される。(最後に評価した式)
end
end
そして「app.rb」からDB接続の情報を抜き取り、下記のように記述。
require 'sinatra'
require 'sinatra/reloader'
require 'pg'
require 'dotenv/load'
require './mydatabase' # moduleの読み込み(「./」はこの「app.rb」の同一ディレクトリということ)
get '/' do
@data = Mydatabase.exec("select * from board_contents;")
erb :index # モジュール名.関数名(self無し)にて呼び出し、引数で()内を渡し結果を受け取る。
end
post '/comments' do
name = params["name"]
comment = params["comment"]
sql = "INSERT INTO board_contents (name, comment)
VALUES ('#{name}', '#{comment}');"
@data = Mydatabase.exec(sql) # 引数自身を変数にしてる。
redirect '/'
end
と書くが、少し分かりづらいかもなので下記ざっくりイメージ参照。
こういう風にモジュールを呼び出す事が出来て、
元のファイルはコンパクトで見やすくなった。
モジュール化ではなくとも事前に変数にコマンドや関数を入れておいて、
それを使い回すことでも同様の効果が見られるので、
今後は念頭に置いておこうと思う。
今回最後の方は初めて「おっとやべぇ」と脳が付いていかない
瞬間がありましたが、まとめて見ると以外と単純なんだなぁって再認識。
ただ実務ではこれが複雑に絡みあうので、基本忘れず一つ一つ
紐解いていけるように勉強怠らず頑張ろう。