Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

TSG 初心者分科会 Web特講 第2回 「Webアプリケーション制作実習」

簡単なRailsアプリケーションを制作し、MVCモデルによる高度なウェブアプリケーションの仕組みを学ぶ。またVue.jsを用いたデータバインディングの仕組みを学ぶ。

RubyとRailsののインストール

rbenvをインストール

# Mac
brew install rbenv
# その他
curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-installer | bash

Rubyをインストール

echo 'eval "$(rbenv init -)"' >> ~/.bashrc
source ~/.bashrc
rbenv install 2.6.4

RailsとBundlerをインストール

gem install bundler
gem install rails

Rubyとは

プログラミング言語の一つ。日本人が作った中でおそらく世界中で最も広く使われているであろうソフトウェア。PerlやPythonの強力な後押しを得て、満を持して世に送り出された。以下の様な特徴がある。

  • 厳密かつ完全なオブジェクト指向
  • 表現の多様性
  • gemという強力な標準パッケージマネージャー

Ruby on Rails というヘヴィーオブジェクトが誕生したことにより世界中のウェブサーバーで動くプログラミング言語になってしまった。

プログラミング言語としての基礎的な記法は以下のとおり。

  • インデントは慣習的にスペース2つ。

  • セミコロンは不要。付けることもできるが普通は付けない。

  • 基本的な構文はカッコなしで書けるが、かわりに end というキーワードで閉じる必要がある。end地獄とも呼ばれる。

    if 1 == 2
      puts 'What?'
    end
    
    for i in 1..10
      puts i
    end
  • Rubyの全てはオブジェクトであり、メソッドの呼び出しを使って簡潔な記述ができる。例えば、「"hoge"を大文字にして前後をひっくり返して最後の文字を切り取った文字列」を求めるプログラムは次のように書ける。

    'hoge'.upcase.reverse.chop
  • 関数呼び出しの括弧は省略することができる。この書き方は可読性を失うとして避けられる場合も多い。

    puts(rand(10.gcd(15)))
    puts rand 10.gcd 15
  • 一部メソッドは do |a, b| ~ end もしくは {|a, b| ~} によって表されるブロックという引数をとることができる。これは「任意の処理」を表現するコードを受け渡したい時に使われる。

    10.times do |i|
      puts i
    end
    
    10.times {|i| puts i}
    
    0.upto(9) do |i|
      puts i
    end
  • 文字列とキーワードの中間の特徴を持つ、シンボルというリテラルが存在する。(語弊があるが)グローバルに使えるenumとでも思えばよい。連想配列のキーや種類が限定られる引数などに使用される。

    hash = {
      :hoge => 'fuga'
    }
    
    # 同じ意味
    hash = {
      hoge: 'fuga'
    }
    
    p hash[:hoge] #=> 'fuga'

不安な人はこのあたりを読むとよさそう。

Ruby on Rails とは

Rubyのメタプログラミングの柔軟性とMVCモデルという武器により世界中に瞬く間に広まったウェブアプリケーションフレームワーク。今やRubyの用途の半分以上は Ruby on Rails であるとも言われる。Railsと略す。

小さなブログから大規模なSNSまで、かなり実用的なウェブサイトが構築できる。例えば以下のサイトはRailsで構築されている。

参考: 国内注目のWebサービスを支える言語・フレームワーク・アーキテクチャ一覧【2013年版】, あなたにも作れる!Ruby on Railsで作られたWebサービス5選!, 37 Sites You LOVE Built With Ruby On Rails

ウェブアプリケーション・フレームワークとは

あらかじめ定められた作法に従ってプログラムを記述することによって、ウェブアプリケーションの制作を簡単かつ構造的に行うことができるプログラムの枠組みのこと。

ウェブアプリケーション・フレームワークの例:

  • Ruby on Rails (Ruby)
  • CakePHP (PHP)
  • Laravel (PHP)
  • Zend (PHP)
  • Django (Python)
  • Sails (JavaScript)
  • TreeFrog (C++)

複雑化しがちなWebアプリケーション開発において、これらのフレームワークの役割のひとつは、ただプログラマに無秩序にプログラムを書かせることではなく、一定の設計規則に則ってWebアプリケーションを記述されることにある。このようなアプリケーションを記述する上での設計規則・設計方針のことをソフトウェアアーキテクチャパターンとかデザインパターンとか読んだりする。

同じデザインパターンを採用しているアプリケーションは、そのパターンを成立させるための共通の処理を含むことが多いが、それとは逆に、個々のアプリケーション固有の「このアプリケーションで実現したい機能・処理」が含まれる。このような処理のことをビジネスロジックと呼ぶ。

Webアプリケーションを開発する上で難しいのは、このようなフレームワークやデザインパターンに何を選択するか、そして実現したいビジネスロジックをそのパターンの上にどのように乗せるか、ということである。

MVCとは

デザインパターンには歴史的なものも含めて非常に多くの種類が知られているが、その中でも特にサーバーサイドで採用されることが多いパターンがMVCパターンである。Ruby on Rails が採用しているデザインパターンでもあり。Model-View-Controller の略。その名の通り、ModelとViewとControllerという3種類の要素でアプリケーションを構成する。

以下では特にRailsにおけるMVCについて解説する。

  • Model: アプリケーションが扱う「モノ」や「コト」と、それに対する操作を表現したもの。典型的な設計ではデータベースのテーブルと同じ数だけ存在する。
  • Controller: 入力(HTTPリクエスト)を受け取り、それに対して必要な操作をModelを通して実行し、その結果をViewに渡す。
  • View: Controllerから渡された結果をもとに出力(HTML)を生成する。

文章でわかるわけがないので、先人が作ったイラストをいくつか参照する。

もう少し詳しく書くとこんな感じになると思う。

このようにMVCモデルではModelとViewとControllerが同列に描かれることが多いが、ことRailsにおけるModelは明らかにViewやControllerとレイヤーが異なる。Modelはデータベースにおけるテーブルをラッピングし、それに付随する操作やドメインロジックを内包した抽象的なクラスとそのインスタンスであり、「ブラウザ→Controller→View→ブラウザ」というデータの流れとは一線を画している。Modelの操作はControllerから行うのが望ましいためControllerから矢印が引かれるが、アプリケーションの一部として他とは独立した概念なので、実はアプリケーションのどこからでも利用することができる。

Railsで Hello, World!

まずはアプリケーションの / (ルートパス)にアクセスしたら “Hello, World!” と表示するプログラムを作る。

ターミナルから適当な作業用フォルダに行き、

rails new hello

と打ち込むとそこに hello というフォルダが作られ、その下に特に何もない空っぽのRailsアプリケーションが生成される。appディレクトリの下にmodelとかviewとかcontrollerみたいなディレクトリがあるのがわかる。

この状態でもう起動することができる。まずはサーバー起動してアクセスしてみる。

cd hello
bundle exec rails server

デフォルトだとlocalhostの3000番ポートにRailsサーバーが立ち上がる。ブラウザから http://localhost:3000/ にアクセスすると、Welcome aboard とかそんなことが書かれたページが現れると思う。

この時点で何か間違えても戻すことができるようにGitリポジトリにしておくことをおすすめする。

git init
git add --all
git commit -m "Initial commit"

まずはHelloという名前のControllerを作ってみる。

bundle exec rails generate controller Hello

これによりHelloという空っぽのControllerとそれに付随するいろいろなものが一気に生成される。

Controller本体は app/controllers/hello_controller.rb に置いてある。これを開いて、以下のように編集する。

class HelloController < ApplicationController
  def show
    @message = 'Hello, World!'
  end
end

次にこのControllerからデータを渡されるViewを作成する。app/views/hello/show.html.erb というテキストファイルを作成し、次のように記述する。

<h1><%= @message %></h1>

ERBはテンプレートエンジンの1つである。PHPと似たようなものだと思ってよい。

最後に / からこのHelloというControllerにアクセスが届くようにルーターの設定を変更する。config/routes.rb を開く。

コメントが大量に書いてあるが、正直いらないのでごそっと削除して以下のように書く。

Rails.application.routes.draw do
  root 'hello#show'
end

これで「ブラウザ→routes.rb→Controller→View→ブラウザ」という一連のデータの流れが完成した。再びRailsサーバーを起動する。

(サーバーを伴うウェブアプリケーションは普通CGIと異なりアクセスごとにプログラムファイルの内容を読みこんだりしないので、プログラムを変更したら再起動する必要がある。ただしRailsのControllerやViewの場合は例外)

bundle exec rails server

http://localhost:3000 にアクセスし、「Hello, World!」と表示されたら成功。

実習課題

前々回のPHP実習と同じく、Rubyの練習も兼ねて日付から曜日を計算するサイトを作ろう。

まずはHelloコントローラーにshow_daysとcalc_daysというメソッドを作る。(これは設計上非常に良くないが今回は実習なので仕方なし)

class HelloController < ApplicationController
  def show
    @message = 'Hello, World!'
  end

  def show_days
    render action: 'days'
  end

  def calc_days
    @year = params[:year].to_i
    @month = params[:month].to_i
    @day = params[:day].to_i

    # ここに何か書く

    @day_in_week = 'hoge曜日'
    render action: 'days'
  end
end

次に app/views/hello/days.html.erb というテキストファイルを作成し、次のように書く。

<h1>曜日計算機</h1>

<% unless @day_in_week.nil? %>
  <p>
    <%= @year %><%= @month %><%= @day %>日は<%= @day_in_week %>です。
  </p>
<% end %>

<p>日付を入力してください</p>

<%= form_tag do %>
  <%= hidden_field_tag :authenticity_token, form_authenticity_token %>
  <%= text_field_tag :year %><%= text_field_tag :month %><%= text_field_tag :day %><%= submit_tag '送信' %>
<% end %>

最後に config/routes.rb を次のように変更する。

Rails.application.routes.draw do
  root 'hello#show'

  get '/days', to: 'hello#show_days'
  post '/days', to: 'hello#calc_days'
end

Railsを再起動して、 http://localhost:3000/days にアクセスする。「曜日計算機」なる画面が表示されたらOK。

gemについて特に説明してないが、好きなgemを使って良い。

Node.jsのインストール

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
source ~/.bashrc
nvm install node

言語としての特徴

プログラミング言語としては、JavaScriptには以下のような特徴がある。

  • 強い動的型付け言語
  • 軽快かつ高機能な関数処理
    • それに伴う非同期処理の容易さ
  • プロトタイプ (or擬似クラス) ベースのオブジェクト指向

上で述べたとおり最近のJavaScriptは色んな所で動くので、言語仕様とAPIは厳密に区別して考える必要がある。例えば、Hello, World! のところで使用した document.write, alert, console.log といった関数はすべて Web API の関数であり、JavaScriptの仕様ではない。

基本的な記法は以下のとおり。

  • 記法はC言語に大きな影響を受けており、if, while, for などはCと同じ形の構文を使用できる。

     if (true) {
       console.log('true is true');
     }
    
     while (true) {
       console.log('infinite loop');
     }
    
     for (const i of [1, 3, 5, 7]) {
       console.log(i);
     }
  • セミコロンはRubyと同じで半分任意みたいなものだが、Rubyと違ってつけることのほうが多い。

  • 変数宣言が必要。原則として let, const (ブロックスコープ)を用いる。

     let hoge = 10;
     {
       let hoge = 24;
     }
     console.log(hoge);
  • Rubyと同じくだいたい全部オブジェクトなので、以下のように書ける。

     [3, 1, 4].concat([1, 5]).sort().reverse().slice(0, 3);
  • 関数は、ES5では function (a, b) {...} 、ES6では (a, b) => {...} と書ける。組み込みオブジェクトのメソッドを用いて関数型言語に近い書き方もできる。

     const f = (number) => {
       console.log(number * 100);
     };
     f(2525);
    
     setTimeout(() => {
       console.log('hogehoge');
     }, 3000);
    
     [3, 1, 4, 1, 5].map((n) => n - 2).filter((n) => n > 0).sort((a, b) => b - a).reduce((a, b) => a + b);
  • 変数名はわりと自由につけることができる(日本語もOK)が、文化としてcamelCaseで書くのが普通である。

     const theAnswerToTheUltimateQuestionOfLife = 42;

ブラウザからJavaScriptが実行される仕組み

ブラウザで表示されるページは基本的にHTMLであり、JavaScriptはHTMLから「呼び出される」形で実行される。

HTMLは<html>から</html>までで一つの文書を表すが、実際に表示される際にはHTMLファイルを上から読み込んでいき、順番に見つかった要素をページに放り込んでいっていると考えて良い。JavaScriptは<script>という要素によって表現され、script要素が見つかったその時点で実行される。なのでscriptが最初に実行された段階では、その後に記述した要素は存在しないのと同じである。

script要素の使い方には二通りある。

インラインJavaScript:

<script>
  console.log('hogehoge');
</script>

外部JavaScript:

<script src="hoge.js"></script>

Web API

ブラウザ上で実行されるJavaScriptは、Web APIにアクセスすることができ、ブラウザの色々な機能を使うことができる。例えば、

  • 要素を作ったり消したりする
  • 要素のCSSを編集し、ぎゅいんぎゅいん動かしたり色を変えたりする
  • 外部のウェブサイトと通信を行う
  • ブラウザに保存されているCookieやWebStorageなどにアクセスする
  • スマホの位置情報を取得する

特にHTML上の要素にアクセスするAPIはDOM(Document Object Model)と呼ばれ、多くのブラウザで利用できる。

イベント

JavaScriptのAPIの特徴として、JavaScriptの非同期処理の容易さを生かしたイベントと呼ばれる仕組みで動作するものが多い。

あるオブジェクトに対して何かが起きた時にある処理を実行させたい時には、プログラムはそのイベントに対してリスナーを追加する。そして実際に何かが起きた時には、API側はそのイベントを発火し、イベントに追加されているリスナーを実行する。

例えば、Web API である要素がクリックされた際にそのX座標を表示するには、以下のようにする。

element.addEventListener('click', (event) => {
  console.log(event.x);
});

// 以下でも同じ
element.onclick = (event) => {
  console.log(event.x);
};

このようにイベントを軸にして記述するプログラミングを、JavaScriptに限らずイベント駆動プログラミングと呼ぶ。

Vue.jsとは

Vue.jsはフロントエンド側のUIを実装するためのJavaScriptライブラリであり、仮想DOMの仕組みを用いてデータバインディングによるリアクティブプログラミングを実現するUIアプリケーションフレームワークである。これだけだと何が何だか分からないが、まずはデータバインディングとはどういう概念なのか理解しておこう。

例えば、以下のようなストップウォッチを表示するJavaScriptアプリケーションを実装したとする。

<div id="app">
  <div class="timer">あと60秒</div>

  <button class="start" type="button">Start</button>
</div>

<script>
  const app = document.getElementById('app');
  const timer = app.querySelector('.timer');
  const start = app.querySelector('.start');

  let remaining = 60;
  
  const clickStart = () => {
    remaining = 60;
    timer.textContent = `あと${remaining}`;

    setInterval(() => {
      handleTick();
    }, 1000);
  };

  const handleTick = () => {
    remaining--;
    timer.textContent = `あと${remaining}`;

    if (remaining < 0) {
      timer.textContent = '終了!';
    }
  };

  start.onclick = clickStart;
</script>

フレームワークを使用しない生のJavaScriptであれば比較的妥当な実装だと思うが、この実装には煩雑な箇所がある。一つは表示される値を変更するために複雑な DOM API を呼び出して更新する要素を取得しないといけないこと、もう一つは内部的な状態 (remaining) が変化するたびに自分で表示する値を更新しないといけないことである。

このような設計だと、どの変数がUIのどの部分に対応しているかというのを頭の中で対応付けて実装しないといけないし、変数が変更されるたびにUIの更新処理を自分で記述しないといけない。変更漏れがあっても気づきにくいし、バグを埋め込みやすい。このようなUIのプログラミング特有の煩雑さに対処するために、人類は多くのデザインパターンを生み出してきたが、それらの根本となっている概念がデータバインディングである。

データバインディングは、プログラムの内部的な状態 (上の例だとremaining) が、UIのどの部分 (上の例だとtimer要素) に対応づいているのかを記述することによってUIを記述し、状態とUIを同期させる仕組みはライブラリ側で吸収するという手法である。

上のプログラムは、Vue.js風に記述すると以下のようになる。

<template>
  <div id="app">
    <div v-if="remaining >= 0">あと{{remaining}}秒</div>
    <div v-else>終了!</div>

    <button type="button" @click="clickStart">Start</button>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        remaining: 60,
      };
    },
    methods: {
      clickStart() {
        this.remaining = 60;
        setInterval(() => {
          this.handleTick();
        }, 1000);
      },
      handleTick() {
        this.remaining--;
      },
    },
  }
</script>

表示の更新をするプログラムがごっそり消えていることがわかる。dataメソッドの中でremainingなるステートを定義したことにより、remaining変数がVue.jsによって管理される変数となり、変数に対する変更が逐一監視される。templateに埋め込んだ{{remaining}}なる部分は実際に表示される際にはremaining変数の値が表示され、値が更新されるたびに自動的に表示も更新される。このように、データバインディングを用いることで、UIアプリケーションの記述を簡潔かつわかりやすく記述することができる。

Vue.jsには他にも仮想DOMやコンポーネント化など、さまざまな重要な概念が存在するが、まずはこの1番基礎的なデータバインディングの仕組みに慣れ親しんでおこう。

実習

上の<template>を使った記法は一体化したVueコンポーネントの記法だが、Vue.jsを気軽に利用するためにCDNのvue.jsを利用したHTML記法を利用することをおすすめする。

app/views/hello/show.html.erbを以下のように書き換えると、「Hello, World!」と表示されるはず。

<div id="app" class="content">
  <div>{{hello}}</div>
</div>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script>
  new Vue({
    el: '#app',
    data() {
      return {
        hello: 'Hello, World!',
      };
    },
    computed: {
    },
    methods: {
    },
  });
</script>

ここに好きなアプリケーションを書いてみよう。

何も思いつかない人は、↓に博多市が回転寿司シミュレーターのテンプレートを書いたので、これを改善してみよう。

  • ボタンを押すと回転速度が上がる/下がる
  • 回す文字を変える
  • レイアウトを変える
  • Railsサーバーと通信して回転速度をみんなで共有する
    • 通信の仕方はfetchとかでググろう

app/assets/stylesheets/application.css:

@keyframes rotate {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

app/views/hello/show.html.erb:

<div id="app" class="content">
  <div :style="{
    animation: `${10}s infinite linear rotate`,
    display: 'inline-block',
    fontSize: '20rem',
  }">
    {{text}}
  </div>
  <button type="button" @click="onClick">すしをまわす</button>
</div>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script>
new Vue({
  el: '#app',
  data() {
    return {
      text: '🍣',
      speed: 1,
    };
  },
  computed: {
  },
  methods: {
    onClick() {
      // ここにクリックされたときの処理を書く
    },
  },
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.