柳屋

ソースコードのリファクタからよもやままで

Lumenで継承を使ってAPIをサクッと作ってみた

LumenでAPIを作ったんですが、APIってCRUDベースで作れば事足りる機能がほとんどかなと思い、継承を使ってサクッと作ってみました。 継承を使えば記述量もぐっと減るし、うまく使えれば大変便利です それにバシーっと決まれば個人的にめっさ気持ち良いですよね

環境は以下のを使います

yanagiya.hatenadiary.com

構成

アセット 1.png

上記図の通り親クラスとなる ApiController に具体的な処理を書き それを継承するようにします 継承先はControllerとModelがあり紐づいてます

CRUDをベースとなるコントローラに作成する

メソッドはCRUDを基とし以下のようになっています

  • index→一覧
  • show→詳細
  • store→登録
  • update→更新
  • destroy→削除
app/Http/Controllers/ApiController.php
<?php

namespace App\Http\Controllers;

use Laravel\Lumen\Routing\Controller as BaseController;
use Illuminate\Http\Request;

class ApiController extends BaseController
{
    protected static $model = "";
    
    public function index()
    {
        $response = static::$model::all();
        return response()->json($response);
    }
    
    
    public function show($id = 0)
    {
        if(! $id) {
            $response["status"] = "ng";
            $response["messages"] = "引数が不正です";
            
            return response()->json($response);
        }
        $response = static::$model::find($id);
        
        if(count($response) < 1) {
            $response["status"] = "ng";
            $response["messages"] = "情報が見つかりません";
            
            return response()->json($response);    
        }
        
        $response["status"] = "ok";
        return response()->json($response);
    }
    
    
    public function store(Request $request)
    {
        $validator = static::$model::validation($request);

        $response = [];
        if ($validator->fails()) {
            $response["status"] = "ng";
            $response["messages"] = $validator->errors()->all();

            return response()->json($response);
        }
        
        
        static::$model::create($request->all());
        
        $response["status"] = "ok";
        return response()->json($response);
    }
    
    
    public function update(Request $request, $id)
    {
        $updates = $request->all();
        unset($updates['api_key']);

        $response = false;
        $response = static::$model::where('id', '=', $id)->update($updates);
        
        if ($response === 0) {
            $response["status"] = "ng";
            $response["messages"] = "パラメータが不正です";

            return response()->json($response);
        }
        
        $response["status"] = "ok";
        return response()->json($response);
    }
    

    public function destroy($id)
    {
        $result = static::$model::destroy($id);
        if ($result === 0) {
            $response["status"] = "ng";
            $response["messages"] = "パラメータが不正です";

            return response()->json($response);
        }
        
        $response["status"] = "ok";
        return response()->json($response);
    }
}

HTTPレスポンスコードの定義してへんやんとかページネーションは?とかのツッコミは一旦なしで

モデルの定義

    protected static $model = "";
    

上記の通り、モデルは継承先で定義するため、親クラスでは空で設定します

バリデーションの定義

<?php

namespace App\Http\Controllers;

use Laravel\Lumen\Routing\Controller as BaseController;
use Illuminate\Http\Request;

class ApiController extends BaseController
{
    〜省略〜        
    
    public function store(Request $request)
    {
        $validator = static::$model::validation($request);

        $response = [];
        if ($validator->fails()) {
            $response["status"] = "ng";
            $response["messages"] = $validator->errors()->all();

            return response()->json($response);
        }
        
        
        static::$model::create($request->all());
        
        $response["status"] = "ok";
        return response()->json($response);
    }
    〜省略〜
}

$validator = static::$model::validation($request);

バリデーションもモデル側で定義することにより継承をやりやすくしております

モデルの定義

app/Company.php
<?php namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Validator;

class Company extends Model 
{
    protected $table = 'companies';
    protected $fillable = ['name'];
    
    public static function validation($request)
    {
        return Validator::make($request->all(), [
            'name' => 'required|max:256|unique:companies',
        ]);
    }
}

とりあえずCompanyモデルを例としています 上記の通りバリデーションを書いてます

LumenはデフォルトでModelディレクトリがないのでこのパスになってますが、Modelディレクトリ作っても良いかもですね。

コントロラーの定義

<?php

namespace App\Http\Controllers;

use App\Company;

class CompanyController extends ApiController
{
    protected static $model = "App\Company";
}

モデルと同様にCompanyコントローラを例としています

最初に作成した ApiController を継承しています そして$modelをComapnyモデルを使うように上書きしています

一点注意としてクラス名を動的に設定する場合はnamespaceを含め設定する必要があります

確認

これでAPIの作成は終わったのでcurlなりPostmanなりで ドメイン/company/ドメイン/company/storeにアクセスし正常に動作すれば完成です

コントロラーを増やしたければCompanyを基にモデルとコントロラーを作成すれば簡単に増やせます

以上です

ネストされたvue routerを別ファイルで管理する

Vue.jsでSPAを作っていると、ルーティングを機能単位で分けたくなります

例えば、ユーザに関する機能があったとして、ユーザの登録、更新、削除、詳細(所謂 CRUD )それぞれにルーティングを設定する必要があり、それを一つの機能としてルーティングもまとめた方が将来的にも見通しがよくなります。

今回はcompanyに対するCRUDを設定していきます。 環境は以前に作成したものを使います。
詳しくは以下の記事をご覧ください

yanagiya.hatenadiary.com

ルーティング設定

src/router/index.jsにComapny-routesファイルを読み込み、そのファイルで設定を書くようにします。

src/router/index.js

 import Vue from 'vue'
 import Router from 'vue-router'
 import HelloWorld from '@/components/HelloWorld'
+import CompanyRoutes from './company-routes'
 
 Vue.use(Router)
 
 export default new Router({
   routes: [
     {
       path: '/',
       name: 'HelloWorld',
       component: HelloWorld
-    }
+    },
+    { ...CompanyRoutes }
   ]
 })

vue-routerの動作モードをhistoryモードに指定する

vue-routerをhistoryモードで動作するように修正します

historyモードはHTML5のHistory APIを使ったもので、これは/companies/5のようなきれいなURLとなります。

src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import CompanyRoutes from './company-routes'

Vue.use(Router)

export default new Router({
+  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: HelloWorld
    },
    { ...CompanyRoutes }
  ]
})

company-routes.js作る

src/router/company-routes.jsを作成し それに設定を書いていきます

src/router/company-routes.js

import Company from '@/components/company/Company'
import Index from '@/components/company/Index'
import Create from '@/components/company/Create'
import Update from '@/components/company/Update'
import Delete from '@/components/company/Delete'

export default {
  path: '/company',
  component: Company,
  children: [
    {
      path: '',
      component: Index
    },
    {
      path: 'create',
      component: Create
    },
    {
      path: 'update/:id',
      component: Update
    },
    {
      path: 'delete',
      component: Delete
    }
  ]
}


コンポーネントを作成する

src/components/company に上記で定義したコンポーネント作ります。

さらに src/components/company にあるコンポーネントを読み込むために src/components/company/Company.vue を作ります。
このファイルが無いと正常に読み込まれません

src/components/company/Company.vue

<template>
  <div>
    <router-view></router-view>
  </div>
</template>

これで以下のようにルーティングが設定されアクセスできるようになりました。

  • ドメイン/company/
  • ドメイン/company/create
  • ドメイン/company/update/1
  • ドメイン/company/delete/1

以上です。

Cloud9にPHP7 + Lumenをインストール

PHPでAPIを作りたかったので LARAVEL ベースの軽量フレームワーク『Lumen』を使いました。

今回はAPIだけなのでマイクロフレームワークを選択しました

Cloud9にはデフォルトではPHP5系がインストールされているので7系をインストールしLumenをインストールします

PHP7インストール

まずはこのサイトを元にPHP7をインストール

sudo add-apt-repository ppa:ondrej/php
sudo apt-get update
sudo apt-get install libapache2-mod-php7.0
sudo a2dismod php5
sudo a2enmod php7.0
sudo apt-get install php7.0-dom
sudo apt-get install php7.0-mbstring
sudo apt-get install php7.0-zip

PHPのバージョン確認

以下のコマンドでPHP7がインストールされている確認

php -v

# 以下のように表示されていれば正常にインストールされている
PHP 7.0.26-1+ubuntu14.04.1+deb.sury.org+1 (cli) (built: Nov 29 2017 10:01:47) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies
    with Zend OPcache v7.0.26-1+ubuntu14.04.1+deb.sury.org+1, Copyright (c) 1999-2017, by Zend Technologies

MySQL用ドライバーインストール

PHP7のMySQL用ドライバーをインストールする

sudo apt-get install php7.0-mysql

Lumenをインストール

Composerを使ってLumenをインストールします 今回はtestと言うプロジェクト名で作ります

composer create-project --prefer-dist laravel/lumen test

Lumenの初期設定

.envファイルの設定

APPKEYの設定とDBの設定を行います DBの設定はCloud9の初期設定状態となってます。

APP_ENV=local
APP_DEBUG=true
- APP_KEY=
+ APP_KEY="EttTJfhKALNC_37cggwwbbRu6QeXXzsaj9c4fnfK_ZN2jz6Ly8ML_6UQKLP7_-2Q"
APP_TIMEZONE=UTC

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
- DB_DATABASE=homestead
- DB_USERNAME=homestead
- DB_PASSWORD=secret
+ DB_DATABASE=c9
+ DB_USERNAME=root
+ DB_PASSWORD=

CACHE_DRIVER=file
QUEUE_DRIVER=sync

MySQLの起動

初期起動時にはMySQLは起動していないので以下コマンドにてMySQLを起動します

mysql-ctl start

apacheのルートディレクトリの変更

apacheのルートディレクトリをLumenのpublicディレクトリに変更します。

sudo vi /etc/apache2/sites-enabled/001-cloud9.conf  

以下のように/etc/apache2/sites-enabled/001-cloud9.confのDocumentRootを/home/ubuntu/workspace/{プロジェクト名}/publicに変更します

<VirtualHost *:8080>
-    DocumentRoot /home/ubuntu/workspace
+   DocumentRoot /home/ubuntu/workspace/test/public
    ServerName https://${C9_HOSTNAME}:443

    LogLevel info

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

    <Directory /home/ubuntu/workspace>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

ServerName https://${C9_HOSTNAME}
search hit BOTTOM, continuing at TOP  

起動確認

アセット_1.png 上記スクリーンショットの通り「Run Project」を押下し

https://{プルジェクト名}-{ユーザ名}.c9users.io/

にアクセスし以下の通り表示されれば完了です アセット 1.png

VueJS + AdminLTEで管理画面を作る

管理画面を作る事になったんですが、せっかくなのでSPAでやってみたいなってなったのでVueJSで作ることにしました。

HTML CSSはサクッと作りたいなと思ったので、管理画面作成に特化したBootstrapベースの AdminLTE を使うことにしました

VueJS環境作成

yanagiya.hatenadiary.com

以前に書いた記事を参考にvue-cliで環境を作ります

npm install -g vue-cli
vue init webpack .
npm install

AdminLteインストール

github.com

調べてみるとVueJS用のnpmがあったのでそれを使いました

npm i --save vue2-admin-lte

設定

AdminLteをインストールしたら
GitHubのページに書いてある通りに設定していきます

webpackにエイリアス設定を追加する

build/webpack.base.conf.jsに以下を追記しエイリアスを設定します

     alias: {
       'vue$': 'vue/dist/vue.esm.js',
       '@': resolve('src'),
+      'va': 'vue2-admin-lte/src'

AdminLteのCSSとJSを読み込む

src/main.jsに以下を追記し、AdminLteのCSSとJSを読み込みます

 import Vue from 'vue'
 import App from './App'
 import router from './router'
+import 'va/lib/css'
+import 'va/lib/script'

コンポーネント作成

設定は終わったので、次は雛形を作ります。
vue2-admin-lteのサンプルがあるんですが、画像にbase64使ってたりするので今回は本家の方を使います

ヘッダー

src/components/MyHeader.vue
を作りヘッダ用のコンポーネントとします
先ほどのURLのヘッダの部分をコピーし貼り付けます
ソースは以下の通り

<template>
  <header class="main-header">
    <!-- Logo -->
    <a href="index2.html" class="logo">
      <!-- mini logo for sidebar mini 50x50 pixels -->
      <span class="logo-mini"><b>A</b>LT</span>
      <!-- logo for regular state and mobile devices -->
      <span class="logo-lg"><b>Admin</b>LTE</span>
    </a>

    <!-- Header Navbar: style can be found in header.less -->
    <nav class="navbar navbar-static-top">
      <!-- Sidebar toggle button-->
      <a href="#" class="sidebar-toggle" data-toggle="push-menu" role="button">
        <span class="sr-only">Toggle navigation</span>
      </a>
      <!-- Navbar Right Menu -->
      <div class="navbar-custom-menu">
        <ul class="nav navbar-nav">
          <!-- Messages: style can be found in dropdown.less-->
          <li class="dropdown messages-menu">
            <a href="#" class="dropdown-toggle" data-toggle="dropdown">
              <i class="fa fa-envelope-o"></i>
              <span class="label label-success">4</span>
            </a>
            <ul class="dropdown-menu">
              <li class="header">You have 4 messages</li>
              <li>
                <!-- inner menu: contains the actual data -->
                <ul class="menu">
                  <li><!-- start message -->
                    <a href="#">
                      <div class="pull-left">
                        <img src="dist/img/user2-160x160.jpg" class="img-circle" alt="User Image">
                      </div>
                      <h4>
                        Support Team
                        <small><i class="fa fa-clock-o"></i> 5 mins</small>
                      </h4>
                      <p>Why not buy a new awesome theme?</p>
                    </a>
                  </li>
                  <!-- end message -->
                  <li>
                    <a href="#">
                      <div class="pull-left">
                        <img src="dist/img/user3-128x128.jpg" class="img-circle" alt="User Image">
                      </div>
                      <h4>
                        AdminLTE Design Team
                        <small><i class="fa fa-clock-o"></i> 2 hours</small>
                      </h4>
                      <p>Why not buy a new awesome theme?</p>
                    </a>
                  </li>
                  <li>
                    <a href="#">
                      <div class="pull-left">
                        <img src="dist/img/user4-128x128.jpg" class="img-circle" alt="User Image">
                      </div>
                      <h4>
                        Developers
                        <small><i class="fa fa-clock-o"></i> Today</small>
                      </h4>
                      <p>Why not buy a new awesome theme?</p>
                    </a>
                  </li>
                  <li>
                    <a href="#">
                      <div class="pull-left">
                        <img src="dist/img/user3-128x128.jpg" class="img-circle" alt="User Image">
                      </div>
                      <h4>
                        Sales Department
                        <small><i class="fa fa-clock-o"></i> Yesterday</small>
                      </h4>
                      <p>Why not buy a new awesome theme?</p>
                    </a>
                  </li>
                  <li>
                    <a href="#">
                      <div class="pull-left">
                        <img src="dist/img/user4-128x128.jpg" class="img-circle" alt="User Image">
                      </div>
                      <h4>
                        Reviewers
                        <small><i class="fa fa-clock-o"></i> 2 days</small>
                      </h4>
                      <p>Why not buy a new awesome theme?</p>
                    </a>
                  </li>
                </ul>
              </li>
              <li class="footer"><a href="#">See All Messages</a></li>
            </ul>
          </li>
          <!-- Notifications: style can be found in dropdown.less -->
          <li class="dropdown notifications-menu">
            <a href="#" class="dropdown-toggle" data-toggle="dropdown">
              <i class="fa fa-bell-o"></i>
              <span class="label label-warning">10</span>
            </a>
            <ul class="dropdown-menu">
              <li class="header">You have 10 notifications</li>
              <li>
                <!-- inner menu: contains the actual data -->
                <ul class="menu">
                  <li>
                    <a href="#">
                      <i class="fa fa-users text-aqua"></i> 5 new members joined today
                    </a>
                  </li>
                  <li>
                    <a href="#">
                      <i class="fa fa-warning text-yellow"></i> Very long description here that may not fit into the
                      page and may cause design problems
                    </a>
                  </li>
                  <li>
                    <a href="#">
                      <i class="fa fa-users text-red"></i> 5 new members joined
                    </a>
                  </li>
                  <li>
                    <a href="#">
                      <i class="fa fa-shopping-cart text-green"></i> 25 sales made
                    </a>
                  </li>
                  <li>
                    <a href="#">
                      <i class="fa fa-user text-red"></i> You changed your username
                    </a>
                  </li>
                </ul>
              </li>
              <li class="footer"><a href="#">View all</a></li>
            </ul>
          </li>
          <!-- Tasks: style can be found in dropdown.less -->
          <li class="dropdown tasks-menu">
            <a href="#" class="dropdown-toggle" data-toggle="dropdown">
              <i class="fa fa-flag-o"></i>
              <span class="label label-danger">9</span>
            </a>
            <ul class="dropdown-menu">
              <li class="header">You have 9 tasks</li>
              <li>
                <!-- inner menu: contains the actual data -->
                <ul class="menu">
                  <li><!-- Task item -->
                    <a href="#">
                      <h3>
                        Design some buttons
                        <small class="pull-right">20%</small>
                      </h3>
                      <div class="progress xs">
                        <div class="progress-bar progress-bar-aqua" style="width: 20%" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100">
                          <span class="sr-only">20% Complete</span>
                        </div>
                      </div>
                    </a>
                  </li>
                  <!-- end task item -->
                  <li><!-- Task item -->
                    <a href="#">
                      <h3>
                        Create a nice theme
                        <small class="pull-right">40%</small>
                      </h3>
                      <div class="progress xs">
                        <div class="progress-bar progress-bar-green" style="width: 40%" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100">
                          <span class="sr-only">40% Complete</span>
                        </div>
                      </div>
                    </a>
                  </li>
                  <!-- end task item -->
                  <li><!-- Task item -->
                    <a href="#">
                      <h3>
                        Some task I need to do
                        <small class="pull-right">60%</small>
                      </h3>
                      <div class="progress xs">
                        <div class="progress-bar progress-bar-red" style="width: 60%" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100">
                          <span class="sr-only">60% Complete</span>
                        </div>
                      </div>
                    </a>
                  </li>
                  <!-- end task item -->
                  <li><!-- Task item -->
                    <a href="#">
                      <h3>
                        Make beautiful transitions
                        <small class="pull-right">80%</small>
                      </h3>
                      <div class="progress xs">
                        <div class="progress-bar progress-bar-yellow" style="width: 80%" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100">
                          <span class="sr-only">80% Complete</span>
                        </div>
                      </div>
                    </a>
                  </li>
                  <!-- end task item -->
                </ul>
              </li>
              <li class="footer">
                <a href="#">View all tasks</a>
              </li>
            </ul>
          </li>
          <!-- User Account: style can be found in dropdown.less -->
          <li class="dropdown user user-menu">
            <a href="#" class="dropdown-toggle" data-toggle="dropdown">
              <img src="dist/img/user2-160x160.jpg" class="user-image" alt="User Image">
              <span class="hidden-xs">Alexander Pierce</span>
            </a>
            <ul class="dropdown-menu">
              <!-- User image -->
              <li class="user-header">
                <img src="dist/img/user2-160x160.jpg" class="img-circle" alt="User Image">

                <p>
                  Alexander Pierce - Web Developer
                  <small>Member since Nov. 2012</small>
                </p>
              </li>
              <!-- Menu Body -->
              <li class="user-body">
                <div class="row">
                  <div class="col-xs-4 text-center">
                    <a href="#">Followers</a>
                  </div>
                  <div class="col-xs-4 text-center">
                    <a href="#">Sales</a>
                  </div>
                  <div class="col-xs-4 text-center">
                    <a href="#">Friends</a>
                  </div>
                </div>
                <!-- /.row -->
              </li>
              <!-- Menu Footer-->
              <li class="user-footer">
                <div class="pull-left">
                  <a href="#" class="btn btn-default btn-flat">Profile</a>
                </div>
                <div class="pull-right">
                  <a href="#" class="btn btn-default btn-flat">Sign out</a>
                </div>
              </li>
            </ul>
          </li>
          <!-- Control Sidebar Toggle Button -->
          <li>
            <a href="#" data-toggle="control-sidebar"><i class="fa fa-gears"></i></a>
          </li>
        </ul>
      </div>
    </nav>
  </header>
</template>

<script>
export default {
  name: 'MyHeader',
}
</script>

サイドバー

ヘッダと同様に src/components/MyAside.vue
を作りサイドバー用のコンポーネントとします
ソースは以下の通り

<template>
  <aside class="main-sidebar">
    <!-- sidebar: style can be found in sidebar.less -->
    <section class="sidebar" style="height: auto;">
      <!-- Sidebar user panel -->
      <div class="user-panel">
        <div class="pull-left image">
          <img src="dist/img/user2-160x160.jpg" class="img-circle" alt="User Image">
        </div>
        <div class="pull-left info">
          <p>Alexander Pierce</p>
          <a href="#"><i class="fa fa-circle text-success"></i> Online</a>
        </div>
      </div>
      <!-- search form -->
      <form action="#" method="get" class="sidebar-form">
        <div class="input-group">
          <input type="text" name="q" class="form-control" placeholder="Search...">
          <span class="input-group-btn">
                <button type="submit" name="search" id="search-btn" class="btn btn-flat">
                  <i class="fa fa-search"></i>
                </button>
              </span>
        </div>
      </form>
      <!-- /.search form -->
      <!-- sidebar menu: : style can be found in sidebar.less -->
      <ul class="sidebar-menu tree" data-widget="tree">
        <li class="header">MAIN NAVIGATION</li>
        <li class="active treeview menu-open">
          <a href="#">
            <i class="fa fa-dashboard"></i> <span>Dashboard</span>
            <span class="pull-right-container">
              <i class="fa fa-angle-left pull-right"></i>
            </span>
          </a>
          <ul class="treeview-menu">
            <li><a href="index.html"><i class="fa fa-circle-o"></i> Dashboard v1</a></li>
            <li class="active"><a href="index2.html"><i class="fa fa-circle-o"></i> Dashboard v2</a></li>
          </ul>
        </li>
        <li class="treeview">
          <a href="#">
            <i class="fa fa-files-o"></i>
            <span>Layout Options</span>
            <span class="pull-right-container">
              <span class="label label-primary pull-right">4</span>
            </span>
          </a>
          <ul class="treeview-menu">
            <li><a href="pages/layout/top-nav.html"><i class="fa fa-circle-o"></i> Top Navigation</a></li>
            <li><a href="pages/layout/boxed.html"><i class="fa fa-circle-o"></i> Boxed</a></li>
            <li><a href="pages/layout/fixed.html"><i class="fa fa-circle-o"></i> Fixed</a></li>
            <li><a href="pages/layout/collapsed-sidebar.html"><i class="fa fa-circle-o"></i> Collapsed Sidebar</a></li>
          </ul>
        </li>
        <li>
          <a href="pages/widgets.html">
            <i class="fa fa-th"></i> <span>Widgets</span>
            <span class="pull-right-container">
              <small class="label pull-right bg-green">new</small>
            </span>
          </a>
        </li>
        <li class="treeview">
          <a href="#">
            <i class="fa fa-pie-chart"></i>
            <span>Charts</span>
            <span class="pull-right-container">
              <i class="fa fa-angle-left pull-right"></i>
            </span>
          </a>
          <ul class="treeview-menu">
            <li><a href="pages/charts/chartjs.html"><i class="fa fa-circle-o"></i> ChartJS</a></li>
            <li><a href="pages/charts/morris.html"><i class="fa fa-circle-o"></i> Morris</a></li>
            <li><a href="pages/charts/flot.html"><i class="fa fa-circle-o"></i> Flot</a></li>
            <li><a href="pages/charts/inline.html"><i class="fa fa-circle-o"></i> Inline charts</a></li>
          </ul>
        </li>
        <li class="treeview">
          <a href="#">
            <i class="fa fa-laptop"></i>
            <span>UI Elements</span>
            <span class="pull-right-container">
              <i class="fa fa-angle-left pull-right"></i>
            </span>
          </a>
          <ul class="treeview-menu">
            <li><a href="pages/UI/general.html"><i class="fa fa-circle-o"></i> General</a></li>
            <li><a href="pages/UI/icons.html"><i class="fa fa-circle-o"></i> Icons</a></li>
            <li><a href="pages/UI/buttons.html"><i class="fa fa-circle-o"></i> Buttons</a></li>
            <li><a href="pages/UI/sliders.html"><i class="fa fa-circle-o"></i> Sliders</a></li>
            <li><a href="pages/UI/timeline.html"><i class="fa fa-circle-o"></i> Timeline</a></li>
            <li><a href="pages/UI/modals.html"><i class="fa fa-circle-o"></i> Modals</a></li>
          </ul>
        </li>
        <li class="treeview">
          <a href="#">
            <i class="fa fa-edit"></i> <span>Forms</span>
            <span class="pull-right-container">
              <i class="fa fa-angle-left pull-right"></i>
            </span>
          </a>
          <ul class="treeview-menu">
            <li><a href="pages/forms/general.html"><i class="fa fa-circle-o"></i> General Elements</a></li>
            <li><a href="pages/forms/advanced.html"><i class="fa fa-circle-o"></i> Advanced Elements</a></li>
            <li><a href="pages/forms/editors.html"><i class="fa fa-circle-o"></i> Editors</a></li>
          </ul>
        </li>
        <li class="treeview">
          <a href="#">
            <i class="fa fa-table"></i> <span>Tables</span>
            <span class="pull-right-container">
              <i class="fa fa-angle-left pull-right"></i>
            </span>
          </a>
          <ul class="treeview-menu">
            <li><a href="pages/tables/simple.html"><i class="fa fa-circle-o"></i> Simple tables</a></li>
            <li><a href="pages/tables/data.html"><i class="fa fa-circle-o"></i> Data tables</a></li>
          </ul>
        </li>
        <li>
          <a href="pages/calendar.html">
            <i class="fa fa-calendar"></i> <span>Calendar</span>
            <span class="pull-right-container">
              <small class="label pull-right bg-red">3</small>
              <small class="label pull-right bg-blue">17</small>
            </span>
          </a>
        </li>
        <li>
          <a href="pages/mailbox/mailbox.html">
            <i class="fa fa-envelope"></i> <span>Mailbox</span>
            <span class="pull-right-container">
              <small class="label pull-right bg-yellow">12</small>
              <small class="label pull-right bg-green">16</small>
              <small class="label pull-right bg-red">5</small>
            </span>
          </a>
        </li>
        <li class="treeview">
          <a href="#">
            <i class="fa fa-folder"></i> <span>Examples</span>
            <span class="pull-right-container">
              <i class="fa fa-angle-left pull-right"></i>
            </span>
          </a>
          <ul class="treeview-menu">
            <li><a href="pages/examples/invoice.html"><i class="fa fa-circle-o"></i> Invoice</a></li>
            <li><a href="pages/examples/profile.html"><i class="fa fa-circle-o"></i> Profile</a></li>
            <li><a href="pages/examples/login.html"><i class="fa fa-circle-o"></i> Login</a></li>
            <li><a href="pages/examples/register.html"><i class="fa fa-circle-o"></i> Register</a></li>
            <li><a href="pages/examples/lockscreen.html"><i class="fa fa-circle-o"></i> Lockscreen</a></li>
            <li><a href="pages/examples/404.html"><i class="fa fa-circle-o"></i> 404 Error</a></li>
            <li><a href="pages/examples/500.html"><i class="fa fa-circle-o"></i> 500 Error</a></li>
            <li><a href="pages/examples/blank.html"><i class="fa fa-circle-o"></i> Blank Page</a></li>
            <li><a href="pages/examples/pace.html"><i class="fa fa-circle-o"></i> Pace Page</a></li>
          </ul>
        </li>
        <li class="treeview">
          <a href="#">
            <i class="fa fa-share"></i> <span>Multilevel</span>
            <span class="pull-right-container">
              <i class="fa fa-angle-left pull-right"></i>
            </span>
          </a>
          <ul class="treeview-menu">
            <li><a href="#"><i class="fa fa-circle-o"></i> Level One</a></li>
            <li class="treeview">
              <a href="#"><i class="fa fa-circle-o"></i> Level One
                <span class="pull-right-container">
                  <i class="fa fa-angle-left pull-right"></i>
                </span>
              </a>
              <ul class="treeview-menu">
                <li><a href="#"><i class="fa fa-circle-o"></i> Level Two</a></li>
                <li class="treeview">
                  <a href="#"><i class="fa fa-circle-o"></i> Level Two
                    <span class="pull-right-container">
                      <i class="fa fa-angle-left pull-right"></i>
                    </span>
                  </a>
                  <ul class="treeview-menu">
                    <li><a href="#"><i class="fa fa-circle-o"></i> Level Three</a></li>
                    <li><a href="#"><i class="fa fa-circle-o"></i> Level Three</a></li>
                  </ul>
                </li>
              </ul>
            </li>
            <li><a href="#"><i class="fa fa-circle-o"></i> Level One</a></li>
          </ul>
        </li>
        <li><a href="https://adminlte.io/docs"><i class="fa fa-book"></i> <span>Documentation</span></a></li>
        <li class="header">LABELS</li>
        <li><a href="#"><i class="fa fa-circle-o text-red"></i> <span>Important</span></a></li>
        <li><a href="#"><i class="fa fa-circle-o text-yellow"></i> <span>Warning</span></a></li>
        <li><a href="#"><i class="fa fa-circle-o text-aqua"></i> <span>Information</span></a></li>
      <li class="bg-green"><a href="https://themequarry.com"><i class="fa fa-star-o" style="color: rgb(255, 255, 255);"></i><span style="color: rgb(255, 255, 255);">Premium Templates</span></a></li></ul>
    </section>
    <!-- /.sidebar -->
  </aside>
</template>

<script>
export default {
  name: 'MyAside',
}
</script>

コンポーネントを読み込む

上記で作ったコンポーネントを読み込み使えるようにします

src/App.vueに以下の通り修正します

 <template>
-  <div id="app">
-    <img src="./assets/logo.png">
-    <router-view/>
+  <div id="app">  
+    <div class="wrapper">
+      <MyHeader></MyHeader>
+      <MyAside></MyAside>
+      <div class="content-wrapper"></div>
+        <router-view/>
+      </div>
+    </div>
   </div>
 </template>
 
 <script>
+import MyHeader from '@/components/MyHeader'
+import MyAside from '@/components/MyAside'
+
 export default {
-  name: 'app'
+  name: 'app',
+  components: {
+    MyHeader,
+    MyAside
+  }
 }
 </script>

スキン適用

これでひとまずのベースはできたんですが、この状態で起動するとヘッダとサイドバーの背景が真っ白なのでスキンを適用します
これがよくわからなくて少しハマりましたw

index.htmlを以下の通り修正します

-  <body>
+  <body class="skin-blue sidebar-mini">
     <div id="app"></div>

確認

この状態で

npm run dev

で起動し以下のように表示されればひとまずは完成です アセット 1.png

これをベースに管理画面を作っていきたいと思います

Cloud9でvue-cli環境を作る

f:id:ultrasevenstar:20171130150551p:plain

最近、開発はめっきりcloud9でやってます。

Cloud9便利ですよね。 環境構築する必要がないのですぐ試せるし クラウドなのでPCさえあればどこでも同じ環境ですぐ実行できます

さらに現在の現場ではプロキシかまされててGitHub等にpushできないんですが、Cloud9ではそんなん関係なくpushできる

Cloud9でvue-cliを使う

VueJSの雛形を作成するvue-cliをCloud9で使おうとしたら少しハマったので、備忘録も兼ねて

cloud9のworkspaceは Node.js を選択しております

vue-cliインストール

まずはvue-cli自体をインストールします

npm install -g vue-cli

vue.jsプロジェクトを作る

次にカレントディレクトリにvue.jsのプロジェクトを作ります

vue init webpack .
npm install

サーバを立ち上げてみる

npm run dev

デフォルトの状態で立ち上げると以下の画面になり正常に表示されません アセット 1.png

Cloud9でサーバを起動できるように修正

Cloud9ではポートやIPアドレスの設定を変更する必要が多々あり、今回のvue-cliもその対象でした。

build/webpack.dev.conf.jsの24行目付近を以下のように修正します

devServer: {
  clientLogLevel: 'warning',
  historyApiFallback: true,
  hot: true,
  compress: true,
- host: process.env.HOST || config.dev.host,
- port: process.env.PORT || config.dev.port,
+ host: '0.0.0.0',
+ port: '8080',
+ disableHostCheck: true,

サーバを立ち上げてみる

npm run dev

この状態でサーバを立ち上げると以下のページが表示され正常に起動することができました アセット 1.png

以上です

Rails5で都道府県、市区町村のマスターデータをワンライナーで作成

郵便局が提供している郵便番号データから都道府県と市区町村のMySQLのマスタデータをワンライナーで作成します

処理の流れ

  • 郵便番号データを郵便局のサイトからダウンロード
  • ダウンロードしたZIPファイルを解凍し保存
  • 保存したCSVファイルを読み込みMySQLに保存

テーブル作成

最終的に完成するテーブルは以下のようになります 今回は必要最小限の構造となってます。

都道府県テーブル

id name
1 北海道
2 青森県

市区町村テーブル

id pref_id name
1 1 札幌市北区

マイグレーション

都道府県テーブル

class CreatePrefs < ActiveRecord::Migration[5.1]
  def change
    create_table :prefs do |t|
      t.string :name, :limit => 4, :unique => true

      t.timestamps
    end
  end
end

市区町村テーブル

class CreateCities < ActiveRecord::Migration[5.1]
  def change
    create_table :cities do |t|
      t.integer :pref_id
      t.string :name, :limit => 16

      t.timestamps
    end
  end
end

マイグレーション実行

マイグレーションを実行しテーブル作成

rails db:migrate

seedファイル

今回は初期データを追加するので db/seeds.rbに追記していきます

require 'csv'
require 'zip'

DLURL           = "http://www.post.japanpost.jp/zipcode/dl/kogaki/zip/ken_all.zip"
SAVEDIR         = "db/"
CSVROW_PREFNAME = 6
CSVROW_CITYNAME = 7

savePath = ""

# 住所CSVダウンロード、解答し保存
open(URI.escape(DLURL)) do |file|
  ::Zip::File.open_buffer(file.read) do |zf|
    zf.each do |entry|
      savePath = SAVEDIR + entry.name
      zf.extract(entry, savePath) { true }
    end
  end
end

# 住所CSVを読み込みDBに保存
CSV.foreach(savePath, encoding: "Shift_JIS:UTF-8") do |row|
  prefName = row[CSVROW_PREFNAME]
  cityName = row[CSVROW_CITYNAME]
  pref = Pref.find_or_create_by(:name => prefName)
  City.find_or_create_by(:name => cityName, pref_id: pref.id)
end

File.unlink savePath

seed実行

以下コマンドで上記スクリプトを実行するとデータが完成

rails db:seed

Lumenでクロスドメインアクセス設定

f:id:ultrasevenstar:20171129113235j:plain

現在 VueJSとLumenでSPAのサービスを作っているのですが クロスドメインに引っかかり一日がっつりハマったので備忘録も兼ねて 記述

結論

色々試行錯誤したのですが結局以下で出来ました。

Lumenのapi/public/index.phpに以下を追記

header('Access-Control-Allow-Origin: *');
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept");
header('Access-Control-Allow-Methods: GET, POST, PUT');

CORSについて

詳しくは https://dev.classmethod.jp/etc/about-cors/ ここなどに譲るとして、簡単に言うとJSONPは裏技的な感じなので、正式にクロスドメインアクセスを実現するための仕様です。

色々調べてるとLumenのCORS対応のライブラリインストールするとか js側でheaderを設定するとかあったんですが 結局はindex.phpにheaderの設定を書くだけで可能でした。

改修

今のソースではAccess-Control-Allow-Originが全て許可する設定になっているのでここはURL設定する方が良いと思います

;(function(document){ var pres = document.getElementsByTagName("pre") for(var i=pres.length; i--; ){  var el = makeOl(pres[i]) pres[i].appendChild(el) } function makeOl(pre){ if (pre.className.indexOf("gist") !== -1) { return } var ol = document.createElement("ol") , li = document.createElement("li") , df = document.createDocumentFragment() , br = pre.innerHTML.match(/\n/g) || 0 ol.className = "preLine" ol.setAttribute("role", "presentation") // no lang, no line-number if( pre.className && ! /lang-./.test(pre.className) ){ br.length += 1 } for(var i=br.length; i--; ){ var li2 = li.cloneNode(true) df.appendChild(li2) } ol.appendChild(df) return ol } })(document)