Posts Rails5.1の基本(Scaffoldなし)+Vuejs+typescript
Post
Cancel

Rails5.1の基本(Scaffoldなし)+Vuejs+typescript

Railsの基本(+α)1

そもそも、一度しっかりRailsの基本についてメモしておこうと考え書き始めたのですが、やっているうちに、remote: trueを少し調べ、 興味がVuejsに移っていき、Axiosでajaxする方向で検討したり、だいぶ基本から逸脱することも調べながら書いているうちに(迷走したというのが正しいかもですw)、かなり文章の量も増えてしまったので、いつくつかに分割することになってしまいました。

なので、全体としては

  1. Rails5.1でのWebpackの使い方
  2. Vuejsの基本
  3. vee-validaeの基本(特にajaxによるユニーク確認ができるように)
  4. deviseの基本
  5. Blogの登録や閲覧部分、およびプラスα(markdownの組み込みなど)

あたりが、そもそものテーマなんですが、今回は3までとなります。

確認環境

筆者の環境は以下のようです。

  1. MacBook Pro (15-inch, Late 2016)
  2. MacOS Sierra 10.12.6
  3. ruby 2.3.3p222 (2016-11-21 revision 56859) [x86_64-darwin16]
  4. Rails 5.1.4
  5. mysql Ver 14.14 Distrib 5.7.18, for osx10.12 (x86_64) using EditLine wrapper
  6. node v8.1.0

前提

上記の環境でRailsはバージョン5.1、rubyは少なくとも2.3以上、mysqlは5.6以上とかは必要かもです。rails5.1からwebpackを利用できるようになったので、そのためnodeが、さらに、yarnもあると便利なので、これもインストールしておきます。

なので、あまりサーバ系のインストールとかに慣れていない方は、http://qiita.com/etsracas/items/53f1230e13cf5f9c40baで、brewrbenv(rubyを色々なバージョンでインストールできるツール)とrubyをインストールします。

また、https://qiita.com/tabolog/items/da18143e70f40e356b5dを参考に、rbenv同様、nodebrewを利用してnodeをインストールし、yarnはターミナル上でnpm install -g yarnを実行してインストールしてください。

さらに、次のように、先ほどインストールしたbrewを使ってmysqlをインストールし、そして最後に以下のようにして、mysqlのユーザを1人作成しておきます。

1
2
3
brew install mysql
brew tap homebrew/services
brew services start mysql

とこれで、mysqlがインストールされ、自動起動するようになっているはず。以下で確認。

1
2
3
4
5
6
brew services list

Name               Status  User    Plist
...
mysql              started chikkun /Users/chikkun/Library/LaunchAgents/homebrew.mxcl.mysql.plist
...

という行が出て入れば大丈夫です。

次にrootのパスワードを設定(パスワードまで真似しないでね)。

1
mysqladmin -u root password 'yourpassword'

次のようなコマンドを叩いて、mysqlクライアントを立ち上げます。パスワードが聞かれるので、上記のパスワードを入力してください。

1
mysql -u root -p

mysql上で

1
2
3
CREATE USER hoge IDENTIFIED BY 'hogechan';
GRANT ALL PRIVILEGES ON *.* TO 'hoge'@'localhost' IDENTIFIED BY 'hogechan' WITH GRANT OPTION;
FLUSH PRIVILEGES;

«««< HEAD を実行して、hogeというユーザ(もちろん名前やパスワードは任意)を作成します。 ======= を実行して、hogeというユーザ(もちろんユーザやパスワードは任意)を作成します。

b6d4789738b078c9e4e2f9298c0c62d2b83d6342

さて、開発開始

Railsの入門というとまずはScaffoldから、というのが多いのですが、これだと今ひとつ何をやるべきことなのか、ということがブラックボックスになってしまい「う〜ん・・・?」という中途半端な理解で終わってしまうことが多い気がします。

そこで、何かしらフォームを使ってのデータの登録を行う簡単なアプリを、Scaffold機能を(その他コマンドも)使わず行ってみようと思います。

しかも、若干基本よりはみ出して、実用性があることまで持って行きたいと考えております。

どんなアプリを作成するか

«««< HEAD usersテーブルを作成し、そこに入るデータは管理者のユーザ名、パスワードなどを登録します。そして、この管理者に登録された人でないと、その管理者自身を登録できないという仕様とします。

また、この管理者は簡易ブログをタグとカテゴリを伴って登録することができ、そのブログは不特定多数の人が見ることができる、極簡単なブログシステムです。一応プロジェクト名をbanshoとします(森羅万象を記す、的な安易な名前ですw)。

usersテーブルを作成し、そこに入るデータは管理者のユーザ名、パスワードなどを登録します。そして、この管理者に登録された人でないと、その管理者自身を登録できないという仕様とします(次回deviseで実装)。

また、この管理者は簡易ブログをタグとカテゴリを伴って登録することができ、そのブログは不特定多数の人が見ることができる、そんな極簡単なブログシステムです。一応プロジェクト名をshinraとします(森羅万象を記す、的な安易な名前ですw)。

b6d4789738b078c9e4e2f9298c0c62d2b83d6342

どんな作業をするかを外観

  1. Railsのプロジェクトを作成、database.ymlの設定とかを変更
  2. テーブルのmigrationファイル作成とmigrate

    要するに、テーブル(およびフィールド)の作成です。あとでDeviseを入れ込むときに、usersテーブルがぶつかりますが、Deviseが結構賢く、よしなに扱ってくれるらしい(これは次回)。

  3. Controller «««< HEAD コントローラの作成です。今回はAjaxでのアクセスがメインになるかもです。

    indexを定義します。

    コントローラの作成です。今回はAjaxでのアクセスがメインになるので、api的な使い方がメインになります。index等を定義します。

    b6d4789738b078c9e4e2f9298c0c62d2b83d6342

  4. Model

    上記1.で作成したテーブルに対応したモデルの作成です。バリデーションはクライアント側で行うので、何もないようなModelになってしまいそうです。

  5. View

    最終的にはブラウザにhtmlとして出力される、Rail標準のテンプレートの作成です。今回は、クライアントでバリデーションするので、このテンプレートの中にJavaScriptでガリガリ書くことになるかもしれません。

    ただ、始めのページで読み込まれた後は、VuejsでAjax通信させるので、index.html.erbのみ書きます。その分Javascriptの量がさらに増えそうですが・・・。

  6. routes どんなURL(とメソッド—POSTやGET)だったら、どのコントローラのどのメソッドを実行するかを定義したものです。例えば、http://sample.rails.jp/ にGETでアクセするとusers_controller.rbのindexというメソッドを実行する、などという記述をすることになります。

Railsプロジェクトの作成

まずは、ターミナルを立ち上げて、プロジェクトを作成する1つ上のディレクトリを作成します(mustじゃありません)。

1
2
3
cd ~
mkdir projects
cd projects

«««< HEAD 次にRailsプロジェクトを作成します。-dオプションでmysqlを指定して、作成します(mysqlがインストールできていない場合は、この指定をやめます)。

さらにwebpackというオプションも付けて、Javascript等はWebpackに管理させます。

*rails new appli_name -d database_name* というように-dでデータベースの種類を指定すると、少し幸せになります。
*rails new appli_name -d database_name --webpack* というようにwebpackを利用する設定にします。
1
2
3
rails new bansho -d mysql --webpack
.....
cd bansho

色々、標準出力が出てきますが、とりあえずスルーして、banshoというプロジェクトディレクトリにcdします。

このwebpackを使うと言うことで(cssはそのままで、jsのみ置き換えます)、

app/assets/javascripts/application.js

は削除して

app/javascript/pack/application.js <-これはすでにあるので、後で中身を書き換えます。

また、今回はajaxメインにしようとしているので、turbolinksは必要ないかと思い、Gemfileから次の行を削除します。

1
gem 'turbolinks', '~> 5'

そして、

app/views/layout/application.html.erb

1
2
<%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>

がありますが、この左の方になるturbokinks関係を削除します。

1
2
<%= stylesheet_link_tag    'application', media: 'all' %>
<%= javascript_include_tag 'application' %>

さらに、ヘルパーメソッドのjavascript_include_tagを次のように変更します。

1
2
<%= stylesheet_pack_tag 'application', media: 'all' %>
<%= javascript_pack_tag 'application' %>

database.ymlの変更

======= 次にRailsプロジェクトを作成します。–database=mysqlオプションでmysqlを指定して、作成します(mysqlがインストールできていない場合は、この指定をやめます→するとsqliteが使われ、これはインストール等が必要ありませんので、単に試すには便利です)。

さらにwebpackというオプションも付けて、Javascript等はWebpackに管理させます。

rails new appli_name –database=database_name というように-dでデータベースの種類を指定すると、少し幸せになります。

rails new appli_name -d database_name –webpack というようにwebpackを利用する設定にします。

1
2
3
$ rails new shinra  --webpack --skip-sprockets --skip-javascript --database=mysql
.....
cd shinra

また、スプロケットやrails-ujs(以前はjquery-ujs)等も使わないため、上記のような–skip-sprockets –skip-javascriptという2つのオプションも付けました。

色々、標準出力が出てきますが、とりあえずスルーして、shinraというプロジェクトディレクトリにcdします。

Vuejsのインストール

インストールと言っても、railsコマンドで一発です。ついでにAjaxで利用するAxiosもついでにインストールしておきます。

1
2
$ rails webpacker:install:vue
$ yarn add axios

これで終了w

ただ、一応この後、vuejsでHello worldをしてみます。しかも、実用性の高そうな単一ファイルコンポーネント(Single File Components)を試してみます。

/app/views/layout/application.html.erb等の修正

さすがに–skip-sprockets –skip-javascriptを指定しているので、/app/views/layout/application.html.erb内に見慣れたapplication.js部分もありません(何故かCSSだけはありますね)。わーお、すっきりしたぁ。

>
1
2
3
4
5
6
7
8
9
10
11
12
13
    <!DOCTYPE html>
    <html>
      <head>
        <title>Wakaran</title>
        <%= csrf_meta_tags %>

        <%= stylesheet_link_tag    'application', media: 'all' %>
      </head>

      <body>
        <%= yield %>
      </body>
    </html>

さて、これを次のような変更を加えます。

  1. CSS関係
    1. stylesheet_link_tagstylesheet_pack_tagに変更。
    2. /app/assets/stylesheets/application.cssの拡張子を変えて、/app/javascript/pack/application.scssに移動させます。
    3. 中の

      1
      2
      
      *= require_tree .
       *= require_self
      

      となっている、イコールをとって、単なるコメントにします(もしくは、削除してしまう)。そして、次のものを書き込みます(暫定)。

      1
      2
      3
      
      p {
         color: red;
       }
      
  2. javascript関係
    1. /app/javascript/pack/application.jsworldwide.jsにリネームします。そして、もともとのを少しだけ変えて、次のようにします(in worldswide.jsを加えただけ)。

      1
      
      console.log('Hello World from Webpacker in worldwide.js');
      
      application.jsだと、何故かうまくいかない・・・。なぜだかは追えていません・汗。
    2. /app/views/layout/application.html.erbに以下を加えます(CSSの上あたり)。

      1
      
      <%= javascript_pack_tag 'worldwide' %>
      

/app/controllers/welcome_controller.rb

コントローラが1つもないと確認のしようもないので、ランディングページ的な/app/controllers/welcome_controller.rbを作成します。

>
1
2
3
4
5
6
class WelcomeController < ApplicationController

  def index

  end
end

何もないindexメソッドしかありませんが、次のようなルールで一応javascriptの確認はできそうです。

controller内のrenderのルールは「app/views/コントローラ名/メソッド名.html.erb」を描画します。

つまり、/app/views/welcome/index.html.erbを次で作成します。

/app/views/welcome/index.html.erb

/app/views/welcome/index.html.erbというファイルを作成して、次のような内容を書き込みます。

>
1
2
3
4
5
6
7
<%= stylesheet_pack_tag 'welcome', media: 'all' %>
<div id="page-header" class="page-header">
  <h4>トップページ</h4>
</div>
<p>Hello Rails 5.1</p>
<div id="MyAppRoot"></div>
<%= javascript_pack_tag 'welcome' %>
  1. 1行目はwelcome.cssを読み込め(下にあるindex.vueファイルから自動作成される)、というERBへの指示
  2. 2〜4行はただのhtml
  3. 6行目は、Vue.jsと紐付けるところ
  4. 7行目はwelcome.jsを読み込め(後で作成)、というERBへの指示

ここで上記3のところに、Hello Vue!を表示させます。

/app/javascript/packs/index.vue

/app/javascript/packs/index.vueが少し触れた単一ファイルコンポーネントです。簡単に言ってしまえば、templatejavascriptstyleコンポーネント毎に1つのファイルにまとめて書いてしまえ、という感じです。実際には次のように書き込んでおきます。

>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
  <div>
    <p>{{ message }}</p>
  </div>
</template>
<script>
export default {
  data: () => {
    return {
      message: "Hello Vue!"
    }
  }
}
</script>

<style scoped>
p {
  font-size: 10em;
  text-align: center;
  color: blue;
}
</style>
  1. 1〜5行目がこのコンポーネントのテンプレート
  2. 6〜14行目までは、この中で動くJavascriptのスクリプト
  3. 17〜22行目までがstyle

です。

3に関しては少々コメントしますと、<style scoped>のようにscopedが書いてあると、この場合だとCSSのpp[data-v-071215b2]というように属性が書き込まれ、上記1にあるtemplateの中の全てのタグに同じ属性が書き込まれ、<div data-v-071215b2="" id="MyAppRoot"><p data-v-071215b2="">Hello Vue!</p></div>というようになります。

つまり、これによって、他のコンポーネントはもとより、別のアプリと関係ないスタイルへの影響を避ける、というものです(これは意外に重宝しそうですね)。

/app/javascript/packs/welcome.js

/app/javascript/packs/welcome.jsは、/app/views/welcome/index.html.erb内のコンポーネントと/app/javascript/packs/index.vueの単一ファイルコンポーネントをマッピングするものになります。内容は次のようです。

>
1
2
3
4
5
6
7
8
9
import Vue from 'vue'
import App from './index.vue';
document.addEventListener('DOMContentLoaded', () => {
    const app = new Vue({ // eslint-disable-line no-new
        el: '#MyAppRoot',
        render: h => h(App)
    })
    console.log(app)
})

上記の

1
document.addEventListener('DOMContentLoaded', () => {

の部分は、jQueryでは

1
$(document).ready

とやっていたところです。

また、/app/javascript/packs/index.vueをimportして、それをAppという変数に受け取り、

1
 const app = new Vue(App).$mount('#MyAppRoot')

とうように、Vueのコンストラクターの引数に渡して、そして、$mountで、views/welcome/index.html.erb<div id="MyAppRoot"></div>へのマッピング(紐付)をします。

config/routes.rb

次の2行を書き込み、ブラウザで確認できるようにします。

1
2
  get "welcome/index"
  root :to => 'welcome#index'

ブラウザによる確認

カレントディレクトリをshinraプロジェクトのルートにしたターミナルを2つオープンしておきます。

片方では

1
bin/webpack-dev-server

を実行します。Webpackがごちゃごちゃ言ってきますが、無視します。これはserverという名前が付いているように、ファイルを変更すると自動でWebpackを実行してくれる優れものです。

次に、railsを立ち上げます。

1
rails s

これでhttp://localhost:3000/にアクセスすると›

hello_vue.png

という感じの画面が見えるはずです。とりあえず、Hello World!は見えたということでw

ここのVue.jsのポイント

  1. /app/javascript/pack/application.scsはcssに変換されて、/app/views/layout/application.html.erbで読み込まれ、そこではcolor: red;と書いてあるのですが、それを上に出てきたindex.vueファイルの中のscopedのところに書いたCSSで上書きしています。反対にviews/welcome/index.html.erb内のHello Rails 5.1は赤いままです。
  2. Vue.jsには書き方が色々ありますが(参考:https://aloerina01.github.io/javascript/vue/2017/03/08/1.html)、今回の単一ファイルコンポーネント(Single File Components)はおすすめです(異論はあるかもしれませんが)。
  3. index.vueの中のdataプロパティは関数を仕込むことを推奨しているようですが、その返しているオブジェクトのキーをtemplateの中から参照できます(<p>{{ message }}</p>のように)。

    1
    2
    3
    4
    5
    6
    7
    
    export default {
       data: () => {
         return {
           message: "Hello Vue!"
         }
       }
     }
    
  4. index.vueの内容をVueとして登録するのがタグのIDを通じてで今回の場合、/app/views/welcome/index.html.erb内の<div id="MyAppRoot"></div>/app/javascript/packs/welcome.js

    1
    2
    3
    4
    
        const app = new Vue({ // eslint-disable-line no-new
             el: '#MyAppRoot',
             render: h => h(App)
         })
    

    にあるelで指定した要素でマッピングします。

Database.ymlの変更

さて、今度はデータベースを作成します。まずは設定からです。今回はmysqlを指定したので、最初に作成したユーザやパスワードに変更します(productionのところとかは今回は変更しません)。

b6d4789738b078c9e4e2f9298c0c62d2b83d6342

config/database.ymlというファイルをエディタで開くと次のようになっていると思います(コメントは取っています。また、上記で-dオプションでmysqlを指定していない場合はsqliteを使う設定なので、変更等は必要はありません)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
  socket: /tmp/mysql.sock

development:
  <<: *default
  database: bansho_development

test:
  <<: *default
  database: bansho_test

production:
  <<: *default
  database: bansho_production
  username: bansho
  password: <%= ENV['BANSHO_DATABASE_PASSWORD'] %>

上記のうち変更するのは唯一mysqlのユーザの作成で作成したユーザ名とパスワードを書き込みます(上では「hoge:hogechan」でしたが、もちろん、任意です)。

1
2
3
4
5
6
7
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: hoge
  password: hogechan
  socket: /tmp/mysql.sock

これでrailsからmysqlにアクセスできるようになったはずです。

テーブルのmigrationファイル作成とmigrate

Railsでは、テーブル名は複数形にするのがルールです。

今回もそれに則っています。

さて、次の一覧のように8つのテーブルを作成します。id等はRailsが自動で作成してくれるので書いていません。

また、通常railsでは型を[string, text, integer, float, date, datetime, boolean, primary_key, time, timestamp, binary, decimal]あたりから選んで書くことになるのですが、このうち4種類しか利用していません(実際のDBには型やサイズ等がもっと色々ありますが、Railsではプログラミングでもなじみのあるもので代用できるようになっており、必要に応じてlimit等で長さを指定したりできます)。

  1. usersテーブル
    1. username:string
    2. email:string
    3. password:string
  2. rolesテーブル
    1. role_name:string
  3. user_rolesテーブル
    1. user_id:integer
    2. role_id:integer
  4. blogsテーブル
    1. user_id
    2. title:string
    3. content:text
    4. issue_date:datetime
  5. tagsテーブル
    1. tag_name:string . categoriesテーブル
    2. category_name:string
  6. blog_tagsテーブル
    1. blog_id:integer
    2. tag_id
  7. blog_categoriesテーブル
    1. blog_id:integer
    2. category_id

これらのテーブルは

  1. usersとrolesがuser_roles(中間テーブル)を介して多対多
  2. usersとblogsが1:多
  3. blogsとtagsがblog_tags(中間テーブル)を介して多対多
  4. blogsとcategoriesがblog_categories(中間テーブル)を介して多対多

という関係なっています。ER図にすると、下のような感じになっています。 er.png

もう少しだけ説明すると、具体的なレコードで説明をすると(さらに下の表参照)、blogsにuser_idというフィールドがあって、これが書いたユーザが誰なのかを表すもので、ユーザ1人で複数のブログを書けるので、「1:多」ということになります(このuser_idは通常外部キーとか言います)。

次に、usersとrolesの関係、つまり、userがどんなロール(役割)を持てるかというのを表す際、もしユーザ1人にロール1つなら、1:1の関係で「1:多」の「多」が常に1になるような関係になれば良いのです。ただ今回は、一人のユーザが複数のロールを持てるようにするので(方法的にはいくつかありますが)、上のER図のように中間テーブルを利用して、つまり、中間テーブルでユーザとロールの関係を表現します。

すなわち(下の表のように)、IDが1のユーザはadminとsuper_adminのロールを2つ持っているような場合、中間テーブルであるuser_rolesにuser_idが1でrole_idが1(これがadmin)のレコードとuser_idが1でrole_idが2(これがsuper_admin)の2レコードを作成することにより表現します。もちろん、user_idが2の人のようにロールが1つということも可能になります。これで中間テーブルを利用して、「多対多」を定義したということになります。

excel_example.png

さて、具体的にmigrateしています。これもコマンドを叩いて作成することが可能ですが、今回は手作業で行います。

db/migrate/20170926153600_create_users_and_roles_and_user_roles.rb

というファイルに次のような内容で書き込みます。ここでファイル名の「20170925153600」は「年→月→日→時→分→秒」までの14桁(しっかりmigrateが順番になるようになっていれば大丈夫なんで、あまり正確じゃなくてもOKかも)、その後の名前はわかりやすいようにしただけです(ただし、createとかremoveとかある程度Railsは読み取っているらしいけれど、ここでは無視します)。今回の名前は、usersとrolesとuser_rolesを作成する、というような意味にしました(もしかしたら、テーブル1つ1つ分けた方が良いという人もいるかもしれませんが、面倒なのでまとめました)。

ただ、後半のわかりやすいようにした名前は(数字は無視)、クラス名ではその名前に合わせたルールがあって、それは最初は大文字、そして_は削除して_の後ろは大文字するというものなので、「create_users_and_role_and_user_roles」は「CreateUsersAndRolesAndUserRoles」というようなクラス名になります。

migrationファイルは年〜秒までの14桁の数字+テーブル名やcreateとかaddとかわかりやすい言葉を_(半角アンスコ)でつなげて作成する。
migrationファイル内のクラス名は数字は無視して、最初は大文字、そして_は削除して_の後ろは大文字にして作成する。
*rails db:create*でデータベースができ、*rails db:migrate*で、テーブルの作成やフィールドの追加・変更・削除ができる。
*rails db:drop*でデータベースを削除して、再度行うことができるが、Rails5からは*rails db:environment:set RAILS_ENV=development*を実行しないとダメになった。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class CreateUsersAndRolesAndUserRoles < ActiveRecord::Migration[4.2]
  def change
    create_table :users do |t|
      t.string :username, :null => true
      t.string :email, :null => false
      t.string :password
      t.timestamps
    end
    create_table :roles do |t|
      t.string :role_name, :null => false
      t.timestamps
    end

    create_table :user_roles do |t|
      t.integer :user_id, :null => false
      t.integer :role_id, :null => false
      t.timestamps
    end
    add_index :user_roles, :user_id
    add_index :user_roles, :role_id
  end
end

※usersのパスワードは今後インストールするdeviseの時に使わなくなるので、今回は「not null」をはずしています。

次に

db/migrate/20170926153800_create_blogs_and_tags_and_categories_and_chukan_tables.rb

というファイルを次のような内容で書き込みます。

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
class CreateBlogsAndTagsAndCategoriesAndChukanTables < ActiveRecord::Migration[4.2]
  def change
    create_table :blogs do |t|
      t.integer :user_id, :null => false
      t.string :title, :null => false
      t.text :content, :null => false
      t.datetime :issue_date
      t.timestamps
    end
    create_table :tags do |t|
      t.string :tag_name, :null => false
      t.timestamps
    end

    create_table :categories do |t|
      t.string :category_name, :null => false
      t.timestamps
    end

    create_table :blog_tags do |t|
      t.integer :blog_id, :null => false
      t.integer :tag_id, :null => false
      t.timestamps
    end
    create_table :blog_categories do |t|
      t.integer :blog_id, :null => false
      t.integer :category_id, :null => false
      t.timestamps
    end

    add_index :blog_categories, :blog_id
    add_index :blog_categories, :category_id

    add_index :blog_tags, :blog_id
    add_index :blog_tags, :tag_id
  end
end

上記はchangeメソッドしか書いていませんが、フィールド名を変えたり、フィールドを削除したりするmigrationの場合はupメソッドとdownメソッドを書いて、migrateした場合はupメソッドが、roll back(元に戻)した場合はdownメソッドを書いたりします。

これは、テーブルを作ったり、フィールドを増やしたりした場合は、ロールバックするにはそれを単純に削除するということで実現できるのでrailsはchangeだけ書けば問題なく処理してくれますが、フィールドを削除したり、フィールド名を変えたりした場合は、どんなフィールドを元に戻すのか、どんなフィールド名に戻すのかがchangeメソッドだけだとさすがのRailsでもわからないので、upメソッドとdownメソッドを書くということになります。詳しくはhttp://tanihiro.hatenablog.com/entry/2014/01/10/182122あたりをご参考に。

また、migrationファイルの書き方等はhttp://www.rubylife.jp/rails/model/index9.htmlや本家のhttps://railsguides.jp/active_record_migrations.htmlをご参考ください。

さて、このファイルを元に次のようなコマンドを叩きます。

1
2
3
rails db:create
rails db:environment:set RAILS_ENV=development #←これをやる必要がある!
rails db:migrate

これでエラーが起こらなかったら、DBやテーブルが作成されたはずです。

controller

controllerの名前は「テーブル名」+_+「controller.rb」というルールになっており、usersテーブルを扱うコントローラだったら*users_controller.rb*となる

というわけで、app/controllers/users_controller.rbを作成します(BlogsControllerは後回しとします)。

下以外のメソッドを使っていけないわけじゃありませんが、通常は次のようなメソッドを作成します。

controllerの中にすでに定義されているメソッド

  1. public
    1. index

      これはレコードの一覧を表示させるメソッド。

    2. show

      1つのレコードの値を表示させるメソッド。

    3. new

      新しいレコードを登録するためのフォームを表示するメソッド(登録するのは次の次のメソッド)。

    4. edit

      レコードの編集するためのフォームを表示するメソッド(値はフィールドにセットされています)。

    5. create

      newで開いたフォームの値を元にレコードを作成するメソッド。

    6. update

      editで表示されたフォームに対して何らかの値を修正して、それらの変更を更新するメソッド。

    7. destroy

      レコードをIDを指定して削除するメソッド。

  2. private
    1. set_user

      編集の時(edit)やデータの表示(show)等にidからレコードを拾ってBlogモデルクラスのインスタンスにレコードをセットするメソッド。

    2. user_params

      Strong Parameterといって、リクエストの値の中にシステムが要求している以外のフィールドとかないかチェックしているメソッド(今回は詳細は避けます)。

さて、このusersテーブルには管理者の情報を登録することになるわけですが、だいたい、scaffoldを使ってcontrollerを作成すると次のようになります。

ただし、Userというモデルがまだないし、Viewもないので、当然動きません。

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
class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  def index
    @users = User.all
  end
  def show
  end
  def new
    @user = User.new
  end
  def edit
  end
  def create
    @user = User.new(user_params)
    respond_to do |format|
      if @user.save
        format.html { redirect_to @user, notice: 'User was successfully created.' }
      else
        format.html { render :new }
      end
    end
  end
  def update
    respond_to do |format|
      if @user.update(user_params)
        format.html { redirect_to @user, notice: 'User was successfully updated.' }
      else
        format.html { render :edit }
      end
    end
  end
  def destroy
    @user.destroy
    respond_to do |format|
      format.html { redirect_to user_url, notice: 'User was successfully destroyed.' }
    end
  end
  private
    def set_user
      @user = User.find(params[:id])
    end

    def user_params
      params.require(:user).permit(:username, :email, :password)
    end
end

model

usersテーブルに対応したModelを作成します(多のモデルは必要に応じて順次作成します)。ただ、バリデーションもしない単純なものを作成します。

app/models/user.rbに下の内容(たったの2行)を書き込みます。

1
2
class User < ActiveRecord::Base
end

view

viewは少々面倒です(scaffoldで作成される程度のものなら、手作業で数は若干多いという程度ですが、その若干数が多いという点に加え、ある程度はしっかりデザインしたいし、クライアントサイドのバリデーションをもしたいとなると面倒だ、という感じです)。通常ですと、次のようなファイルを作成することになります。

  1. _form.html.erb

    edit.html.erbとnew.html.erbからインクルードされている、htmlのformが書かれている(後述)。

  2. index.html.erb

    レコード(Blog)の一覧表示するもの。

  3. new.html.erb

    ほとんど、_form.html.erbを読み込んでいるだけ。

  4. edit.html.erb

    ほとんど、_form.html.erbを読み込んでいるだけ。

  5. show.html.erb

    レコードの値の表示するもの。

ただし、とりあえず、今回はすべてをシングルページアプリケーションにするわけじゃありませんが、このユーザ部分はそうしようと考えているのでindex.html.erbのみを作成します。

app/views/users/index.html.erb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div id="page-header" class="page-header">
  <h4>管理者一覧ページ</h4>
</div>

<table class='table-striped table-condensed table-bordered' align="center" width="55%">
  <thead>
  <tr>
    <th width="45%" class="center">メールアドレス</th>
    <th width="55%" class="center">パスワード</th>
  </tr>
  </thead>
  <tbody>
  <% @users.each do |user| %>
      <tr>
        <td class="left"><%= user.email %></td>
        <td class="left"><%= Devise::Encryptable::Encryptors::Aes256.decrypt(user.password).gsub(/\w/, "*") %></td>
      </tr>
  <% end %>

  </tbody>
</table>

ちょっと先回りして、暗号化

パスワードの暗号化は、後にDeviseをインストールするときに考えようと思っていたんですが、この後viewでパスワードとかを登録するときに必要かな、と思いここで暗号化するクラスをを作成します。

Deviseの標準のパスワードの暗号化はsha512とか使っていて、これは復号できない。つまり、暗号化してその暗号化されたものから元には戻せないものです。

これはユーザ本人がパスワードをすべて管理するような場合(つまり、本人が自分のユーザ登録を行い、忘れた場合は自分で再発行してメールで受け取るような場合)ではなく、管理者が管理者を登録したりするような場合、復号化できた方が良いようなケースも存在します(もちろん、リスクも伴うので色々意見もありそうですが、とりあえずそこはスルーしてもらって)。

というわけで、暗号化をDeviseの標準じゃなく、例えば、復号もできるけれど強度の高いAes256とかを使ってみようと考えているわけです。

そこで、

  1. まずは、暗号化のライブラリーを導入

    Gemfileに

    gem 'aes'

    を書き込んで、シェルで

    bundle install

  2. config/initializers/aes256.rbというファイルに次のような内容を書き込みます。

    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
    
    # -*- coding: utf-8 -*-
    require 'aes'
    
    module Devise
      module Encryptable
        module Encryptors
          class Aes256# < Base
            class << self
              def digest(password, stretches = 10, salt = Rails.application.config.salt, pepper  = Rails.application.config.pepper)
                begin
                  ::AES.encrypt(password, pepper, {:iv => salt}) if password
                rescue
                  nil
                end
              end
    
              def salt(stretches = 10)
                ::AES.iv(:base_64)
              end
    
              def decrypt(encrypted_password, pepper  = Rails.application.config.pepper)
                if encrypted_password.blank?
                  return ""
                end
                ::AES.decrypt(encrypted_password, pepper  = Rails.application.config.pepper)
              end
            end
          end
        end
      end
    end
    

上記でsaltやらpepperの扱いは注意が必要です。というのもこれがわかってしまうと復号されてしまうわけで、可能なら環境変数等へ登録して、そこから読み取るような仕組みにした方が良いでしょう(今回はしていませんが)。

また、Rails.application.config.salt, Rails.application.config.pepperを書き込む必要があります(暗号化については詳細は省きます)。つまり、

config/appliction.rbの上の方に

1
    require 'aes'

class宣言の中に

1
2
3
      class Application < Rails::Application
        config.pepper = 'fca0c29958bcddb613c9b94cbb537761536433d5da7c533486f003e367c72f6d1b280fbd2646d042170a4541bcb0127b0713786aaa23786a3031d564e2feaca0'
        config.salt        = ::AES.iv(:base_64)

を書き込みます。

また、libディレクトリへの検索パスを通すためにconfig/application.rbに次のものを書き込みます。

1
2
    config.autoload_paths += %W(#{config.root}/lib)
    config.eager_load_paths += %W(#{config.root}/lib)

サンプルデータの作成

ついでに当面必要なusersテーブルのレコードを作成してきます。そのためにGemfileに次の2行を書き込み(当面forgeryしか使っていませんが)

1
2
gem 'faker'
gem 'forgery'

そして、

1
bundle install

を実行します。

次にdb/seeds.rbに次のコードを書き込み

1
2
3
10.times do
  User.create(email: Forgery('email').address, password: Devise::Encryptable::Encryptors::Aes256.digest(Forgery(:basic).password))
end

これで

1
rails db:seed

で、サンプルデータが投入されます(10人分)。

bootstrapの導入

Bootstrapを使ってデザインしていこうと考えているので、以下のことを行います。

プロジェクトのトップにあるGemfileの中に

1
 gem 'bootstrap-sass'

を書き込み、ターミナルで

1
bunlde install

を実行します。

次に、app/javascript/src/application.cssというファイル(上でコピーしていたもの)をapp/javascript/src/application.scssにリネームします。これは拡張子をcssからscssに変えているだけです。

ただし、中身を少々変えます。

1
2
*= require_tree .
*= require_self

という2行しかないところを(それ以外はコメント)、2行自体もコメントにします。つまり、*イコールをとります}。

1
2
* require_tree .
* require_self

そして、次の2行をコメントが終わったあたりに書き込みます。

1
2
    @import "bootstrap-sprockets";
    @import "bootstrap";

また、JavaScriptも使えるように、app/javascript/pack/application.jsに以下の1行を書き込みます。

1
//= require bootstrap-sprockets

application.html.erb

app/views/layouts/application.html.erb

は、特に指定しない限り、通常すべてのページで読み込まれ、今後作成するnew.html.erbなどをこれから読み込む、という形になります。

つまり、

app/views/layouts/application.html.erb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
  <head>
    <title>Shinra</title>
    <%= csrf_meta_tags %>

    <%= stylesheet_link_tag    'application', media: 'all' %>
    <%= javascript_pack_tag 'application' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

の下の方にある<%= yield %>のところにnew.html.erbとかの内容と置き換わる仕組みです。

さて、Bootstrapではcontainerクラスのdivで囲むのが一般的(僕の場合だけの可能性が・・・汗)なので、上記を

app/views/layouts/application.html.erb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
  <head>
    <title>森羅万象を記す</title>
    <%= csrf_meta_tags %>

    <%= stylesheet_link_tag    'application', media: 'all' %>
    <%= javascript_pack_tag 'application' %>
  </head>

  <body>
    <div class="container">
      <%= yield %>
    </div>
</html>

と書き換えます(12〜14行目)。タイトルがShinraではダメなので(と思うので)、森羅万象を記すと書き換えました。

config/routes.rb

config/routes.rbに次の1行を書き込みます。

1
 resources :users, only: %i(index)

とりあえず、ここまでの確認

ターミナルで、

1
rails s

http://localhost:3000/usersにブラウザでアクセスすると、次のように表示されればOKです。

first_confirm.png

シェア
#内容発言者

WindowsでRails、ついでにHeroku

thisにまつわる話し

坂井和郎