あいつの日誌β

働きながら旅しています。

okamuuu.com をはじめました。

現在 Google App EngineGolang + React での開発を行っているのですが、学習曲線を高めるために同じ構成で Blog を作ってみました。

いろいろとはまるポイントがあって学ぶポイントが結構あったので折を見て共有したいと思います。とりあえずはてなブログで書きなぐった記事を少しずつ POST して少しずつ成長させたいと思います。メリークリスマス。

https://okamuuu.com

[2019/04/02] 上記サイトは Netlify + VuePress で動いています。

MySQL で sql.NullString なあいつを JSON に Marshalling する

追記: c.Bind とかする時に type error が起きたりするので Unmarshal も追加

あらすじ

MySQL で nullable なカラムレコードを入れるために struct の定義をこうしたらレコードには null が入らずに空文字が入ってしまいます。 Something.Code には Unique 制約をつけたいので空文字ではなく Null を挿入したい。

type Something struct {
    ID int `json:"id"`
    Code string `json:"code"`
    Name string `json:"name"`
}

この問題自体は以下のように sql.NullString を使えば解決します。

type Something struct {
    ID int `json:"id"`
    Code sql.NullString `json:"code"`
    Name string `json:"name"`
}

s := Something{ID: 1, Code: sql.NullString{"", false}, Name: "something"}

なのですがこれを JSON にすると以下のようになるけどそうじゃないんだという場合にどうすればいいのかというお話です。

{
   "id" : 1,
   "code" : { "String" : "", "Valid" : false },
   "name" : "somethen"
}

結果

型を定義しました。

type NullString struct {
    sql.NullString
}

func (s *NullString) MarshalJSON() ([]byte, error) {
    if s.Valid {
        return json.Marshal(s.String)
    } else {
        return json.Marshal(nil)
    }
}

func (s *NullString) UnmarshalJSON(data []byte) error {
    var str string
    json.Unmarshal(data, &str)
    s.String = str 
    s.Valid = str != ""
    return nil 
}

func NewNullString(s string) NullString {
    return NullString{sql.NullString{s, s != ""}}
}

type Something struct {
    ID int `json:"id"`
    Code NullString `json:"code"`
    Name string `json:"name"`
}

s := Something{ID: 1, Code: NewNullString(""), Name: "something"}

めでたし。

{
   "id" : 1,
   "code" : null,
   "name" : "something"
}

はじめての elm-lang

あらすじ

grunt, gulp, webpack ツールは違えど Javascript を bundle する事前準備で疲弊していることに変わりがない今日この頃ですが、elm-lang は何も考えなくても bundle してくれるので意外とアリかもしれないぞと思ったので練習します。

いい感じの動画があったのでそちらで練習しました。 もしそちらの動画を参考にする場合は使用されている Syntax が 0.18 では削除されていて動かない箇所があるので読み替える必要があります。

  • [1..3] -> range 1 3
  • Html.App.beginnerProgram -> Html.beginnerProgram
  • type' -> type_

install

% brew install elm
% elm --version
0.18.0

hello world

% mkdir practice-elm && cd $_
% elm package install -y
% elm reactor

create exercise.elm

module Main exposing (..)

import Html

main =
  Html.text "Hello World"

確認

% open http://localhost:8000/exercise.elm

increment

create increment.elm

elm 0.18 から Html.App.beginnerProgram ではなく Html.beginnerProgram に変更されています

import Html exposing (..)
import Html.Events exposing (..)

main =
  Html.beginnerProgram { view = view, model = model, update = update }

--model
type alias Model =
  Int

model : Model
model =
  0

--update
type Msg
  = Inc
  | Dec

update : Msg -> Model -> Model
update msg model =
  case msg of
    Inc ->
      model + 1
    Dec ->
      model - 1

-- view
view : Model -> Html Msg
view model =
    div []
        [ button [ onClick Dec ] [ text "-" ]
        , div [] [ text (toString model) ]
        , button [ onClick Inc ] [ text "+" ]
        ]

まとめ

近年のフロント側の技術の進歩は目覚ましく、いろいろ覚えることがたくさんあるので elm-lang に対して最初は良い印象はありませんでした。

virtual-dom を採用しているし、 redux の思想と共通するので一度 redux が絡むアプリを触っておくと違和感がないと思います。

So The Elm Architecture is easy in Elm, but it is useful in any front-end project. In fact, projects like Redux have been inspired by The Elm Architecture, so you may have already seen derivatives of this pattern.

あと webpack 不要なのが個人的には嬉しいです。とある週末に「ひさしぶりに家でプログラミング書くか」「以前書いた webpack.config.js をコピーして...」「あれーなんかいろいろ変わってる」「やっと直った」「もう寝ないと:0」みたいな経験するぐらいなら新しい言語触りたい。

参考にした動画

exercise.elm: https://www.youtube.com/watch?v=Rf2CkojtxFw

increment.elm: https://www.youtube.com/watch?v=vb7ZdjSblok

Single Page Application の Mock Server を作る 2016

対象読者

以下のワードに興味が無い方はたぶん読まなくても良い記事です。

  • json-serverjwt
  • browsersynchttp-proxy-middleware
  • npm-run-all --parallel

準備

% mkdir practice-mock-api && cd $_
% mkdir -p mock src/components www
% npm init -y

json-server と jwt

json-server は express がベースとなっているので少し手を加えて jwt が関わる処理を追加します。 db.json は jsonplaceholder のデータを流用します。

% npm install --save-dev json-server body-parser jsonwebtoken express-jwt
% curl http://jsonplaceholder.typicode.com/db -o ~/practice-mock-api/mockServer/db.json

create mockServer.js:

const jsonServer = require('json-server')
const server = jsonServer.create()
const router = jsonServer.router('./mock/db.json')
const jwt = require('jsonwebtoken')
const expressJwt = require('express-jwt')
const bodyParser = require('body-parser')

server.use(bodyParser.urlencoded({ extended: false }))
server.use(bodyParser.json())

const SERCRET = "shhhhhhared-secret"

function createToken() {
  return jwt.sign({
    exp: Math.floor(Date.now() / 1000) + (60 * 60), // 1 hour
    data: {
      username: "okamuuu",
      roles: ["operator", "admin"],
    }
  }, SERCRET)
}

const token = createToken()

console.log("=== create new token ===")
console.log(token)

jwt.verify(token, SERCRET, (err, decoded) => {
  console.log("=== decoded ===")
  console.log(decoded)
})

server.post('/auth/login', (req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({token: createToken()}))
})

server.post('/auth/refresh', expressJwt({secret: SERCRET}), (req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({token: createToken()}))
})

server.use('/api', expressJwt({secret: SERCRET}), router)

server.listen(3000, function () {
  console.log("=== Mock API Server is running ===")
  console.log("=== USAGE ===")
  console.log(`curl -H \"Authorization: Bearer ${token}\" http://localhost:3000/api/posts`)
})

起動するとこんな感じです。

% node mockServer.js
=== create new token ===
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0ODAzOTAxNTksImRhdGEiOnsidXNlcm5hbWUiOiJva2FtdXV1Iiwicm9sZXMiOlsib3BlcmF0b3IiLCJhZG1pbiJdfSwiaWF0IjoxNDgwMzg2NTU5fQ.s5f1tRvFCwIZeJant3LLyyqWcVFlny4oQRv6KJ3PCRM
=== decoded ===
{ exp: 1480390159,
  data: { username: 'okamuuu', roles: [ 'operator', 'admin' ] },
  iat: 1480386559 }
=== Mock API Server is running ===
=== USAGE ===
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0ODAzOTAxNTksImRhdGEiOnsidXNlcm5hbWUiOiJva2FtdXV1Iiwicm9sZXMiOlsib3BlcmF0b3IiLCJhZG1pbiJdfSwiaWF0IjoxNDgwMzg2NTU5fQ.s5f1tRvFCwIZeJant3LLyyqWcVFlny4oQRv6KJ3PCRM" http://localhost:3000/api/posts

動作確認をします。無事 JWT で認証する必要がある WEB API の mock ができました。

% TOKEN=`curl -s -X "POST" http://localhost:3000/auth/login | jq .token -r`
% curl -s -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/posts | jq length
100

browsersync と http-proxy-middleware

前回の記事で紹介したように BroserSync と webpack を使って Hot Module Replacement するためのサーバーを作成します。 前回の記事との違いは port の指定と起動する度にブラウザが open しないようにオプションを追加していて、http-proxy-middleware を使って /api/auth へのリクエストを mockServer へ渡すようにしています。

% npm install --save react react-dom
% npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-stage-0 babel-preset-react
% npm install --save-dev browser-sync webpack webpack-dev-middleware webpack-hot-middleware http-proxy-middleware

create webpack.config.js

const path = require('path')
const webpack = require('webpack')

module.exports = {
  debug: true,
  devtool: 'inline-source-map',
  entry: [
    'webpack/hot/dev-server',
    'webpack-hot-middleware/client',
    './src/index.js'
  ],
  output: {
    publicPath: "/",
    path: path.resolve('www'),
    filename: "bundle.js",
  },
  plugins: [
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  ],
  resolve: {
    extensions: ['', '.js'],
  },
  module: {
    loaders:[
      { test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel'] }
    ]
  }
}

create devServer.js

var webpack = require('webpack')
var proxy = require('http-proxy-middleware');
var browserSync = require('browser-sync')
var webpackDevMiddleware = require('webpack-dev-middleware')
var webpackHotMiddleware = require('webpack-hot-middleware')

var config = require('./webpack.config')
var bundler = webpack(config)

var apiProxy = proxy('/api', {
  target: 'http://localhost:3000',
  logLevel: 'debug'
});

var authProxy = proxy('/auth', {
  target: 'http://localhost:3000',
  logLevel: 'debug'
});

browserSync({
  port: 8080,
  open: false,
  server: {
    baseDir: config.output.path,
    middleware: [
      apiProxy,
      authProxy,
      webpackDevMiddleware(bundler, {
        publicPath: config.output.publicPath,
        stats: { colors: true }
      }),
      webpackHotMiddleware(bundler)
    ]
  },
  files: [
    'www/*.html'
  ]
})

create .babelrc

{
  "presets": ["es2015", "stage-0", "react"]
}

create www/index.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  </head>
  <body>
    <div id="root"></div>
    <script src="/bundle.js"></script>
  </body>
</html>

create src/index.js

import React from 'react'
import { render } from 'react-dom'
import App from './components/App'

render(
  <App />, 
  document.getElementById('root')
)

if (module.hot) {
  module.hot.accept('./components/App.js', function() {
    const NextApp = require('./components/App').default;
    render(<NextApp/>, document.getElementById('root'));
  }); 
}

create scr/components/App.js

import React from 'react'

export default (props) => (
  <h1>HMR test</h1>
)

起動

% node devServer.js

npm-run-all

最後に2つのフォアグランドプロセスをまとめて実行、停止できるようにします。

% npm install --save-dev npm-run-all

edit package.json

  "scripts": {
    "mock-server": "node mockServer.js",
    "dev-server": "node devServer.js",
    "serve": "npm-run-all --parallel mock-server dev-server"
  },  

rpm run serveCtrl-C を使って起動、停止ができます。

SEE ALSO

https://github.com/chimurai/http-proxy-middleware/blob/master/examples/browser-sync/index.js

Using Hot Module Replacement with React

Browsersync と webpack でやってみます。

Goal

少しわかりづらいのですが Live Reload を React Component 単位で行います。

f:id:okamuuu:20161125211944g:plain

準備

mkdir practice-react-hmr && cd $_
mkdir -p src/components www
npm init -y
npm install --save-dev browser-sync webpack webpack-dev-middleware webpack-hot-middleware
npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-stage-0 babel-preset-react react-hot-loader
npm install --save react react-dom

create webpack.config.js

const path = require('path')
const webpack = require('webpack')

module.exports = {
  debug: true,
  devtool: 'inline-source-map',
  entry: [
    'webpack/hot/dev-server',
    'webpack-hot-middleware/client',
    './src/index.js'
  ],
  output: {
    publicPath: "/",
    path: path.resolve('www'),
    filename: "bundle.js",
  },
  plugins: [
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  ],
  resolve: {
    extensions: ['', '.js'],
  },
  module: {
    loaders:[
      { test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel'] }
    ]
  }
}

create devServer.js

var webpack = require('webpack')
var browserSync = require('browser-sync')
var webpackDevMiddleware = require('webpack-dev-middleware')
var webpackHotMiddleware = require('webpack-hot-middleware')

var config = require('./webpack.config')
var bundler = webpack(config)

browserSync({
  server: {
    baseDir: config.output.path,
    middleware: [
      webpackDevMiddleware(bundler, {
        publicPath: config.output.publicPath,
        stats: { colors: true }
      }), 
      webpackHotMiddleware(bundler)
    ]   
  },  
  files: [
    'www/*.html'
  ]
})

create .babelrc

{
  "presets": ["es2015", "stage-0", "react"],
  "plugins": ["react-hot-loader/babel"]
 }

create www/index.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  </head>
  <body>
    <div id="root"></div>
    <script src="/bundle.js"></script>
  </body>
</html>

create src/index.js

import React from 'react'
import { render } from 'react-dom'
import App from './components/App'

render(
  <App />, 
  document.getElementById('root')
)

if (module.hot) {
  module.hot.accept('./components/App.js', function() {
    const NextApp = require('./components/App').default;
    render(<NextApp/>, document.getElementById('root'));
  }); 
}

create scr/components/App.js

import React from 'react'

export default (props) => (
  <h1>HMR test</h1>
)

run server

node devServer.js

ブラウザを確認しながら scr/components/App.js を編集するとブラウザを reoload せずに component だけ refresh することができます。

webpack で複数の entry ファイルを出力する

あらすじ

ユーザー画面と管理画面を SPA で作りたいんだけど component とか再利用したい時のお話です

mkdir practice-webpack-output && cd $_
mkdir -p src/{app,admin} www/{app,admin}
npm init -y
npm install --save-dev webpack babel-core babel-loader babel-preset-es2015

create .babelrc

echo '{"presets": ["es2015"]}' > .babelrc

create webpack.config.js

const path = require('path')
const webpack = require('webpack')

module.exports = { 
  entry: {
    "app/app": "./src/app/index.js",
    "admin/admin": "./src/admin/index.js"
  },
  output: {
    path: "./www",
    filename: "[name].bundle.js",
  },  
  devtool: 'inline-source-map',
  resolve: {
    extensions: ['', '.js'],
  },  
  module: {
    loaders:[
      { test: /\.js$/, exclude: /node_modules/, loader: 'babel' },
    ]   
  }
}

共通処理を記述

echo 'console.log("common")' > src/common.js

ユーザー画面用の処理を記述

echo 'import "../common"' > src/app/index.js
echo 'console.log("app")' >> src/app/index.js

管理画面の処理を記述

echo 'import "../common"' > src/admin/index.js
echo 'console.log("admin")' >> src/admin/index.js

コンパイル

$(npm bin)/webpack 

確認

% node www/app/app.bundle.js
common
app

% node www/admin/admin.bundle.js
common
admin

参考

http://webdesign-dackel.com/2015/09/10/webpack-multiple-output/

golang: gorm で 日付を定義したい

User.birthday や Diary.ymd みたいな date 型を指定したい場合は struct を以下のようにすれば良いらしい。 Update するときは Query に hms つけた状態でも ymd だけ更新されます。 Find するときは ymd 形式に変えてあげればいいと思います。

package main

import (
    "fmt"
    "time"

    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
)

type Diary struct {
    ID    int
    Title string
    Ymd   time.Time `sql:"not null;type:date"`
}

func main() {
    db, err := gorm.Open("mysql", "root:@/go_tutorial?charset=utf8&parseTime=True&loc=Local")
    if err != nil {
        panic("failed to connect database")
    }
    db.LogMode(true)
    db.AutoMigrate(&Diary{})
    db.Delete(&Diary{})
    ymd := time.Date(2014, time.December, 31, 0, 0, 0, 0, time.UTC)
    db.Create(&Diary{Title: "titile", Ymd: ymd})

    var diary Diary
    err = db.Where("ymd = ?", ymd.Format("2006-01-02")).First(&diary).Error
    if err != nil {
        panic(err)
    }
    newYmd := time.Date(2015, time.December, 31, 0, 0, 0, 0, time.UTC)
    diary.Ymd = newYmd
    db.Save(diary)

    err = db.Where("ymd = ?", newYmd.Format("2006-01-02")).First(&diary).Error
    if err != nil {
        panic(err)
    }

    fmt.Println(diary)
}

実行結果

[2016-11-16 16:37:13]  [0.36ms]  DELETE FROM `diaries`
[2016-11-16 16:37:13]  [0.44ms]  INSERT INTO `diaries` (`title`,`ymd`) VALUES ('titile','2014-12-31T00:00:00Z')
[2016-11-16 16:37:13]  [0.50ms]  SELECT * FROM `diaries`  WHERE (ymd = '2014-12-31') ORDER BY `diaries`.`id` ASC LIMIT 1
[2016-11-16 16:37:13]  [0.66ms]  UPDATE `diaries` SET `title` = 'titile', `ymd` = '2015-12-31T00:00:00Z'  WHERE `diaries`.`id` = '13'
[2016-11-16 16:37:13]  [0.59ms]  SELECT * FROM `diaries`  WHERE `diaries`.`id` = '13' AND ((ymd = '2015-12-31')) ORDER BY `diaries`.`id` ASC LIMIT 1
{13 titile 2015-12-31 00:00:00 +0900 JST}