先日の呑み会で Web フレームワークの話が出て、「サーバ挙動からまとめて書けたら便利だよね」という話の流れから久々に sinatra で遊んでみました。
下記、参考にしたページです。
- チュートリアル – sinatraへ簡易アカウント管理機能を追加する – Qiita
- sinatra入門 – Qiita
- 【Ruby】Sinatraで、速攻でWebサイトを公開するための環境構築 – Qiita
- sinatraで一からwebアプリケーションを構築する – Qiita
- capistrano-unicornでハマった話 – kanetann’s blog
- DB – MongoDB(3) mongoid – そういうことだったんですね
- Sinatraでmongoidを使う(herokuでの公開も) – Qiita
- マークアッパー的 Haml入門21の手引き – Web学び
- Ruby – Sinatraの使い方あれこれ – Qiita
sinatra は簡単に目的の Web アプリケーションが書けるというのが最大のメリットですが、かならずしも「初心者にも簡単」というわけではありません。 それぞれ、何のために何をやっているのかを十分に理解しておかないと変なところで詰まづくと思われます。
かくいう自分も、仕事でも趣味でも Web アプリケーションを書く機会に恵まれず、Rails すらまともに使ったことがない程度の知識。 実は数年前に nginx で Ruby を使ってちょっとしたツールを書こうと思った際に sinatra で一通り動くものを作ったことがありました。数年経つとほとんど忘れていて怖いです。
そんな自分のための覚書です。
セッション管理する基本的な構造
ディレクトリ構成
よく使う構成はテンプレート化してどこかに置いておけばいいんですが、やるたびに忘れているので毎回ゼロから構築してしまいます。かえって手間をかけてしまう…。
app/
+- assets/ … CSS や JavaScript のコードが格納されているディレクトリ。
+- models/
| +- user.rb … MongoDB に格納してもらうオブジェクトを定義してる User クラス
+- spec/
+- vender/
| +- bundle/ … 利用する gem 置き場
+- views/
+- Gemfile … 利用する gem 定義
+- app.rb … Web アプリ本体
+- config.ru … Web アプリの定義ファイル
+- mongoid.yml … Web アプリが利用する MongoDB の定義ファイル
カレントディレクトリを app ディレクトリにしておいて、次のコマンドを使えばいろいろとできます。
bundle init # … このディレクトリを bundle アプリとして設定する
# bundle とは依存 gem 一式をローカルに配置して用いる仕組み
bundle exec shotgun # … サーバを起動
# shotgun はサーバを停止しなくても、スクリプトファイルの
# 更新時に自動リロードしてくれる便利な gem
bundle install --path vender/bundle
# … Web アプリに必要な gem ファイルを vender/bundle にインストール
# Gemfile に明示的に書いたものをインストールしてくれる
Gemfile の例
使うものを使うだけ定義。ローカルの sinatra を使おうとしたら、バグがあったので最新リビジョンのやつを取ってきています。
# A sample Gemfile
source "https://rubygems.org"
#gem "sass"
gem "haml"
gem "bcrypt"
#gem "coffee-script"
gem "mongoid"
gem "shotgun"
gem 'sinatra', git: 'git@github.com:sinatra/sinatra.git',
ref: '5f6168bfc92280892e819df524d4508cf9032f6d'
# for https://github.com/sinatra/sinatra/issues/961
group :test do
gem 'rspec'
end
config.ru の設定
root = ::File.dirname(__FILE__)
require ::File.join(root, 'app')
run Server
MongoDB の設定
MongoDB をインストール後、使う DB やホスト名を指定します。
development:
sessions:
default:
database: sinatora_test
hosts:
- localhost:27017
production:
sessions:
default:
uri: <%= ENV['MONGOHQ_URL'] %>
HAML テンプレート
views/login.haml
ログイン前に表示されるページのテンプレート。
%fieldset
%legend ログイン
%form{:action => '/session', :method => 'post'}
%label{:for => "name"} 名前:
%input{:name => "name", :type => "text", :value => ""}
%label{:for => "password"} パスワード:
%input{:name => "password", :type => "password", :value => ""}
%input{:type => "submit", :value => "ログイン"}
%fieldset
%legend 新規登録
%form{:action => '/regist', :method => 'post'}
%label{:for => "name"} 名前:
%input{:name => "name", :type => "text", :value => ""}
%label{:for => "password"} パスワード:
%input{:name => "password", :type => "password", :value => ""}
%input{:type => "submit", :value => "新規登録"}
views/dashboard.haml
ログイン後に表示されるページのテンプレート。
%h1 Dashboard
%table.table
%tbody
%tr
%td Name
%td= @user.name
%tr
%td Password(hashed)
%td= @user.password_hash
%a(href="/logout") ログアウト
models/user.rb
User クラス
require 'mongoid'
require 'bcrypt'
Mongoid.load!('./mongoid.yml')
class User
include Mongoid::Document
field :name
field :password_hash
field :password_salt
attr_readonly :password_hash, :password_salt
validates :name, presence: true
validates :name, uniqueness: true
validates :password_hash, confirmation: true
validates :password_hash, presence: true
validates :password_salt, presence: true
# パスワードを暗号化するメソッド
def encrypt_password(password)
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
# ユーザーが存在するか
def self.has_user(name)
self.where(name: name).first != nil
end
# ユーザー認証
def self.authenticate(name, password)
user = self.where(name: name).first
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
end
app.rb
メインスクリプト
require 'sinatra/base'
require 'haml'
require_relative 'models/user'
class Server < Sinatra::Base
enable :sessions
set :session_secret, "My session secret"
get '/login' do
session[:user_id] ||= nil
if session[:user_id]
redirect '/'
end
if params['failed'] == "1"
@msg = "ログインに失敗しました。"
elsif params['failed'] == "2"
@msg = "登録に失敗しました。"
end
haml :login
end
get '/logout' do
session[:user_id] = nil
redirect '/login'
end
post '/session' do
if session[:user_id]
redirect "/"
end
user = User.authenticate(params[:name], params[:password])
if user
session[:user_id] = user._id
redirect '/'
else
redirect "/login?failed=1"
end
end
post '/regist' do
if User.has_user(params[:name])
redirect "/login?failed=2"
else
user = User.new(name: params[:name])
user.encrypt_password(params[:password])
if user.save!
session[:user_id] = user._id
redirect "/"
else
redirect "/login?failed=2"
end
end
end
get '/' do
@user = User.where(_id: session[:user_id]).first
if @user
haml :dashboard
else
redirect '/login'
end
end
end
sinatra や MongoDB が悪いわけではなく、自分の頭が悪いだけなんですが、ひっぱってくるものが多すぎて普段使わないと思い出すだけで頭が痛くなります…(´・ω・`) 「sinatra」「bundle」「Rack」「haml」「slim」「erb」「mongodb」…普段全然使ってないものをひっぱってこようとすると、バックエンドまでちゃんと知らないとまともに使えないのはどの環境でも同じことですが…。 普段から使い始めて、自由自在に使えるようになったらこういう手順も楽チンなんだろうなあと憧れるばかりです。
とりあえず、仕事や趣味のプロジェクトでも比較的絡めやすそうな MongoDB の取り扱いから慣れていこうと考えてます。
[…] ハッシュ関連 – https://www.rubydoc.info/github/codahale/bcrypt-ruby/BCrypt%2FEngine.hash_secret – https://qiita.com/kenjiszk/items/d38e398bc120cf4bbd1c ヒントになりました – https://dyama.org/2015/03/sinatra-mongoid%E3%81%A7%E7%B0%A1%E6%98%93%E3%83%A6%E3%83%BC%E3%82%B6%E3%8… […]