はじめに
今年は就活なので、前々から開発&運営している個人ブログの記事投稿システムをポートフォリオとして作ってみました。
この記事ではその記事投稿システムの簡易版を、解説しつつ一から構築していきたいと思います!
一応初心者向けという事で、細かいところもある程度詳しく書いてます。
※長いので3つに分けてます
対象読者
- Sinatra入門者
- Rails入門者
- 簡易的なブログシステムを作ってみたい人
- ある程度ちゃんとした構成でSinatraの環境構築をしたい人
- Railsがよく分からなくて、もう少し基礎からWebアプリを作りたい人
※RailsやSinatraなどのFWを一度は触った事がある前提としてます
Sinatraとは
Rubyで作成されたオープンソースのWebアプリケーションフレームワークである。
他の著名なRubyで作成されたWebアプリケーションフレームワークであるRuby on Railsなどは、Model View Controller(MVC)の考え方に基づいた設計となっている。一方SinatraはMVCに基づかない設計で作成されており、小さく、柔軟性があるプログラミングが可能となるよう意識されている。
Sinatra - Wikipedia
Sinatraでの開発手法としてクラシックタイプとモジュラータイプというものがありますが、今回はクラシックタイプで進めます。
なぜSinatra?
当初はRailsで作ろうと思っていたのですが、Railsがよく分からなかった&&もう少し基礎から勉強したい&&作るものに対してRailsは重装備すぎる、ということでSinatraにしました。
ほとんどの情報をネットで得たため、至らない点やベストじゃない点、間違ってる所があるかと思います。もしそういう所があればコメントで教えてくれると助かります。
目標
想定するシナリオとしては、カテゴリーを持った記事をブラウザから投稿できる記事投稿サイトを作成します。
要は管理画面のあるブログみたいな感じです。
- 記事投稿画面を作成する。
- 記事のサムネイル画像も投稿できるようにする。
- 記事はmarkdownで書き、それをHTMLに変換してプレビュー・投稿を行う。
- 簡単にバリデーションも追加する。
- ログイン画面を作成する。
- 記事を投稿できるのは決められたユーザ(今回は一人しか想定しないためユーザは一人)のみにし、ログイン処理を作る。
- ログアウト処理も作る。
- CSRF対策を実装する。
- 画面遷移やセッション、SQLやActiveRecord、そのほかGemやRack・Rakeなどの基本的な使い方や導入に関する理解などが深まるとうれしい。
公開するところまではやりません。
環境
- Mac 10.14.5
- Ruby 2.5.3
- MySQL 8.0.17
- Sinatra 2.0.7
- ActiveRecord 5.2.3
環境構築
最初の難関・環境構築です。
単に動かすだけでなく、実用を想定してある程度しっかりした構成を最初に組んでいきます。
今回はMySQLを使うのですが、自分は大昔に入れてしまったため、インストール手順はMac へ MySQL を Homebrew でインストールする手順 - Qiitaなどを見て入れてください。
次に、プロジェクトフォルダを作成し、Gemfileを生成します。
mkdir Article-Post
cd ./Article-Post
bundle init # Gemfileを生成
Gemfileができたら、今回使うgemたちを記述します。
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
gem 'activerecord'
gem 'bcrypt'
gem 'mysql2'
gem 'rake'
gem 'redcarpet'
gem 'sinatra'
gem 'sinatra-activerecord'
gem 'sinatra-contrib'
gem 'slim'
Sinatra + Slim + MySQLという編成にActiveRecordを乗せてやって行きたいと思います。
説明が必要そうなやつだけ軽く紹介
- sinatra 本体
- sinatra-contrib 開発時に便利な機能とかを色々追加できる
- 今回はファイルを変更した時にサーバーを再起動しなくてよくするreloaderというやつを使います
- redcarpet markdown記法で入力されたものをHTMLに変換してくれるすごいやつ
- Rake Rubyで処理内容を定義できるビルドツール(今回だとあんまり活用できてないかも)
Gemfileに記述したgemをインストールします。
sudo bundle install --path vendor/bundle
オプションでpathを指定するとインストール場所をその指定したpath配下になります。
pathを指定する理由は、システムにインストールし過ぎるとgemがカオスになるらしく、bundlerの推奨環境としてvender/bundle以下に個別に管理するようにしたほうが良いらしいです。
mysql2のインストールでエラーが起きた場合
An error occurred while installing mysql2 (0.5.2), and Bundler cannot continue. Make sure that gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/' succeeds before bundling.
というエラーが出たらとりあえず言われた通りに下を実行
sudo gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'
今度は
Don't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load
ld: library not found for -lssl clang: error: linker command failed with exit code 1 (エラー原因っぽい場所を抜粋)
と出る
ぐぐるとこのサイトが出てきました。https://qiita.com/akito19/items/e1dc54f907987e688cc0
つまりパスを通せってことらしいです。(超解釈)
パスを通すために下のコマンドを実行
bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib --with-cppflags=-I/usr/local/opt/openssl/include"
そしてもう一度インストールすると出来ます。
sudo bundle install --path vendor/bundle
app.rbを作成
次に、メインの処理などを記述するrubyファイルを作成します。
touch app.rb
このapp.rbに様々な(今回では主にコントローラ)処理を書いて行きます。ファイル名は任意です。
SinatraはRailsなどと違ってMVCに基づいていないので、1つのファイルに全ての処理を記述することは問題ではありません。
が、コード量が多くなってくると流石に見通しが悪くなるので今回はMVCを分けてやって行きます。
まず動作確認のためのサンプルをapp.rbに記述しておきます。
get '/' do
'hello world by sinatra'
end
これは、webサーバーを立てた時に、例えばhttp//localhost:9292/にアクセスした場合のGETリクエストに対応する処理となります。
このへんの基本的な説明が分からない人はREADMEとSinatra 入門(Qiita)に目を通しておくと良いです。
次に、config.ruファイルを用意します。
config.ruとは
config.ruとは、Rackのrackupコマンドを使ってサーバ起動を行う場合の設定ファイルになります。
今回は、gemの読み込みやDBの設定ファイルの読み込みなどを記述しておきます。
Rackとは
RubyによるWebアプリケーション開発のHTTP送受信処理を担当するモジュール(gem)で、Ruby on Railsを始めとする多くのWebフレームワークの一番下のレベルで利用されているもの。
Rack解説 - Rackの構造とRack DSL
touch config.ru
require 'rubygems'
# bundleに入れたgemを全てrequire
require 'bundler'
Bundler.require
# sinatra-contribのオートリロードしてくれるやつはrequireする必要がある
require 'sinatra/reloader'
# app.rbもrequireする
require './app.rb'
run Sinatra::Application
ここまでで実行できる最低限の環境が揃ったのでサーバーを立ち上げてみます。
ポート番号は9292です。
bundle exec rackup config.ru
ここまでのディレクトリ構成
.
├── Gemfile
├── Gemfile.lock
├── app.rb
├── config.ru
└── vendor
└── bundle
└── ruby
ちなみに
sinatraを起動するだけならもっと簡単で、config.ruファイルを使わずにapp.rbファイルでsinatraをrequireし、
require 'sinatra'
get '/' do
'hello world by sinatra'
end
bundle exec ruby app.rb
で起動できます。
このように、config.ruファイルを使わずapp.rbに直接gemをrequireしたりDB設定を丸ごと書いて動かす事はできるのですが、見にくいのであんまりオススメしません。
データベース関連の作成
Active Recordの機能であるマイグレーションを用いてテーブル作成を行っていきます。
マイグレーションとは
マイグレーション (migration) はActive Recordの機能の1つであり、データベーススキーマを長期にわたって安定して発展・増築し続けることができるようにするための仕組みです。マイグレーション機能のおかげで、Rubyで作成されたマイグレーション用のDSL (ドメイン固有言語) を用いて、テーブルの変更を簡単に記述できます。スキーマを変更するためにSQLを直に書いて実行する必要がありません。
Active Record マイグレーション
要するにSQLを直に書かなくてもRubyとDSL(ドメイン固有言語)でテーブルの作成や変更ができるよ、ということです。
変更毎にその内容がファイルとして生成されるので、履歴確認としても便利だなーと思います(小並感)
今回、そのマイグレーション用ファイルを生成するためのRakefileを作成します。
(Rakefikeとはrakeタスクの為の定義を記述するファイル...という事ですが、自分もよく分かってません。今回はあくまでDBの設定を書いてマイグレーションを行えるようにする、という目的のために作成しています。)
touch Rakefile
require 'sinatra/activerecord'
require 'sinatra/activerecord/rake'
Rakefileでこの二つのパッケージをrequireすることにより、rakeタスクというものが使えるようになります。
rakeタスクの確認は bundle exec rake -T
でできます。
このrakeタスクの中にある rake db:create_migration
を使ってテーブルの作成(マイグレーション)を行います。
ですが、まだそのテーブルを格納するためのデータベースの作成を行っていないので、そちらを先にやります。
データベースを作成
MySQLで今回使うデータベースを作成します。
mysql.server start # MySQL起動
mysql -u root # ログイン
起動後、以下のSQLを発行
mysql> CREATE DATABASE articles4; # articles4という名前のDBを作成
mysql> USE articles4; # それを使うよーと宣言
database.ymlを作成
次に、データベースとの接続情報を記述するdatabase.ymlを作成します。
touch database.yml
development:
adapter: mysql2
database: articles4
host: localhost
username: root
password:
encoding: utf8
database:という所に先程作成したデータベース名を入れます。
今回はとりあえずdevelopmentだけ設定します。ちなみにパスワードは無しです。
次に、今作ったymlファイルをconfig.ruとRakefileでActiveRecordに読み込ませます。
ついでに、DBのタイムゾーンをTokyoに変更。
# 〜省略〜
require './app.rb'
# 追加↓
# database.ymlを読み込んでくれ〜
ActiveRecord::Base.configurations = YAML.load_file('database.yml')
# developmentを設定
ActiveRecord::Base.establish_connection(:development)
# タイムゾーンを東京にする
Time.zone = 'Tokyo'
ActiveRecord::Base.default_timezone = :local
run Sinatra::Application
require 'sinatra/activerecord'
require 'sinatra/activerecord/rake'
# 上と同じやつ
ActiveRecord::Base.configurations = YAML.load_file('database.yml')
ActiveRecord::Base.establish_connection(:development)
Time.zone = 'Tokyo'
ActiveRecord::Base.default_timezone = :local
マイグレーション用ファイルを作成
諸々の設定は出来たので、テーブルを追加するためのマイグレーション用ファイルをrakeを用いて作成します。
今回作成するテーブルは投稿した記事を格納するためのpostテーブル、その記事のカテゴリーを分類するためのcategoryテーブルを作成します。
テーブル名は複数形にして作成します。
まずはcategoriesテーブルから
bundle exec rake db:create_migration NAME=create_categories
実行すると、db/migrateディレクトリが作成され、その下にマイグレーション用ファイル 日付_create_categories.rb
が作成されています。
中身はこんな感じ
class CreateCategories < ActiveRecord::Migration[5.2]
def change
end
end
changeメソッドの中に、テーブルに必要なカラムを追加していきます。
categoriesテーブルには、
- カテゴリー番号(自動生成)
- カテゴリー名
を格納するためのカラムを作ります。
class CreateCategories < ActiveRecord::Migration[5.2]
def change
create_table :categories do |t|
t.string :name, null: false # stringはVARCHAR(255), null: falseでNOT NULL制約
end
end
end
次にpostsテーブルも作成
このテーブルには、投稿された記事の
- 記事番号(自動生成)
- カテゴリー番号
- タイトル
- 本文
- 記事のサムネイルの画像名
- 投稿日(自動生成)
- 更新日(自動生成)
を格納するためのカラムを作ります。
記事のサムネイル画像は、画像を丸ごとDBに保存するのではなく、そのサムネイルの画像名だけ保存します。
bundle exec rake db:create_migration NAME=create_posts
class CreatePosts < ActiveRecord::Migration[5.2]
def change
create_table :posts do |t| # postsがテーブル名
t.integer :category_id
t.string :title, null: false
t.text :body, null: false
t.string :thumbnail, null: false
t.timestamps # これを書くとcreated_atとupdated_atカラムが自動定義される
end
end
end
これらを記述したら、 bundle exec rake db:migrate
を実行してマイグレーション(テーブル作成)します。
もしMySQLサーバーを起動していない場合は起動してください。
# 起動していない場合
mysql.server start
bundle exec rake db:migrate
テーブルができているかMySQLで確認
mysql -u root # ログイン
# ここからSQL
mysql> use articles4;
mysql> SHOW columns FROM categories;
+-------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
+-------+--------------+------+-----+---------+----------------+
mysql> SHOW columns FROM posts;
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| category_id | int(11) | YES | | NULL | |
| title | varchar(255) | NO | | NULL | |
| body | text | NO | | NULL | |
| thumbnail | varchar(255) | NO | | NULL | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
良い感じにできてます。
本当は、postsテーブルが持つcategory_idを外部キーにしたかったのですが、やり方がよく分からなかったので諦めました(´・ω・`)
ここまでのディレクトリ構成
.
├── Gemfile
├── Gemfile.lock
├── Rakefile
├── app.rb
├── config.ru
├── database.yml
├── db
│ ├── migrate
│ │ ├── 日付_create_categories.rb
│ │ └── 日付_create_posts.rb
│ └── schema.rb
└── vendor
└── bundle
└── ruby
モデルを分ける
次に、MVCやその他必要なファイルを作成していきます。
まずモデルのためのファイルを作成します。
mkdir models
touch models/categories.rb
touch models/posts.rb
modelsディレクトリの中に先ほど作ったテーブル名を単数形にした名前のクラスを作成し、それにActiveRecord::Baseを継承させます。
ファイルの中身は以下のようにします。
class Category < ActiveRecord::Base
end
class Post < ActiveRecord::Base
end
ここにモデルで行うバリデーションなどの処理を記述します。
この辺はRailsと似たような感じです。
ビューを分ける
ビューを作成していきます。
mkdir views
touch views/layout.slim
layout.slimはビューのデフォルトテンプレートになります。(ファイル名はおそらくlayoutにしないといけない)
これを用意すると、他の全てのビューはこのlayout.slimを通してレンダリングされます。
その他必要なディレクトリを作成
app.rbやビューで使うヘルパーメソッドをまとめるhelpersディレクトリを作成
mkdir helpers
静的ファイル(html, css, jsなど)を置くためのpublicディレクトリを作成
静的ファイルは./publicディレクトリから配信されます。 (ファイル名はおそらくpublicにしないといけない)
mkdir public
mkdir public/js
mkdir public/css
mkdir public/img
作成したディレクトリをconfig.ruに読み込み
modelsとhelpersはconfig.ruで読み込む必要があります。
読み込む順番をミスるとエラーが起きたりするので注意してください。
# 〜省略〜
require './app.rb'
# 追加↓
# /models, /helpers配下のrubyファイルを全てrequireする
Dir[File.dirname(__FILE__) + '/models/*.rb'].each { |f| require f }
Dir[File.dirname(__FILE__) + '/helpers/*.rb'].each { |f| require f }
# database.ymlを読み込み
ActiveRecord::Base.configurations = YAML.load_file('database.yml')
# 〜省略〜
ここまでのディレクトリ構成
.
├── Gemfile
├── Gemfile.lock
├── Rakefile
├── app.rb
├── config.ru
├── database.yml
├── db
│ ├── migrate
│ │ ├── 日付_create_categories.rb
│ │ └── 日付_create_posts.rb
│ └── schema.rb
├── helpers
├── models
│ ├── categories.rb
│ └── posts.rb
├── public
│ ├── css
│ ├── img
│ └── js
├── vendor
│ └── bundle
│ └── ruby
└── views
└── layout.slim
ターミナルから色々な操作やデバッグをできるようにする
Rails consoleのようにターミナルからテーブルのカラムを追加したり、モデルのバリデーションを試したりできるようにします。
通常app.rbにgemやdbの設定を書いている場合は bundle exec irb -r './app.rb'
でrails consoleのような操作ができるのですが、今回はその設定ファイルをconfig.ruに分離しているので、上のコマンドを打ってもできません。
なので、それを解決する為のgemであるrackshを導入します。
gem install racksh
起動は racksh
と打つだけ
racksh
Rack::Shell v1.0.0 started in development environment.
[1] pry(main)>
pryが立ち上がります。これにて、rails consoleのような操作が出来るようになります。
その2へ
環境構築はこれにて終了です。長く苦しい戦いでした。
次の回からメインとなる記事投稿システムを作っていきます。(その2〜3は自分のブログに載せてます)
【ポートフォリオ】Ruby+Sinatraで記事投稿システムを作ろう!(その2) - Knowledge-Blog
【ポートフォリオ】Ruby+Sinatraで記事投稿システムを作ろう!(その3) - Knowledge-Blog
最終的な完成品はこちら(GitHub)