JQ Blog

[JS] Bower, Npmについて

bowerとnpmは両方ともパッケージが管理できるツール。
npmはNode Package ModulesのことでNode.jsのライブラリやパッケージをインストールし、管理できる。
bowerはツイッターが作ったフロントエンドのパッケージを管理ツールである。

NPM(Node Package Modules)

インストール

npmはNode.jsをインストールすると自動にインストールされる。

使い方

1
2
3
4
5
6
7
8
9
10
11
12
13
# 構成設定ファイルである package.json を作成する
$ npm init

# プロジェクトのみインストール&アンインストール
$ npm install <package> --save
$ npm uninstall <package> --save

# グローバルインストール&アンインストール
$ npm install -g <package>
$ npm uninstall -g <package>

# package.jsonに記載されているパッケージをインストール
$ npm install

ちなみに--saveをつけるとそのパッケージが依存している全てのパッケージも一気にインストールされる。

Railsでの使い方

  • Railsの設定ファイル変更
    config/initializers/assets.rb
1
Rails.application.config.assets.paths << Rails.root.join('node_modules')
  • assetsで参照する
    requireimportを通して使うことができる

Bower

インストール

bowerはNode.jsのパッケージなのでnpmでインストールする。

1
2
3
4
5
# node.jsのインストール
$ brew install node

# bowerのインストール
$ npm install -g bower

使い方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 構成設定ファイルである bower.json を作成する
$ bower init

# ライブラリを検索する
$ bower search <package>

# パッケージを追加し、かつbower.jsonに記録する
$ bower install <package> --save

# '#'をつけるとバージョンを指定してパッケージを追加できる
$ bower install <package>#1.1.1 --save

# bower.jsonに記録されたパッケージをインストールする
$ bower install

# パッケージを削除する
$ bower uninstall <package>

構成設定ファイル

  • .bowerrc
    • directory属性設定でパッケージのインストールパス(bower_components)を指定する
    • 手動でbowerを使うなら.bowerrcを手動で作らなきゃいけない
  • bower.json
    • 依存性及び開発バージョン情報などを表す

bower-rails

インストール

  • gemfileに追加
1
gem 'bower-rails'
  • bundle install
1
$ bundle install

使い方

1
2
3
4
5
6
7
8
# bowerを初期化
$ rails g bower_rails:initialize

# Bowerfileに欲しいアセットを任意に追加
asset '<package name>', '<package version>'

# bowerをインストール
$ rake bower:install

あとはインストールされたパッケージをrequireでロードして使う。

参考

npmの使い方
npm とか bower とか一体何なんだよ!Javascript 界隈の文脈を理解しよう
Bower/bower-rails チートシート

VuejsにおけるDrag&Drop

Rails5.1.1でのVuejs

1. HTML5を利用したDrag&Drop

Rails5.1.1の環境で実装してみた。ビューとJS側はただ宣言と登録だけして全てのコードは.vueファイルに書いておいた。 - ビューとJS側

index.html.slim

1
2
3
4
#todo
  todo-dnd

= javascript_pack_tag 'todo'

todo.js

1
2
3
4
5
6
7
8
import Vue from 'vue/dist/vue.esm'
import TodoDnd from './todo-dnd.vue'

Vue.component('todo-dnd', TodoDnd)

new Vue({
  el: '#todo'
})
  • vueファイル

todo-dnd.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<template>
  <div class="mainDiv">
    <div class="div">
      <ul>
        <li v-for="list in lists" draggable="true" @dragstart="dragStart(list, $event)">
          <input type="checkbox" v-model="list.todoDone">
          <label v-show="!list.todoDone"></label>
          <label v-show="list.todoDone"><s></s></label>
        </li>
      </ul>
    </div>
    <div class="div">
      <textarea cols="50" rows="10" @dragover="dragOver($event)" @dragleave="dragLeave($event)" @dragover.prevent @drop="drop($event)"></textarea>
    </div>
    <div>
      <button v-on:click="afterSend">送信</button>
    </div>
  </div>
</template>

<script>
export default {
  data: function(){
    return {
      lists: [
        {name: "Ruby", content: 'Hello, Ruby!', todoDone: false},
        {name: "Rails", content: 'Hello, Rails!', todoDone: false},
        {name: "Vue.js", content: 'Hello, Vue.js!', todoDone: false}
      ],
      listIndex: null,
      dragContent: ''
    }
  },
  mounted: function(){

  },
  props: [],
  methods: {
    dragStart: function(item, evt) {
      this.dragContent = item.content;
      this.listIndex = this.lists.indexOf(item);
    },
    dragOver: function(evt) {
      evt.target.setAttribute('placeholder', this.dragContent);
    },
    dragLeave: function(evt) {
      evt.target.removeAttribute('placeholder');
    },
    drop: function(evt) {
      evt.target.removeAttribute('placeholder');
      evt.target.value = this.dragContent;

    },
    afterSend: function() {
      this.lists[this.listIndex].todoDone = true;
    },
  },
}
</script>

<style scoped>
.mainDiv {
  width: 100%;
}
.div {
  float: left;
  width: 100%;
  margin-right: 100px;

  ul {
    width: 300px;
    border: 1px solid black;

    li {
      border: 1px solid black;
      margin-top: 5px;
    }
  }
}
</style>

Vuejsで提供してくれるEventの一部

@click
@submit
@keyup
@mousedown
@mousemove
@mouseup
@mouseleave
@touchstart
@touchmove
@touchend
@dragstart
@dragenter
@dragleave
@dragover
@dragend

この中でdrag&dropに関するEventもあるのでそれらを活用してVuejsとHTML5を連携する。 テンプレート側で、 Dragする対象にdraggable="true"にしてDragが可能になるようにする。あと@dragstartをかけてdragが始まったときのEventを処理する。 Dropされる対象に@dragover,@dragleave,@dropをかけてそれぞれの必要な処理をさせる。

2. VueDraggableを利用したDrag&Drop

ビューとJS側はさっきのやつと同じようになるのでvueファイルだけ書く。 VueDraggableを使うためには二つの方法がある。

1
2
3
4
5
6
7
8
import draggable from 'vuedraggable'
...
export default {
      components: {
          draggable,
          ...
      }
      ...

こういうふうにimportをするか、

1
var draggable = require('vuedraggable')

requireをする。 今回は前者のimportの方法を使う。importをするにはnpmbowerでインストールをする。今回はnpmで。

1
$ npm install vuedraggable
  • vueファイル
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<template>
  <div>
    <div>
      <h2>Simple DnD</h2>
      <ul>
        <draggable v-model="lists" :options="{group:'people'}" @start="drag=true" @end="drag=false">
          <li v-for="list in lists"></li>
        </draggable>
      </ul>
    </div>
    <hr>
    <div class="div2">
      <h2>Two Lists</h2>
      <div class="twoListsDiv">
        <ul>
          <draggable v-model="lists" :options="{group:'people'}">
            <li v-for="list in lists"></li>
          </draggable>
        </ul>
      </div>
      <div class="twoListsDiv">
        <ul>
          <draggable v-model="lists2" :options="{group:'people'}">
            <li v-for="list in lists2"></li>
          </draggable>
        </ul>
      </div>
    </div>
    <hr>
    <div>
      <h2>List clone</h2>
      <div class="listCloneDiv">
        <ul>
          <draggable v-model="lists" :options="{group:{ name:'people',  pull:'clone', put:false }}">
            <li v-for="(list, index) in lists" :key="index"></li>
          </draggable>
        </ul>
      </div>
      <div class="listCloneDiv">
        <ul>
          <draggable v-model="lists2" :options="{group:'people'}">
            <li v-for="(list, index) in lists2" :key="index"></li>
          </draggable>
        </ul>
      </div>
    </div>
  </div>
</template>
<script>
import draggable from 'vuedraggable'
export default {
  components: {
    draggable,
  },
  data: function() {
    return {
      lists: [{name: 'Ruby'}, {name: 'Rails'}, {name: 'Vuejs'}],
      lists2: [{name: 'Java'}, {name: 'Swift'}, {name: 'Objective-C'}],
    }
  }
}

</script>
<style>
div {
  ul {
    width: 200px;
    border: 1px solid
  }
}
.twoListsDiv, .listCloneDiv {
  float: left;
}
.div2 {
  overflow: hidden;
}
</style>

VueDraggableではdraggableというタグを使う。このタグを使っていろんなDnDができる。optionsを使って同じグループに結んだらDnDを簡単に実装することができるし、pullとかputとかでDnDのOptionを決めることができる。

参考

Vue.jsのリストレンダリングとhtml5のドラッグ&ドロップの実装
ネイティブ HTML5 ドラッグ&ドロップ
require is not definedを解消してrequireを使えるようにする

Vue.jsを使ってTodoリストを作成する

この前書いたRailsにおけるVue.jsの使い方に続いてVue.jsを使ってTodoを作成するようにしてみた。

環境

  • Rails 5.1.0.rc1
  • Ruby 2.4.0p0
  • Vue.js 2.0

webpack-dev-server

ロカルの場合、bin/webpack-dev-serverコメンドを使って「webpack」を起動するとjsファイルがビルドされる。

単一ファイルコンポーネント

Vue.jsには.vueというファイルを通して単一ファイルコンポーネントが使える。
もちろん.jsで管理することもできるが、Vue.jsの公式サイトでみてみると

  • グローバル宣言は全てのコンポーネントに一意な名前を強制すること
  • シンタックスハイライトの無い文字列テンプレートと複数行 HTML の時に醜いスラッシュが強要されること
  • CSS サポート無しだと、 HTML と JavaScript がコンポーネントにモジュール化されている間、これ見よがしに無視されること
  • ビルド処理がないと Pug (前 Jade) や Babel のようなプリプロセッサよりむしろ、 HTML や ES5 JavaScript を制限されること

ということで単一ファイルコンポーネントを使ってこれらの問題を解決すると書いてある。正直まだjsにコンポーネントを置くのとvueファイルに置くのの違いってよく実感できないんだけど。。(笑)
とりあえずRails newをしてプロジェクトを作成したらapp/javascript/packsディレクトリーにDefaultでhello_vue.jsapp.vueファイルが生成される。それの中身をみてみると
hello_vue.js

1
2
3
4
5
6
7
8
9
10
11
12
13
import Vue from 'vue/dist/vue.esm'
import App from './app.vue'

document.addEventListener('DOMContentLoaded', () => {
  document.body.appendChild(document.createElement('hello'))
  const app = new Vue({
    el: 'hello',
    template: '<App/>',
    components: { App }
  })

  console.log(app)
})

app.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
  <div id="app">
    <p></p>
  </div>
</template>

<script>
module.exports = {
  data: function () {
    return {
      message: "Hello Vue!"
    }
  }
}
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: center;
}
</style>

vueファイルでtemplate,script,styleを使うことができる。つまり、vueファイル単位で簡単にモジュール化することができる。
でも今回作るTodoリストアプリケーションはscriptだけ使ってみる。

コンポーネント作成

  • vueファイル作成

まず、vueファイルを作成しよう。もちろん使うRails側のコントローラーとモデルは先に用意して置こう。
todo-list.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<script>
module.exports = {
  template: '#todo-list',
  data: function () {
    return {
      todos: [],
      inputValue: '',
      createMode: false
    }
  },
  mounted: function () {
    var that = this;
    this.$http.get('/api/todos').then(
      function(response){
        that.todos = response.data;
      });
  },
  directives: {
    "focus": function(el) {
      el.focus()
    }
  },
  props: [],
  methods: {
    onOffCreateMode: function(boolean) {
      this.createMode = boolean;
    },
    createTodo: function () {
      var that = this;
      this.$http.post('/api/todos', {title: this.inputValue}).then(
        function(response){
          that.todos.push(response.body);
          that.inputValue = '';
        });
      this.onOffCreateMode(false);
    },
    DeleteTodo: function (todo) {
      var that = this;
      this.$http.delete('/api/todos/' + todo.id).then(
        function(response){
          that.todos = response.body;
        });
    }
  }
}
</script>
  • jsファイル作成

todo.js

1
2
3
4
5
6
7
8
9
import Vue from 'vue/dist/vue.esm'
import TodoList from './todo-list.vue'
Vue.use(require('vue-resource/dist/vue-resource.min'));

Vue.component('todo-list', TodoList)

new Vue({
  el: '#todo',
})
  • ビュー及びテンプレート作成

index.html.slim

1
2
3
4
5
#todo
  todo-list

= render 'components/todos'
= javascript_pack_tag 'todo'

_todos.html.slim

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template#todo-list
  div
    ul
      div v-show="!createMode"
        button v-show="!createMode" v-on:click="onOffCreateMode(!createMode)"
          | +
      div v-show="createMode"
        button v-on:click="onOffCreateMode(!createMode)"
          | -
        input[v-model="inputValue" v-on:keypress.enter="createTodo" v-focus]
      li v-for="todo in todos"
        | 
        button v-on:click="DeleteTodo(todo)" style="margin-left: 20px;"
          | Delete
        todo-item :todo="todo"

他のコンポーネントの追加

コンポーネントの作成はさっきの同じようにする。

  • vueファイル作成

todo-item.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<script>
module.exports = {
  template: '#todo-item',
  data: function () {
    return {
      todoItems: [],
      inputValue: '',
      createMode: false
    }
  },
  mounted: function () {
    var that = this;
    this.$http.get('/api/todo_items?todo_id=' + this.todo.id).then(
      function(response){
        that.todoItems = response.data;
      });
  },
  directives: {
    "focus": function(el) {
      el.focus()
    }
  },
  props: ['todo', 'item'],
  methods: {
    onOffCreateMode: function(boolean) {
      this.createMode = boolean;
    },
    createItem: function () {
      var that = this;
      this.$http.post('/api/todo_items', {title: this.inputValue, todo_id: this.todo.id}).then(
        function(response){
          that.todoItems.push(response.body);
          that.inputValue = '';
        });
      this.onOffCreateMode(false);
    },
    DeleteItem: function (item) {
      var that = this;
      this.$http.delete('/api/todo_items/' + item.id + '?todo_id=' + this.todo.id).then(
        function(response){
          that.todoItems = response.body;
        });
    }
  }
}
</script>
  • jsファイル編集

jsファイルはさっきのtodo.jsに追加する形で。
todo.js

1
2
3
4
5
6
7
8
9
10
11
import Vue from 'vue/dist/vue.esm'
import TodoList from './todo-list.vue'
import TodoItem from './todo-item.vue' /* todo-itemをimportする */
Vue.use(require('vue-resource/dist/vue-resource.min'));

Vue.component('todo-list', TodoList)
Vue.component('todo-item', TodoItem) /* コンポーネントを作成 */

new Vue({
  el: '#todo',
})
  • ビュー及びテンプレート作成

_todo_item.html.slim

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template#todo-item
  div
    div v-show="!createMode"
      a href="javascript:void(0)" v-on:click="onOffCreateMode(!createMode)"
        | 項目を追加
    div v-show="createMode"
      input[v-model="inputValue" v-on:keypress.enter="createItem" v-focus]
      button v-on:click="onOffCreateMode(!createMode, todo)"
        | -
    ul
      li v-for="item in todoItems"
        | 
        button v-on:click="DeleteItem(item)" style="margin-left: 20px;"
          | Delete

参考

単一ファイルコンポーネント

JSのMVCフレームワーク

種類

  • React.js
    • 最近注目を浴びているFacebook製のライブラリで、MVCアーキテクチャでいうViewにあたる機能を提供する
    • JSX、Virtual DOMという、新しいアプローチ
    • MVCとは違う、Fluxという設計パターン
    • Isomorphicなアーキテクチャが可能
  • Angular.js
    • Googleが開発している人気MVCフレームワーク
    • フルスタックで、できないことは殆ど無い
    • Yeomanなどを使うとSPA(Single Page Application)が30分位で作れてしまう
    • Angular Wayともいわれるが、モジュール化がきっちりされていて、汚いコードにはほぼならない
  • Angular.js 2.0
    • ES6等の最新のJavaScript規格を取り入れ、TypeScriptをベースとし記述する
    • Angular.jsとはかなり書き方が違う
    • WebComopmentsを利用した、より厳密に独立したコンポーネント化を推進
  • Backbone.js
    • 単一データの管理を行うModel、複数件のモデルの管理を行うCollection、画面の管理を行うViewを組み立てていく
    • Backbone.js自体は構造化の仕組みがなく、フレームワークとしてMarionette.jsと一緒に使われることが多い
    • 事例が案外多い(Buffer、Soundcloud、Trello、Qiitaなど)
  • Vue.js
    • MVVMパターン
    • 作成したUIコンポーネントを組合せてページを構成することを前提にしている
    • 似ているといわれるが、AngularJSと設計思想は全く異なるもの
    • 理解しやすいフレームワークとして有名
  • Mithril.js
    • Mercuryとともに、スピードがかなりはやいフレームワーク
  • Aurelia.js
    • ES6、ES7なシンタックス
    • GoogleでAngularJSの開発に関わっていたRob Eisenbergさんが開発した
  • Knockout.js
    • MVVM
    • Bindingに特化したフレームワーク
    • 機能は少ないが、導入するのが容易で、学習コストも低い
  • Ember.js
    • 後で調べる
  • Riot.js
    • 後で調べる
  • Ractive.js
    • 後で調べる
  • まとめ
    • 現存するフレームワークは多すぎる。その中でReact,Vueは早い速度で成長している。Angularは最もシェアが多いのでライブラリとかもちゃんとよいされ、安定的に開発できる。Backboneは段々シェアが落ちていくけど、影響力はまだかなり大きい。

タスクランナー

  • Gulp
    • タスクランナーは、フロントエンドの様々な面倒事を自動化できるツールです。これなしのフロントエンドはもはや考えられない気がします。

モデュール化

  • Webpack
    • Webコンテンツには、CSS、JavaScript、画像、webフォント等多くのファイル(アセット)が必要になる。こういったwebで必要なファイルを取り扱うためのWebpackというものが最近人気になっている。
    • webpackとは、 webコンテンツを構成するファイルを「モジュール」単位で取り扱い、最適な形に作り変えるためのツールである。

参考

Comparison of 4 popular JavaScript MV* frameworks (part 2)
ここ数年前から2015/5までのモダンフロントエンドを総まとめしてみた
JS開発で人気のWebpackとは!?

RailsにおけるVue.jsの使い方

環境

  • Rails
    • Rails 5.1.0.rc2
  • Ruby
    • ruby 2.4.0p0
  • Vue.js
    • 2.0

Vue.jsの使い方

アプリケーション作成

1
$ rails new vuejs -d postgresql --webpack=vue

rails newに --webpackをつけたらアプリケーションが生成した時点でwebpackを使えるようになる。 app フォルダーの下に javascript フォルダーが生成されていて、その下には packs フォルダーが生成されている。そこのjsファイルとvueファイルを通して Vue.js を使うことができる。

コントローラー作成

使うコントローラーとビューを作る。このアプリケーションにはtodoリストを作成してみるのでコントローラー名は todos にする。

1
$ rails g controller todos index

routesに追加

1
2
root to: 'todos#index'
resources :todos, only: :index

js側

packs フォルダーに todo.js ファイルを作る。そして import Vue from 'vue/dist/vue.esm' を記入してVueをimportする。

ビュー側

1
= javascript_pack_tag 'todo'

‘Hello Vue!'の出力

Vue.js公式サイトの最初の方に出ている最も基本的な Hello Vue! を出力してみる。まず、todos/index.html.slimobject を出力する div を入れる。それからこのビューにjsから message というデータをバインティングする。

  • ビュー
1
2
#app
  | {{ message }}
  • js
1
2
3
4
5
6
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

参考

【動画付き】Rails 5.1で作るVue.jsアプリケーション ~Herokuデプロイからシステムテストまで~
Rails5.1.0 Beta1でVue.jsアプリケーションを作る
Vue.js_Guide

Client-side MVC

なぜClient-sideのMVCが出たのか

昔のウェブページは静的であった

昔のウェブはページの全てのコンテンツが静的だったので既存サーバサイドのMVCパターンで十分だった。その時まではjavascriptというものはページのコンテンツに関するものではなかった。

Ajaxの登場

2005年度からAjaxが本格的に流行り始めて、それから動的なコンテンツの時代が来る。その頃jqueryも出てきながらjsを通した動的なウェブページというものが広がる。

問題の発生

フロントサイドが段々複雑になってきながらhtmlとjavascriptが混ざり、可読性も悪くなりつつjs自体のコードもとても複雑で保守が大変になるということと、それ以外にもセキュリティーとかの問題が発生する。

フレームワークの登場

ウェブの環境は段々リッチになり、そのウェブを利用する人々はもっとシンプル、楽なサービスを求めることになる。そんな状況で既存の問題を改善しながら技術的にはもっと高めるようなClient-sideMVCのフレームワークが出てくる。

Client-sideMVCの目的

  • サーバサイドとの分離によって効率性を高める
  • ビューの細分化によってコードの再使用性を高める
  • 保守の便利性

クライアント側フレームワークの種類

  • React.js
  • Angular.js
  • Angular.js 2.0
  • Backbone.js
  • Vue.js
  • Mithril.js
  • Aurelia.js
  • Knockout.js
  • Ember.js
  • Riot.js
  • Ractive.js

RailsにおけるMVC

MVCはモデル/ビュー/コントローラーの頭文字を取ってMVCと呼ばれていて、各役割について処理を分割する。分割することでフロントエンドとバックエンドの業務を分けてさらに効率的な作業が可能になる。
MVCパターンの基本的な流れとしては下記のようになる。

Alt text

(参照 - MVCパターン再考)

MVCの目的

  • コードの再利用性を高める: ビジネスロジックをモデルに詰め込むことで、コードの再利用性を上げる
  • 処理の分割: 分割することでフロントエンドとバックエンドの業務を分け、さらに効率的な作業が可能になる

MVCの役割

コントローラ

Alt text (参照 - RailsにおけるMVC(モデル/ビュー/コントローラ))

利用者がブラウザ経由でRailアプリケーションにリクエストを送信すると、まずはWebサーバでそのリクエストを受け取る。そのリクエストはURLとして届くようになる。届いたURLを分析し、どのコントローラーに含まれるアクションを実行すればいいのかを判断する時に使われるのが「routes.rb」ファイルである。コントローラーのアクションが呼び出されるとそのアクションによって必要なモデルを呼び出したりビューをレンダーしたりする。

モデル
  • データベース管理
  • ビジネスロジックを実行

Railsのモデルはデータベースとのやり取りを行うクラスのことであり、データベースはテーブルの集合でできている。

テーブルの例

ID ユーザー名 ユーザー年齢
1 鈴木 36
2 佐藤 30
3 高橋 32

モデルのクラスはテーブルの各カラムをそれぞれのインスタンスとして持ち、活用できるようになる。
例えば、

インスタンス1
1
鈴木
36
インスタンス2
2
佐藤
30
インスタンス3
3
高橋
32

のようになる。モデルを使うと、直感的で記述しやすいコードでデータベースの処理ができる。 MVCの流れに戻ってみると、コントローラーにある何かのアクションから呼ばれ、必要なロジックを行う。

Alt text

(参照 - RailsにおけるMVC(モデル/ビュー/コントローラ))

ビュー

Alt text
(参照 - RailsにおけるMVC(モデル/ビュー/コントローラ))

モデルを通して取得したデータをコントローラーに返し、コントローラーは変数としてビューにデータを渡す。ビューは渡されたデータを活用してHTML文書を作成、またコントローラーに返す。

結果を返す

返したビューはコントローラーによってブラウザを通してクライアントに見えるようになる。

Alt text

(参照 - RailsにおけるMVC(モデル/ビュー/コントローラ))

参照

RailsにおけるMVC(モデル/ビュー/コントローラ)
Railsのmodelを徹底解説!知っておくべき3つの知識も紹介
Rails controllerを徹底解説!知っておくべき3つの知識も紹介
MVCのおさらいと、RailsのMVCについて説明する

Mix-in(クラスメソッド、インスタンスメソッド),concernsの使い方

Rubyのクラスは多重継承ができない。そういう限界を解決できるようにしてくれるのがMix-inという開発パタン。
モジュールを通して複数の機能を追加することができる。

インスタンスメソッドの場合

基本的にincludeするとモジュールのメソッドをインスタンスメソッドとして使用できる。

1
2
3
4
5
6
7
8
9
10
11
module Foo
  def foo
    p "good job!"
  end
end

class Hoge
  include Foo
end

Hoge.new.foo # => "good job!"

クラスメソッドの場合

モジュールのメソッドをクラスメソッドとして使用するためには

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module Foo
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def class_foo
      p "class good job!"
    end
  end

  def foo
    p "good job!"
  end
end

class Hoge
  include Foo
end

Hoge.new.foo # => "good job!"
Hoge.class_foo # => "class good job!"

クラスでインクルードされたらself.included(base)にコールバックされる。ここで引数に渡されるのはインクルードしたクラスになる。そこにモジュールをextendしたらクラスメソッドとしても使用できる。

concernsの使い方

concernsはActiveSupport::Concernをextendするだけでmix-inの複雑な記述を省略できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module Foo
  extend ActiveSupport::Concern

  # なくても良い
  included do
  end

  class_methods do
    def class_foo
      p "class good job!"
    end
  end

  def foo
    p "good job!"
  end
end

class Hoge
  include Foo
end

Hoge.new.foo # => "good job!"
Hoge.class_foo # => "class good job!"

concernsのメリット
複雑な依存関係を考えなくても良い。FooをextendするBarをHogeクラスでextendするとしたら

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
module Foo
  extend ActiveSupport::Concern

  # なくても良い
  included do
    p "included Foo"
  end

  class_methods do
    def class_method
      p "Foo class good job!"
    end
  end
end

module Bar
  extend ActiveSupport::Concern
  include Foo

  included do
    p "included Bar"
  end

  class_methods do
    def class_method
      super
      p "Bar class good job!"
    end
  end
end

class Hoge
  include Bar
end

Hoge.class_method # =>
# "included Foo"
# "included Bar"
# "Foo class good job!"
# "Bar class good job!"

こういうふうに簡単にできる。

終わり

  • モジュールをインクルードすることでモジュールのメソッドをインスタンスメソッドとして使用できる
  • includedメソッドを使ってextendさせたらインスタンスメソッドを使いながらクラスメソッドも使用できる
  • concernsを使ったらもっと簡単にmix-inができる
  • concernsで複雑な依存関係をさらに簡単にまとめられる

参考

Rails: includeされた時にクラスメソッドとインスタンスメソッドを同時に追加する頻出パターン
Rails 4.2からはmodule ClassMethodsではなくConcern#class_methodsを使おう
[Rails] ActiveSupport::Concern の存在理由

クラス変数、インスタンス変数

前回のポスト(クラスとインスタンスの違い)の続きでクラス変数、インスタンス変数及びMix-inの使い方についてまとめてみる。

クラス変数、インスタンス変数

クラス変数

1
2
3
4
5
6
class Foo
  @@foo = 1
  def bar
    puts @@foo
  end
end

@@で始まる変数はクラス変数である。クラス変数はクラス定義の中で定義され、クラスの特異メソッド、インスタンスメソッドなどから参照/代入ができる。
クラス変数は

  • サブクラスから参照/代入が可能
  • インスタンスメソッドから参照/代入が可能

クラス変数は、そのクラスやサブクラスのインスタンスで共有され、グローバル変数のように使える。

定義

1
@@foo = 1

使い方

1
2
3
4
5
6
7
8
9
class Foo
  @@foo = 1
end
class Bar < Foo
  p @@foo += 1 # => 2
end
class Baz < Bar
  p @@foo += 1 # => 3
end

モジュールで定義されたクラス変数(モジュール変数)は、そのモジュールをインクルードしたクラス間でも共有される。

1
2
3
4
5
6
7
8
9
10
11
module Foo
  @@foo = 1
end
class Bar
  include Foo
  p @@foo += 1 # => 2
end
class Baz
  include Foo
  p @@foo += 1 # => 3
end

インスタンス変数

@で始まる変数はインスタンス変数であり、これはオブジェクトに所属している。
明示的にインスタンス変数を宣言しなくて良い。

  • インスタンス変数にアクセスできるのは、initializeメソッドとオブジェクトのインスタンスメソッドだけ
    • initializeメソッドで初期化されて、その後各インスタンスメソッドから参照・変更

定義

1
@foo = 'foo'

使い方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Foo
  # initializeメソッドからアクセス
  def initialize(foo)
    @foo = foo
    p "#{@foo} が初期化されました。"
  end

  # インスタンスメソッドからアクセス
  def bar
    p "barメソッドから #{@foo} がプリントされました。"
  end

  # クラスメソッドからアクセス
  def self.foobar
    p @foo
  end
end

# initializeメソッドからはアクセスできる
foo = Foo.new("foo") # => 'foo が初期化されました。'

# 各インスタンスごとに違う値を持つことができる
foo2 = Foo.new("bar") # => 'bar が初期化されました。'

# インスタンスメソッドからもアクセスできる
foo.bar # => 'barメソッドから foo がプリントされました。'
foo2.bar # => 'barメソッドから bar がプリントされました。'

# クラスメソッドからはアクセスできない
Foo.foobar # => nil

attr_accessorの書き方
上記のクラスにattr_accessorを定義するとさらに使いやすくなる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Foo
  # attr_accessorを定義する
  attr_accessor :foo

  # initializeメソッドからアクセス
  def initialize(foo)
    @foo = foo
    p "#{@foo} が初期化されました。"
  end

  # インスタンスメソッドからアクセス
  def bar
    p "barメソッドから #{@foo} がプリントされました。"
  end

  # クラスメソッドからアクセス
  def self.foobar
    p @foo
  end
end

こういうふうに定義すると

1
2
3
4
foo = Foo.new("foo1") # => 'foo1 が初期化されました。'
foo.bar # => 'barメソッドから foo1 がプリントされました。'
foo.foo = "foo2" # => 'foo2'
foo.bar # => 'barメソッドから foo2 がプリントされました。'

のように使える。

参照

【まとめ】インスタンス変数、クラス変数、クラスインスタンス変数
class Class - Ruby 2.3.0 変数と定数

クラスとインスタンス、モジュールの違い

Rubyはオブジェクト指向プログラミング言語であり、オブジェクトというものをわからなければRubyを正しく理解することはできない。なので、オプジェクト指向とオプジェクトの一般的な概念になるクラスやモジュールをまとめてみる。

オブジェクト指向とは

Ruby, Java, Pythonなどはオブジェクト指向言語というものに分類されている。オブジェクトというものは具体的に識別できるものならなんでもオブジェクトなれる。例えば、文字列、数値、時刻など全てのデータがオブジェクトとして扱われる。"hello, world"とか"object"は文字列オブジェクト、1や3.14などは数値オブジェクトと呼ばれる。
また、オブジェクトはメソッドを通して何かの行動ができる。オブジェクトのメソッドを実行するには下記のようなコードを使う。

1
(オブジェクト).(メソッド名)

クラス、インスタンス

クラスとはオブジェクトの種類を表したものである。Rubyのオブジェクトは必ずクラスに属している。オブジェクトがどんなクラスに属しているかを確認するためには下記のように.classメソッドを使って調べる。

1
2
str = "hello, world!"
p str.class # => String

このクラスというものは設計図になるとしたら、インスタンスはその設計図を使って実際に作り出すものになる。つまり、インスタンスはクラスを具体化したものだということ。そして全てのオブジェクトはあるクラスのインスタンスだとも言える。

実際に使ってみる

1
2
3
4
5
6
7
8
class Greet
  def hello(name)
    p "こんにちは、" + name + "さん"
  end
end

greet = Greet.new
greet.hello("Jo") # => "こんにちは、Joさん"

説明すると

  • クラスを定義する
1
2
3
class クラス名
  # クラスの内容
end
  • メソッドを定義する
1
2
3
def メソッド名
  # 実行する処理
end
  • インスタンスを作成
1
インスタンス名 = クラス名.new

newメソッドを用いることでインスタンスを作成できる。

モジュール

まず、コンソールで試してみたら

1
2
3
4
5
pry(main)> Class.superclass
=> Module

pry(main)> Module.superclass
=> Object

モジュールはクラスのスーパークラスということをわかる。
クラスとモジュールの違いは、リファレンスマニュアルを見ると、こう書かれてる。

クラスとモジュールには

  • クラスはインスタンスを作成できるが、モジュールはできない。
  • モジュールを他のモジュールやクラスにインクルードすることはできるが,クラスをインクルードすることはできない。

という違いがありますが、それ以外のほとんどの機能は Module から継 承されています。Module のメソッドのうち

  • Module#module_function
  • Module#extend_object
  • Module#append_features
  • Module#prepend_features
  • Module#refine

は Class では未定義にされています。

class Class - Ruby 2.3.0 リファレンスマニュアル

上記の特性を見ると、モジュールはあくまでクラスの機能を補助(拡張)するものというイメージ。
下記は一番シンプルな例。

1
2
3
4
5
6
7
8
9
10
11
12
module M
  def foo
    'module method'
  end
end

class C
  include M
end

puts C.new.foo
# => module method

Rubyでのモジュールの役割

大きく二つの役割がある

  • Mix-in
  • Namespace

Mix-in

クラスやモジュールにモジュールをインクルードすることをMix-inと呼ぶ。 Rubyのクラスは多重継承できないが、Mix-inなら複数のモジュールをインクルードすることができる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module M1
  def foo
    'method foo'
  end
end

module M2
  def bar
    'method bar'
  end
end

class C
  include M1
  include M2
end

puts C.new.foo
puts C.new.bar
# => method foo
# => method bar

Namespace

クラス定義をモジュールで囲むことで、Namespaceとして利用できる。異なるNamespaceであれば、同名のクラスも定義できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module M1
  class Foo
    def foo
      'foo'
    end
  end
end

module M2
  class Foo
    def bar
      'bar'
    end
  end
end

puts M1::Foo.new.foo
puts M2::Foo.new.bar
# => foo
# => bar

優先順位

インクルードしたモジュールにスーパークラスと同名のメソッドが定義されていた場合、モジュールが優先される。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module M1
  def foo
    'module foo'
  end
end

class A
  def foo
    'class foo'
  end
end

class B < A
  include M1
end

puts B.new.foo
# => module foo

複数のモジュールをインクルードした場合は、後でインクルードしたモジュールのメソッドが優先。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module M1
  def foo
    'M1 foo'
  end
end

module M2
  def foo
    'M2 foo'
  end
end

class A
  include M1
  include M2
end

puts A.new.foo
# => M2 foo

参照

第2回 クラスとインスタンス 〜Rubyの勉強〜
Rubyのモジュールについて調べた